Skip to content
FutureBlitz

    Install

    Basics, 8 of 8

    Function values and lambdas

    Pass functions as values, write them inline as lambdas, and keep state in closures.

    What you’ll build

    A tiny calculator that looks up its operations by name, and a few tools for working on lists of numbers. Both treat functions as values: stored in variables, passed to other functions, and written inline where they’re needed.

    A function is a value

    A function’s name, without parentheses, is a value that can be stored and called later. Its type is written Func(Int): Int: a function that takes an Int and gives an Int. Func(String), with no result type, gives nothing.

    values.fb
    Import std.core
    
    Function Twice(x: Int): Int => x * 2
    Function Square(x: Int): Int => x * x
    
    Local change: Func(Int): Int = Twice
    Print(change(5))
    change = Square
    Print(change(5))
    Output
    10
    25

    Passing a function

    A parameter of a Func type takes a function, so the caller decides what work is done. Apply runs a function on each number in a list.

    values.fb
    Import std.core
    
    Function Twice(x: Int): Int => x * 2
    Function Square(x: Int): Int => x * x
    
    Function Apply(numbers: Int[], change: Func(Int): Int): Int[]
        Local result: Int[] = []
        For n In numbers
            result.Add(change(n))
        End For
        Return result
    End Function
    
    Print(Apply([1, 2, 3], Twice))
    Print(Apply([1, 2, 3], Square))
    Output
    [2, 4, 6]
    [1, 4, 9]

    Lambdas

    A lambda is a function written where it’s used, with Function and no name. Function(x) => x + 1 gives one expression’s value; a lambda of several lines ends with End Function. Where a Func is expected, a lambda’s parameters take its types, so they needn’t be written.

    values.fb
    Import std.core
    Import std.collections
    
    Local numbers = [4, 8, 15, 16, 23, 42]
    Print(numbers.Filter(Function(n) => n % 2 == 1))
    Print(numbers.Map(Function(n) => n * 10))
    Print(numbers.Find(Function(n) => n > 20) ?? 0)
    
    Local described = numbers.Map(Function(n): String
        If n < 10 Then Return "small"
        Return "big"
    End Function)
    Print(described)
    Output
    [15, 23]
    [40, 80, 150, 160, 230, 420]
    23
    [small, small, big, big, big, big]

    A block lambda gives the type that the Func expected where it goes says it gives, or what its own : Type says. Here nothing says, since Map works out its result’s type from the lambda, so the lambda writes : String. A lambda written with => gives its expression’s type. Filter, Map, and Find are among the Methods std.collections gives every array.

    Closures

    A lambda can use the variables around it. It captures them, and keeps them alive as long as it lives. A captured variable is shared, not copied: a change to it shows on both sides. Here each counter has its own count, which only it can reach.

    counter.fb
    Import std.core
    
    Function MakeCounter(): Func(): Int
        Local count = 0
        Return Function()
            count++
            Return count
        End Function
    End Function
    
    Local tickets = MakeCounter()
    Local orders = MakeCounter()
    Print(tickets(), tickets(), tickets())
    Print(orders())
    
    Local bonus = 10
    Local addBonus = Function(x: Int) => x + bonus
    bonus = 100
    Print(addBonus(1))
    Output
    1 2 3
    1
    101

    addBonus(1) is 101, since the lambda sees bonus as it is when it runs. A loop’s variable is the exception: each pass gives a lambda its own copy.

    Functions in a Map

    Function values can go in collections. The calculator keeps its operations in a Map from each operation’s name to a Func(Int, Int): Int, a Global so that Run can use it. A Map literal is written in braces, each key, a colon, and its value. Get gives the function for a name, or Null when there’s none.

    calculator.fb
    Import std.core
    Import std.collections
    
    Global operations: Map<String, Func(Int, Int): Int> = {
        "add": Function(a, b) => a + b,
        "subtract": Function(a, b) => a - b,
        "multiply": Function(a, b) => a * b,
        "power": Function(a, b) => a ** b,
    }
    
    Function Run(name: String, a: Int, b: Int): String
        Local operation = operations.Get(name)
        If operation == Null Then Return "{name}: no such operation"
        Return "{name} {a} {b} = {operation(a, b)}"
    End Function
    
    Print(Run("add", 2, 3))
    Print(Run("power", 2, 8))
    Print(Run("divide", 1, 2))
    Output
    add 2 3 = 5
    power 2 8 = 256
    divide: no such operation

    What you learned

    • A function’s name is a value of a Func type, which can be stored, passed, and called.
    • A lambda is a function written inline with Function, as one expression or a block.
    • A lambda’s parameters take their types from the Func it’s passed as.
    • A closure captures the variables around it, shares them, and keeps them alive.
    • Function values can go in arrays and Maps, like any other value.

    Report an issue with this page or with FutureBlitz on GitHub.