Overview

You declare functions using the fn statement. The two forms of function declarations in MUFL are:

<name> = fn (args) [-> return-type] = (expression). // expression form
<name> = fn (args) [-> return-type] { code } // block form

Another convenient form for these declarations is:

fn <name> (args) [-> return-type] = (expression). // expression form
fn <name> (args) [-> return-type] { code } // block form

Valid Locations for Function Declarations

Above, name refers to a variable that holds the function. The same variable is used to invoke it later.

Grammatically, function declarations can be used in variable declarations as above, as well is in other contexts, for example, when defining a dictionary:

d = ("increment"->fn (x: int) { return x+1. }, "decrement"->fn (x: int) = (x-1)).

Additionally, functions in expression forms can be used in any expression. For example:

_print (fn (x:int) = (x+1) 1). // define a function and immediately invoke it, then print the result

Prints:

2

Returning a Value

If the return type is declared, then the value the function returns must be of a compatible type.

For block-form functions, return terminates evaluation of a function and returns immediately.

If a function does not have a return statement in its body, then the value of the last statement executed in the body is the return value.

Expression-form functions always return the result of the expression and infer the return type when omitted from the expression.

Arguments

Function arguments take the form [safe]name[:type]. For example:

F = fn (x:int) = (x+1).

If the type of an argument is omitted, the argument is treated as having type any.

Type Destructuring in Arguments

When the argument type is record or tuple, function declarations allow pattern destructuring in argument declaration.

fn F (record:($field1->arg1:int, $field2->arg2:str))
{ 
    _assert (record $field1 == arg1 && record $field2 == arg2). 
}

A more verbose alternative is:

fn F (record:($field1->int, $field2->str)) 
{ 
    bind ($field1->arg1, $field2->arg2) to record. 
    _assert (record $field1 == arg1 && record $field2 == arg2). 
}

The safe Attribute

Adding the safe attribute to an argument of a function instructs the MUFL compiler to generate runtime type verification for the given argument, essentially inserting a safe cast into the generated code.

Invoking Functions

Having assigned a function to a name, you can use this name to invoke the function using Curry notation:

fn increment (x:int) = (x+1). 
_assert (increment 5 == 6).

Binding Values

Functions in MUFL act much like lambda values in other functional languages. Local variables are always captured by value. The important exception to general norms of functional programming is that global variables are always captured by reference. Consequently, functions can be used to mutate global variables.

For more information, refer to Using Reference to Mutate Data.