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.
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))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.
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))[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.
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)[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.
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))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.
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))add 2 3 = 5 power 2 8 = 256 divide: no such operation
What you learned
- A function’s name is a value of a
Functype, 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
Funcit’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.
The rules are in the language reference.
Report an issue with this page or with FutureBlitz on GitHub.