Skip to main content

Functions

As a functional programming language, NumFu treats functions as first-class values that can be created, passed around, and composed to build complex programs from simple parts.


Function Basics

Creating Simple Functions

In NumFu, functions are created using lambda expressions with the syntax {parameters -> body}:

{x -> x + 1}                    // Function that adds 1
{x -> x * x} // Function that squares a number
{name -> "Hello, " + name} // Function that creates a greeting

Calling Functions

Call functions using parentheses:

let square = {x -> x * x} in square(5)     // 25
let greet = {name -> "Hello, " + name} in greet("NumFu") // "Hello, NumFu"

Inline Function Calls

You can call lambda expressions directly:

{x -> x + 1}(10)               // 11
{x, y -> x * y}(3, 4) // 12

Multiple Parameters

Multiple Arguments

Functions can accept multiple parameters separated by commas:

{x, y -> x + y}                // Addition function
{a, b, c -> a * b + c} // Linear function
let say = {name, age -> format("{} is {} years old", name, age)} in
say("John", 42)
// John is 42 years old

Recursive Functions

Named functions can call themselves recursively:

let fibonacci = {n ->
if n <= 1 then n
else fibonacci(n - 1) + fibonacci(n - 2)
}

fibonacci(10) // 55

You can control the maximum recursion depth using the CLI argument --rec-depth (see CLI reference).

Tail Call Optimization

NumFu automatically optimizes tail-recursive function calls, allowing you to write recursive algorithms without running into stack overflow or performance issues.

A function call is in "tail position" when it's the last operation before returning. NumFu can optimize these calls to use constant memory instead of growing the call stack:

// Tail-recursive factorial (optimized)
let factorial = {n, acc ->
if n <= 0 then acc
else factorial(n - 1, acc * n)
}

// Non tail-recursive factorial (not optimized)
let factorial_slow = {n ->
if n <= 0 then 1
else n * factorial_slow(n - 1) // multiplication happens after the call
}

The tail-recursive version works, but the non-optimized version fails with large values:

>>> factorial(10000, 1); factorial_slow(10000)
2.84625968091707e+35659
[at REPL:1:?]
RecursionError: maximum recursion depth exceeded

You can control the maximum maximum number of tail-call iterations using the CLI argument --iter-depth (see CLI reference).


Rest Parameters

Collecting Extra Arguments

Use ...paramName to collect remaining arguments into a list:

{first, ...rest -> [first, rest]}(1, 2, 3, 4, 5)
// [1, [2, 3, 4, 5]]

{...args -> length(args)}(1, 2, 3) // 3

Each function can only have a single rest parameter which must come at the end.

Example

import length, slice from "std"

let sum = {...numbers ->
let helper = {nums, acc ->
if length(nums) == 0 then acc
else helper(slice(nums, 1, -1), acc + nums[0])
} in helper(numbers, 0)
} in
sum(1, 2, 3, 4, 5) // 15

Higher-Order Functions

Functions That Accept Functions

Functions can take other functions as parameters:

let twice = {f, x -> f(f(x))} in
let add1 = {x -> x + 1} in
twice(add1, 5) // 7 (add1(add1(5)))

Functions That Return Functions

Functions can return other functions:

let makeAdder = {n -> {x -> x + n}} in
let add10 = makeAdder(10) in
add10(5) // 15

Function Factories

Create specialized functions using function factories:

let makePowerFunction = {exponent ->
{base -> base ^ exponent}
} in
let square = makePowerFunction(2) in
let cube = makePowerFunction(3) in
[square(4), cube(3)] // [16, 27]

Closures

Capturing Environment

Functions capture variables from their surrounding environment:

let multiplier = 10 in
let scale = {x -> x * multiplier} in
scale(5) 50

Closures Preserve Values

Even when the original scope ends, the captured values remain:

let makeCounter = {start ->
let current = start in
{increment -> current + increment}
} in
let counter = makeCounter(100) in
counter(5) // 105

Examples

Configurable Greeting

// Create a configurable greeting function
let makeGreeting = {greeting, punctuation, name ->
greeting + ", " + name + punctuation
} in
let casual = makeGreeting("Hi") in
let casualExcited = casual("!") in
casualExcited("Alice") // "Hi, Alice!"

Conditional Function Selection

Choose functions based on conditions:

let operation = "square" in
let processor = if operation == "square" then {x -> x * x}
else if operation == "double" then {x -> x * 2}
else {x -> x} in
processor(6) // 36

Mathematical Functions

import sqrt from "math"

// Distance between two points
let distance = {x1, y1, x2, y2 ->
let dx = x2 - x1, dy = y2 - y1 in
sqrt(dx^2 + dy^2)
} in
distance(0, 0, 3, 4) // 5

Function Composition Function

// Compose multiple functions
let compose = {f, g -> {x -> f(g(x))}} in

let add1 = {x -> x + 1} in
let double = {x -> x * 2} in

compose(double, add1)(5) // 12 (double(add1(5)))

Higher-Order Functions

// Create a function that applies another function twice
let applyTwice = {f, x -> f(f(x))} in
let increment = {x -> x + 1} in
applyTwice(increment, 10) // 12
// Create a function that creates multiplier functions
let makeMultiplier = {factor -> {x -> x * factor}} in
let triple = makeMultiplier(3) in
triple(7) // 21

Recursive Functions

// Factorial function
let factorial = {n ->
if n <= 1 then 1
else n * factorial(n - 1)
} in
factorial(5) // 120
// Square root using Newton's method
let sqrt = {x ->
let improve = {guess -> (guess + x / guess) / 2} in
let iterate = {guess, n ->
if n <= 0 then guess
else iterate(improve(guess), n - 1)
} in iterate(x / 2, 10)
} in sqrt(25) // 5