Overview

MUFL is a functional programming language that uses Curry notation to describe both function calls and container lookups. In brief, it is a no-side-effects programming language operating over immutable data.

Functions

MUFL has two types of functions: expression and procedural.

An expression function is limited to being a single expression, and is not allowed multiple statements:

fn A (a: int, b:int) = (a + 2*b).

A procedural function has a body that can have multiple statements:

fn B (a: int, b: int) -> int { 
	var = 2 * a. 
	var -> var + b. 
	return var. 
}

Functions are declared with optionally-typed parameters and return value.

Functions are invoked using Curry notation:

fn B (a:int, b:int) = (a + b). 
_print (B 1 2) "\n". // calculates 3
3

Curry Notation

MUFL uses Curry notation for both function calls and container access. Curry syntax eliminates the () for function calls, replacing it instead with expressions separated by whitespace.

Result1 = OneArgumentFunction 1. 
Result2 = TwoArgumentFunction "hello" "world". 

Adding () after an atom, such as a name or a parenthesized expression, is a convenient shortcut indicating a reduction with the value true. This syntactic construct is important because MUFL does not have zero-parameter functions. To simulate zero-parameter behavior, a one parameter function is used. Invoking it as F() is a readable alternative to F true.

The () syntax has high lexicographic precedence, so expression A B() resolves to A (B true), not (A B) true.

fn F (_) { _print "HELLO". }
F(). // will print "HELLO".
HELLO

The underscore as a variable name is treated as a discarded variable and does not generate an error if used multiple times. Any variable whose name starts with underscore skips unused variable checks. By convention, primitive function names start with an underscore.

Curry notation is also used as subscript lookup into containers:

A = [1,2,3,4]. // initialize an array
_print (A 0). // will print '1'
1

Value Capture

Functions capture local variables by value, and global variables by name. This approach enables functions to have what appears to be a side effect of modifying a variable in the global scope. However, this apparent mutability of global variables is slightly misleading because all data, including packets, is immutable. In practice, what happens is that all functions in MUFL implicitly take packet state as input and implicitly return new packet state as output (known as a form of a monad in functional programming), enabling MUFL functions to construct new states with very little additional syntactic overhead.

Functions as Data

Functions can be stored in data structures (dictionaries) and assigned to variables. This example constructs a dictionary whose two string keys map to two different functions:

integer_ops = ($add->fn a:int, b:int = (a+b), 
				$subtract->fn a:int, b:int = (a-b)).

which leads to this clever way of constructing a function call:

integer_ops $add 1 16. // returns 17
integer_ops $subtract 5 2. // return 3.

Result:

17
3

The $ syntax is a shorter alternative to quoted strings ($xyz is equivalent to "xyz"). For more information, refer to the string constants syntax description.