Functions

Functions and higher-order functions are the key building block of a functional programming language.

In Flix, top-level functions are defined with the def keyword. For example:

def add(x: Int32, y: Int32): Int32 = x + y + 1

A definition consists of the function name followed by an argument list, the return type, and the function body. Although Flix supports type inference, top-level function definitions must declare the type of their arguments and their return type.

In Flix, all function arguments and local variables must be used. If a function argument is not used it must be prefixed with an underscore to explicitly mark it as unused.

First-Class and Higher-Order Functions

A higher-order function is a function that takes a parameter which is itself a function. For example:

def twice(f: Int32 -> Int32, x: Int32): Int32 = f(f(x))

Here the twice function takes two arguments, a function f and an integer x, and applies f to x two times.

We can pass a lambda expression to the twice function:

twice(x -> x + 1, 42)

which evaluates to 44 since 42 is incremented twice.

We can also define a higher-order function that requires a function which takes two arguments:

def twice(f: (Int32, Int32) -> Int32, x: Int32): Int32 =
    f(f(x, x), f(x, x))

which can be called as follows:

twice((x, y) -> x + y, 42)

We can call a higher-order function with a top-level function as follows:

def inc(x: Int32): Int32 = x + 1

def twice(f: Int32 -> Int32, x: Int32): Int32 = f(f(x))

twice(inc, 42)

Function Type Syntax

Depending on the number of arguments to a function, the syntax for the function type differs:

Unit -> Int32                // For nullary functions
Int32 -> Int32               // For unary functions
(Int32, Int32, ...) -> Int32 // For the rest

Function Composition

Flix supports several operators for function composition and pipelining:

let f = x -> x + 1;
let g = x -> x * 2;
let h = f >> g;     // equivalent to x -> g(f(x))

Here >> is forward function composition.

We can also write function applications using the pipeline operator:

List.range(1, 100) |>
List.filter(x -> x `Int32.mod` 2 == 0) |>
List.map(x -> x * x) |>
println;

Here x |> f is equivalent to the function application f(x).

Curried by Default

Functions are curried by default. A curried function can be called with fewer arguments than it declares returning a new function that takes the remainder of the arguments. For example:

def sum(x: Int32, y: Int32): Int32 = x + y

def main(): Unit \ IO =
    let inc = sum(1);
    inc(42) |> println

Here the sum function takes two arguments, x and y, but it is only called with one argument inside main. This call returns a new function which is similar to sum, except that in this function x is always bound to 1. Hence when inc is called with 42 it returns 43.

Currying is useful in many programming patterns. For example, consider the List.map function. This function takes two arguments, a function of type a -> b and a list of type List[a], and returns a List[b] obtained by applying the function to every element of the list. Now, if we combine currying with the pipeline operator |> we are able to write:

def main(): Unit \ IO =
    List.range(1, 100) |>
    List.map(x -> x + 1) |>
    println

Here the call to List.map passes the function x -> x + 1 which returns a new function that expects a list argument. This list argument is then supplied by the pipeline operator |> which, in this case, expects a list and a function that takes a list.

Pipelines

Flix supports the pipeline operator |> which is simply a prefix version of function application (i.e. the argument appears before the function).

The pipeline operator can often be used to make functional code more readable. For example:

let l = 1 :: 2 :: 3 :: Nil;
l |>
List.map(x -> x * 2) |>
List.filter(x -> x < 4) |>
List.count(x -> x > 1)

Here is another example:

"Hello World" |> String.toUpperCase |> println

Operators

Flix has a number of built-in unary and infix operators. In addition Flix supports infix function application by enclosing the function name in backticks. For example:

123 `sum` 456

is equivalent to the normal function call:

sum(123, 456)

In addition, a function named with an operator name (some combination of +, -, *, <, >, =, !, &, |, ^, and $) can also be used infix. For example:

def <*>(x: Int32, y: Int32): Int32 = ???

can be used as follows:

1 <*> 2