Function Declarations
Other Declarations | Type Computations |
Using Declarations | TypeOf Example |
Writing Function Declarations | Constraints on Declarations |
Function declarations give a convenient way to reuse code; they are written once but can be used multiple times. This means that any improvements or fixes only need to be made in one place. They also provide additional benefits, such as recursion (the ability to call themselves) and polymorphism (working with different input types through a single declaration), both of which will be illustrated with examples.
In the Wolfram Compiler, declarations are supported by FunctionDeclaration, which has a rich syntax for specifying code to use in compilations. Also, they can be used in a variety of different ways.
There are other declarations in the Wolfram Compiler; these include LibraryFunctionDeclaration, which specifies functions that come from external libraries, and TypeDeclaration, which declares new types.
The following declares a function that computes exponents and adds them up:
This represents the declaration; in itself it does not cause any compilation to be carried out. However, it can be used in compilation functions such as FunctionCompile:
You can use the declaration with all forms of FunctionCompile. For example, this takes a list of functions and it returns a list of compiled functions:
The declaration can also be used with other compilation functions:
Passing in the declarations in this way is a a convenient and simple way to get started developing your code.
DownValuesFunction
The code in a function declaration can be given directly with a Function; the previous section had examples of this. They can also be taken from declarations given with := in the normal fashion for the Wolfram Language. This is done with DownValuesFunction.
The following is a definition for a function that adds up exponents:
The definition can be called directly in compiled code using DownValuesFunction:
But to call the definition many times, it may be convenient to use FunctionDeclaration:
A function that uses the declaration can be compiled and the resulting compiled code used:
It should be noted that code compiled from DownValuesFunction converts everything into efficient machine code.
KernelFunction
Another way to give the implementation of a declaration is to use the Wolfram Language interpreter. This is done with KernelFunction and is a useful way to add support for things that are not currently supported in the compiler.
One example is the use of file operations. First, create a temporary file:
The declaration uses KernelFunction on Get, which tells the compiler to call the interpreter:
Call the code; the result is a packed array of integers:
There will be an error if the result of the kernel call is not a packed array of integers:


Inside the compiled code, the result of the KernelFunction can be used in other computations:
The argument to KernelFunction can be a Wolfram Language Function (written with &). This allows it to have other arguments:
When KernelFunction is used from compiled code, the Wolfram evaluator is used. This will lose some of the speed advantages but will benefit from the flexibility of the Wolfram System.
Other Declarations
LibraryFunctionDeclaration declares functions that live in external libraries and is described in more detail in another section. OperationDeclaration declares operations for types, it is described in a later section.
Using Declarations
Declarations can be used directly in compilation functions such as FunctionCompile, as was seen in the previous section. They can also be used in other ways.
Compiler Environments
A compiler environment is a convenient way to use many declarations in different functions. There is a default environment, $CompilerEnvironment. In addition, compilation functions, such as FunctionCompile, all take a CompilerEnvironment option that allows you to use your own environments.
You can add declarations to the default compiler environment:
Now a compilation will automatically have access to those declarations. This is because the compilation here is using the default environment:
You can use CompilerInformation to see the new declaration:
One advantage of using a compiler environment is that intermediate code is cached in the compiler environment and further compilations can be faster. When a list of declarations is given to a compilation function, such as FunctionCompile, a new environment is made each time and so this optimization for compilation is not available.
Resetting the Compiler Environment
if you no longer want the declarations you added to the default compiler environment, you can just reset it to a clean initial version:
Now the compilation will fail:

Custom Compiler Environments
You can make your own compiler environment and add declarations to that:
Setting the CompilerEnvironment option to your own value allows FunctionCompile to work:
Compiled Components
In order to handle a collection of many declarations, it can be useful to collect them into a CompiledComponent. These have a number of benefits, such as convenient ways to build into and use from a library. The more detailed examples are described in a later section. In addition, it is quite convenient to store a CompiledComponent into a Wolfram Language paclet.
For a demonstration, the following DeclareCompiledComponent adds two declarations to a component called "DemoComponent":
You can inspect the component:
Now a compilation will automatically have access to those declarations:
This calls the compiled code using the declarations from "DemoComponent":
More information on compiled components is found in another section.
Writing Function Declarations
Overloading
Several function declarations can use the same name. These functions can take different numbers of arguments and different types. The compiler will select the actual appropriate version.
Here are three versions of a function called MyMax. The first takes a single 64-bit integer, the second takes two 64-bit integers, and the third takes two double-precision numbers:
This compiles calls to each different version of MyMax:
This calls each function, giving the expected result:
A function name with several declarations with different types and/or different numbers of arguments can be said to be overloaded. Another term for function overloading is ad hoc polymorphism.
Looking at the declarations of MyFun, it can be noticed that the versions that took two arguments had the same implementation for the integer and real cases. It would be better to write these with just one declaration and have this work in both cases. This is possible; it is known as parametric polymorphism and is explored in the next section.
You can see the definitions with CompilerInformation if they are added to a compiler environment:
Inline Setting
FunctionDeclaration can include settings for the function, such as its inline behavior. A function is inlined when a call to the function is replaced with a body of the function. Function declarations can use the "Inline" setting. In the following example, the default inlining setting is used, and the function call is not inlined:
Now a setting of "Hint" is used, and the function call is replaced by the body of the function:
If the function is relatively cheap to execute, inlining it can lead to faster code. Inlining is quite a complicated topic, and the pipeline of tools for the Wolfram Compiler has various stages of inlining.
Other settings for inlining are "Never", which never inlines code, and "Always", which always inlines the code. The latter can be useful when a declaration uses ToRawPointer to allocate a pointer on the stack. There is an example of this in a later section.
ForAllType—One Declaration, Many Types
The Wolfram Compiler supports a powerful form of function declaration that allows a single declaration to work for many types. This is called parametric polymorphism, and in the Wolfram Compiler, the construct ForAllType is used to set up declarations of this form.
Here is a declaration of a function that works for many types. It takes two arguments and returns a result, all of the same type. It uses a type variable, v, to represent the type. If the same variable is used in several places, they all have to represent the same type. This is just like in the Wolfram Language pattern matcher, where a pattern variable bound more than once has to bind to the same expression. Type variables in ForAllType can be symbols or strings:
Compile a call to MyMax with two "Integer64" arguments:
Compile a call to MyMax with two "Real64" arguments:
So this is very satisfactory. However, there are some issues to be considered.
Trying to compile a call where the arguments are not the same type fails. The message and the error markup tell you that it is the call to MyMax that was a problem:

The declaration could be adjusted to work with different types.
A different problem arises when compiling a call where the arguments are Boolean. The message and the error markup tell you that the problem is inside the declaration of MyMax:

An alternative would be a declaration that only works for the types the body supports. This can be achieved by adding a constraint to the type variable specifying that the actual type replacing it must belong to the "Ordered" type class:
Now the compilation fails, but the error information is more useful because it is the declaration that is at fault:

You can see all the types that will work for this type declaration. You can see that "Integer64" and "Real64" both work:
A key point about parametric polymorphism is that it makes very efficient code. Traditional class-based, object-oriented languages obtain polymorphism by means of virtual functions, i.e. by switching to the appropriate implementation at runtime. The parametric polymorphism used in the Wolfram Compiler takes a polymorphic version of the function and compiles it with the specific types, creating a special version just for those types. This process, which can be monomorphization, leads to very efficient code, since the specific functions can be cross-optimized together with function inlining, code elimination and so on.
Function Selection
The Wolfram Compiler allows the same function to have more than one declaration. When the declarations differ by the number of arguments or their types, no problem arises because only one function could match.
When there are type variables, it is more complicated because more than one declaration could possibly match. For example, here are three declarations:
This compiles three functions:
These are calls to the three functions:
So each function has picked out one of the declarations. The function with a Boolean input picked the declaration for Boolean. The function with an integer input picked the declaration that was restricted to "Integers", and the real input picked out the unrestricted declaration. The inference system actually picks the narrowest declaration that can match. In an intuitive sense, Boolean seems the narrowest and the unconstrained polymorphic declaration the most general.
The concept of narrowest can be put onto a more formal and specified form with the concept of subsumption.
Subsumption
The concept of a narrow type can be expressed more formally as a subsumption. Suppose there are two types, T and S, and T has type variables. Then it can be said that T subsumes S if there is a substitution of type variables in T so that it covers everything covered by S.
For T being ForAllType[{"a"},{"a"}"String"] and S {"Integer64"}"String", if a substitution in T is made that "a" is "Integer64", then the result clearly covers everything in S. Thus T subsumes S, and S is chosen.
Another use of this is for type equality. If T subsumes S and S subsumes T, it can be said that T and S are equal.
It can be that two types do not have a subsumption relation. For example, ForAllType[{"a"},{"a"}"String"] and ForAllType[{"a"},{"a"}"Integer64"] do not have a relation.
The subsumption relation over a set of types makes for a partially ordered set. When the Wolfram Compiler carries out type inferencing, if there are multiple choices, it will see if there is a partial minimum for the choices. If there is a minimum, this will be used. If there is no minimum, then the compilation fails and additional constraints need to be added. Techniques for this are described in the next section.
Solving Selection Problems
The subsumption mechanism described above does not work in all cases. The following is an example:
When the input is a "Real64", there is only one declaration that can match, and compilation works:
When the input has a type of "Integer64", both declarations can match. Since one of the declarations results in a string and the other in an integer, there is no subsumption relation between them, i.e. there is not a substitution to convert one into the other.
Since it is not obvious which one should be used, the compilation fails:

If the result is guided to a solution using TypeHint, then compilation can work:
Another solution is to add constraints to the declarations so that more than one declaration cannot match; NotElement can be useful for this:
So the solution to being able to compile declarations such as this is to use more constraints. More information on constraints is given later in this section.
Type Computations
Writing polymorphic functions raises new challenges and problems. Breaking the task into sub-parts is a useful way to make progress. Another powerful way to move forward is to take advantage of the interactive nature of the Wolfram Language.
One division is first to set up the types and then to actually implement the code.
The following is a declaration with a dummy body:
This declaration can be tested with a call to the top-level TypeOf function:
If the types appear to be correct, then it would be possible to continue and actually implement the function.
If the output type is not easily related to one of the arguments, there are still many ways to make progress. Many of these make use of TypeEvaluate.
TypeEvaluate
For an example of TypeEvaluate, it will create a function that takes a packed array and outputs an array of higher rank than the input. Thus, for an input vector the result would be a matrix, and for a matrix the result would be a rank-3 tensor.
The following is a declaration with a dummy body; initially the problem is just to make the types work. In this implementation, 1 is added to the rank:
This tests the type for an input of vector of reals:
Here the resulting type is tested for an input that is a matrix of complex numbers:
Now the type for this function is as desired you can continue to develop the implementation.
TypeOf
Another way to write the type signature of a function such as this is to use TypeOf inside the TypeEvaluate. This is useful if the type of the result (or some element of the result) can be expressed in terms of another function.
This example function takes a packed array matrix and returns a packed array vector:
With an input of a matrix of reals, the output is a vector of reals:
When the input is a matrix of complex reals, the output is also a vector of reals:
In this way, you can work to set up the types for your function.
Information
There is a lot of flexibility in the use of TypeEvaluate to specify types in declarations. It can be used in other positions besides the result. It can be used with other constructs, such as Information. In this example, it is used to specify a nested function passed as an argument to a function. This would work as a type for Fold.
Here the second argument to the nested function has a type set by the type contained in the container argument. It can be done without knowing what the container actually is:
The container is a packed array matrix of reals. The contained type will be a vector of reals:
Here, the container is a "DynamicArray" data structure of strings. The operation is adding up the lengths of the elements:
In both of these examples, the initializer was set explicitly to a literal. In one case, a real zero and in another, an integer zero. There are possibilities to make these more polymorphic, and this is explored in another section.
TypeOf Example
This is an example of the use of TypeEvaluate and TypeOf in a running function.
This is a polymorphic function that accepts two arguments, prints them and multiplies them. This can use the Times function in the implementation, but it is useful to express the type. This is done conveniently as shown below:
Now TimesWithPrint is called with arguments of type "Integer64" and "Real64":
Now the same declaration is used with types "Integer64" and "PackedArray"::["Real64", 1]:
Using techniques like this, polymorphic functions can be written that are efficient and that work for different input types.
Constraints on Declarations
This section explores how to set constraints so that declarations apply to a narrower set of arguments. This helps to get better error messages, since it avoids unexpected code from trying to compile. It's also valuable when there are problems from multiple declarations as it helps find a desired solution.
This section will cover the ways that constraints can be used in declarations.
Element
Element can be used to select types.
In this declaration, the type variable is constrained to be an element of "Integers":
If the input is an integer, compilation works:
However, if the input is not an integer, compilation fails:

Of course, in this case, there might be other declarations for MyTest1, so compilation could work.
As ever, it is quite useful to use CompilerInformation to see what is included in an abstract type:
NotElement
NotElement can be used to exclude types.
In this declaration, the type variable is constrained to not be an element of "Integers":
If the input is not an integer, compilation works:
However, if the input is an integer, compilation fails:

Of course, in this case, there might be other declarations for MyTest1, so compilation could work.
The use of NotElement to exclude types was shown earlier to allow compilation to progress in cases where choosing the narrowest type could not work.
TypeEvaluate
TypeEvaluate can be used in a constraint for a declaration. In order for it to work once the type variable actually becomes a concrete type, the result should be True or False. One key usage is for packed array computations that use a literal type to hold the rank, as shown below:
The rank of the input is 2, so compilation succeeds:
The rank of the input in this example is 2, so compilation fails:

Alternatives
The arguments in Element can include an abstract type. However, sometimes there is not a convenient abstract type for a declaration. While this could quite easily be added, a way to avoid that is to use an alternatives setting in Element:
The input is an integer, so compilation succeeds:
The input is a string, so compilation succeeds:
The input is a Boolean, so compilation fails:

Another very useful form of the alternatives is when one of the types is compound:
The input is an integer that matches the declaration:
The input is a packed array that also matches the declaration:
The input is a rank-2 packed array that does not match the declaration:

This use of alternatives is very useful, since it gives opportunities to describe the shape of one of the types. It also allows the constraint to be narrowed to a group of types where there is not a convenient type class.