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.

Use the following guidelines to decide whether to write your functions in SPL or as native C++ or Java code.
  • 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.