Native functions
Native functions are declared with SPL syntax in XML function model files, but native function implementations are defined in a native file.
An example native function prototype is:
<ordered T> T max(list<T>)
This example declares a generic max
function
that works on lists of any ordered type T
. An ordered
type is a type for which the ordered comparison operators (<, >, <=,
and >=) are defined, including strings, time
stamps, enumerations, blobs, and all numeric types except complex
numbers. Here is the syntax:
functionPrototype ::= genericFormals functionModifier*
type ID ‘(' protoFormal*, ‘)'
genericFormals ::= ( ‘<' typeFormal+, ‘>' )? ( ‘[' boundsFormal+, ‘]' )?
typeFormal ::= typeFormalConstraint ID
typeFormalConstraint::= ‘any' | ‘collection' | ‘complex' | ‘composite' | ‘decimal'
| ‘enum' | ‘float' | ‘floatingpoint' | ‘integral'
| ‘list' | ‘map' | ‘numeric' | ‘optional' | ‘ordered'
| ‘primitive' | ‘set' | ‘string' | ‘tuple'
boundsFormal ::= ID
protoFormal ::= formalModifier* type ID?
Like
SPL functions, native functions are stateless unless explicitly declared
stateful, and their parameters are immutable unless explicitly declared
mutable. Unfortunately, it is impractical to statically check statelessness
or strict deep parameter immutability for native functions, so library
vendors must be careful to declare native functions stateful or parameters
mutable when they read or write outside state. The modifier public
for native
functions makes the function visible from other namespaces. Without
the modifier, native functions are private and only visible in their
own namespace.
A generic type formal such as <ordered
T>
in the max
function prototype can match
any actual parameter type subject to two restrictions: first, the
actual parameter type must obey the typeFormalConstraint
;
and second, the match must be the same even if it occurs in multiple
parameters. Consider the following native function prototype:
<list T> T concat(T vals1, T vals2)
In this case, the first restriction requires
that the parameters must be of some list types, and the second restriction
requires that both parameters are of the same list type. For instance,
the call concat ([1,2],[3,4,5])
is correct because
both parameters are of type T==list<int32>
.
Besides
generic type formals, a native function prototype also has optional
generic bounds formals. For example, here is a prototype that overloads
the max
function for bounded lists:
<ordered T>[N] T max (list <T>[N])
Again, if the bounds-formal appears multiple times in the prototype, then all the matching bounds from actual parameter types must be identical. If there are ambiguous overloaded versions of the same generic native function in the same scope, the compiler flags an error.
For language experts: Native function call resolution, type checking, and overload conflict detection use a standard unification algorithm, augmented with checks for the constraints of type formals.
- Write an SPL function if you want to avoid the burden of going to a different file or language, if you want the portability of auto-generated code, or if you want the compiler to check statelessness for you.
- Write a native function if you want to reuse existing native code, if you need generics, if the native language permits a more natural implementation, or if the native function is significantly faster than a function that is written in SPL.