Skip to content
MnL HelpMacaca nigra Language v5.2.0 · build 240826
04 / Functions

Functions

MnL treats a function as a first-class citizen: it can be bound to a name, passed as an argument, and returned from another function. There are two function blocks — the lambda block, which is an expression, and the function block, which is a declaration.

The two blocks
  • Lambda is an expression block taking two inputs: a parameter and an expression.
  • Function is a declaration block taking three: an identifier for the name, a parameter, and an expression.

The lambda block

A lambda is an anonymous function — one with no name. An empty lambda block starts with both inputs unfilled and a warning triangle showing what is still missing.

An incomplete lambda block with an empty parameter input and an empty expression input.
Fig. 1 An incomplete lambda block.
A lambda block whose parameter is unit and whose body is a string.
Fig. 2 A unit parameter, returning a string.
A lambda block taking a string parameter named hello and concatenating it after the prefix 'MNL: '.
Fig. 3 A string in, a string out.

Naming a lambda is a matter of connecting it to a variable block:

A variable named mnl_say bound to a lambda, and a second variable say_it applying mnl_say to 'Hello there'.
Fig. 4 A lambda bound to the name mnl_say.

SML

val mnl_say = fn (hello) => ("MNL: " ^ hello)
val say_it = mnl_say ("Hello there")

Scala

val mnl_say = (hello : String) => ("MNL: " + hello)
val say_it = mnl_say ("Hello there")

The function block

The function block folds that pairing — a variable bound to a lambda — into a single declaration.

A function block named greetings with a unit parameter returning the string 'Hello there', and a variable say_it applying it.
Fig. 5 The function block.

SML

fun greetings () = "Hello there"
val say_it = greetings ()

Scala

def greetings () : String = "Hello there"
val say_it = greetings ()

Bound — using a name

Declaring a name is only half of it. To use a name — a parameter, a variable, another function — you reach for the Bound block from the Expression category and type the name into it. Every reference in the Factorial example, including the recursive call to factorial itself, is a Bound block.

An Application block applies a function to an argument: the function goes in the Application of input and the argument in the Over input.

Recursion

A function may refer to itself by name, which is all recursion requires.

A recursive function tail_function that returns 1 when n is at most 1 and otherwise multiplies n by tail_function of n minus 1.
Fig. 6 A recursive function.

SML

fun tail_function (n) =
  if (n <= 1) then 1 else (n * tail_function ((n - 1)))

Scala

def tail_function (n : Float) : Float =
  if (n <= 1) then 1 else (n * tail_function ((n - 1)))
Watch it run

Recursion is where the visualization views earn their keep. Load the bundled Factorial example, switch to the Debug perspective, and step the CSEK machine to watch the continuation stack grow and unwind.

Two or more parameters

MnL’s core is the lambda calculus, where a function takes exactly one parameter. To pass more than one value, group them — either into a tuple or into a record — and take that as the single parameter.

A tuple as the parameter

A function greetings taking a tuple parameter and concatenating its first and second items.
Fig. 7 A tuple as the function parameter.

SML

(* SML does not support type inference for a tuple as a parameter.
   Other ML languages may support the syntax below. *)

Scala

/* the first item index is 0 in Scala */
def greetings (tpl : (String, String)) : String =
  ((tpl(0)) + (tpl(1)))

val greetings_1 = greetings (("Hello ", "World!"))
val greetings_2 = greetings (("World!", "Hello "))

A record as the parameter

A record does the same job but names the parts, which reads better once there are more than two.

A function taking a record parameter and combining two of its named fields.
Fig. 8 A record as the function parameter.

SML

(* SML does not support type inference for a record as a parameter.
   Other ML languages may support the syntax below. *)

Scala

/* Scala has no primitive Record type */

The alternative is currying — a function returning a function, one parameter at a time. That is the subject of the next section.

Higher-order functions

A higher-order function takes a function as a parameter, returns a function as its result, or both.

A function as a parameter

A fold_list function taking a list, an accumulator and an operator function, folding the operator across the list.
Fig. 9 fold_list takes the folding operator as a parameter.

SML

fun fold_list (list_a) =
  fn (accumulator) => fn (operator) =>
    let
      fun supporter (list_b) =
        if null (list_b) then accumulator
        else operator (hd (list_b)) (supporter (tl (list_b)))
    in
      supporter (list_a)
    end

fun sum (a) = fn (b) => (a + b)

val application_of_fold = fold_list ([17, 2, 2004]) (0) (sum)

Scala

/* Scala does not support a type variable on a lambda here */

Returning a function

A function that returns a function lets you supply arguments one at a time — each application yields another function until the last one produces a value. This is partial application, and there is a bundled example of it under Examples › Basic.

A function f_continuation taking param_1 and returning nested lambdas over param_2 and param_3 that concatenate all three.
Fig. 10 Returning a function, applied one argument at a time.

SML

fun f_continuation (param_1) =
  fn (param_2) => fn (param_3) => (param_1 ^ (param_2 ^ param_3))

val first_app  = f_continuation ("Hello ")
val second_app = first_app ("World")
val the_result = second_app ("!")

Scala

def f_continuation (param_1 : String) : (String) => (String) => String =
  (param_2 : String) => (param_3 : String) => (param_1 + (param_2 + param_3))

val first_app  = f_continuation ("Hello ")
val second_app = first_app ("World")
val the_result = second_app ("!")
Seeing a function’s value

Right-click any Function block and choose Function value to render what the function actually is — the lambda it denotes, drawn as blocks. That is often the clearest way to understand a partially applied function. See function value.

MnL is developed at L-Workshop. This help was written against MnL 5.2.0 (build 240826).