Tuple types
Wherever an item type can be used (for example, in the type signature of a function or variable,
or in an instance of
expression), Saxon allows a tuple type to appear. The syntax is:
"tuple" "(" fieldName "?"? (":" sequenceType)? ("," fieldName "?"? (":" sequenceType)? )* ("," "*")? ")"
where fieldName
is an NCName. Whitespace is advisable before or after the colon. A question mark ("?") after
a field name indicates that the field is optional. A final ", *" at the end of the list of fields indicates that the tuple
type is extensible: additional fields beyond those that are explicitly declared are permitted to appear in the map.
For example:
tuple(ssn: xs:string, emp: element(employee))
or:
tuple(ssn, emp)
or:
tuple(firstName: xs:string, middleInitial?: xs:string, lastName: xs:string, *)
A tuple type is essentially a way of defining a more
precise type for maps. The first example will be satisfied by any map that has an entry whose key is "ssn" and whose
value is a string, plus an entry whose key is "emp" and whose value is an employee
element. The second
example matches any map with entries having the keys "ssn" and "emp", regardless of the types of the values.
More formally, a value M
is an instance of a tuple type tuple(N1:T1, N2:T2, ...)
(where
Tn
defaults to item()*
if not specified) if
and only if:
-
M is a map
-
For every (
N
,T
) pair in the tuple definition:-
If the field is not optional (that is, there is no question mark after the field name in the type definition), then
map:contains(M, N)
is true. -
If
map:contains(M, N)
is true, thenmap:get(M, N) instance of T
returns true
-
-
If the tuple type is not extensible (there is no final ", *" after the list of field names), then every key present in the map is a string equal to one of the field names appearing in the tuple definition.
That is, for every field defined in the tuple definition, the map will typically contain an entry whose key matches the field name and whose value matches the corresponding type. The entry is allowed to be absent only if the type definition indicates that it is optional. Additional entries are allowed only if the type definition indicates that it is extensible.
Since a tuple is a map, fields can be accessed using the lookup syntax $T?NNN
where $T
is an expression that returns a tuple, and NNN
is the name of the field.
Using tuples makes stronger type checking possible. With a general-purpose map, any atomic value can be
supplied as a key, and the map:get()
function (or a dynamic call on the map as a function) returns
an empty sequence if the entry is not present. With tuples, however, supplying a key value that is not a string,
or that is not one of the known field names, results in a static type error if it can be detected at compile time.
Furthermore, if $T
is known to be a tuple type, then the type of an expression such as $T?field
can be statically inferred, which again enables better error detection and better optimization.
Tuple types are useful where maps are used to hold information with a regular structure; for example when a function accepts maps as input or returns maps as output. Declaring the argument or result type of a function as a tuple type often gives better information about the expected contents than when it is declared as a map.
In XSLT, as described below, it is possible to declare the type in both
portable and Saxon-specific syntax, for example
<xsl:param name="location" as="map(xs:string, xs:double)" saxon:as="tuple(x: xs:double, y: xs:double)"/>
.
A map that contains additional entries beyond those defined in an extensible tuple type still conforms to the type (so if the expected
type of a function argument is tuple(a: xs:integer, b: xs:integer, *)
, then you can supply any
map that contains these two fields, even if it also contains others); however,
static references to such entries will result in a type error even if the map is extensible. Static references are only allowed
to fields that are explicitly declared.
Saxon does not currently attempt to make subtyping inferences about tuple types, so for the time being it is best to avoid using them within the signatures of higher-order functions.
The following XSLT example shows a couple of functions from a library designed to perform
complex number arithmetic, where a complex number is defined as the type tuple(r:
xs:double, i: xs:double)
:
The same functions could be written in XQuery:
declare function cx:complex ($real as xs:double, $imag as xs:double) as tuple(r: xs:double, i: xs:double) { map{'r':$real, 'i':$imag} }; declare function cx:add ($x as tuple(r: xs:double, i: xs:double), $y as tuple(r: xs:double, i: xs:double)) as tuple(r: xs:double, i: xs:double) { cx:number($x?r + $y?r, $x?i + $y?i) };Two additional Saxon extensions help to improve the usability of tuple types:
- A commonly used type can be referenced using a type alias, for example
as="~cx:complex"
. Type aliases are available in both XSLT and XQuery, and are described in Type Aliases. Type aliases go some way towards hiding the implementation details of the type, and allowing the implementation to be changed without affecting users of the function library; in addition they simply make the code more readable. - In XSLT only, elements that have an
as
attribute to define the type of a variable, function, or parameter, also have an optional saxon:as attribute which allows additional type information to be supplied using Saxon extension syntax. If both attributes are present, then the type specified insaxon:as
must be a subtype of the type specified in theas
attribute. Typical usage might be<xsl:param name="x" as="map(*)" saxon:as="tuple(r: xs:double, i: xs:double)"/>
. This device allows tuple types to be used without compromising the portability of the stylesheet to an XSLT 3.0 processor that does not recognize Saxon's extension syntax (an XSLT 3.0 processor that does not recognize thesaxon:as
attribute is required to ignore it).