Lambdas and Closures
A lambda is a function without a name. Lambdas are values: store them in variables, pass them to functions, and return them.
Writing a lambda
Section titled “Writing a lambda”The arrow form holds a single expression:
square = do (n) => n * nadd = do (a, b) => a + banswer = do () => 42
println square(5) # 25println add(2, 3) # 5println answer() # 42The block form holds any number of statements and returns the value of the last one. Leave out the arrow and close with end:
classify = do (n) if n > 0 "positive" elsif n < 0 "negative" else "zero" endend
println classify(-3) # negativewith (params) do ... end is an alternative spelling of the block form:
multiply = with (a, b) do a * bend
println multiply(6, 7) # 42Lambda parameters support everything function parameters do: defaults, type hints, and a variadic * parameter.
greet = do (name = "you") => "Hi, ${name}"total = with (*nums) do nums.sum() end
println greet() # Hi, youprintln total(1, 2, 3) # 6println total(*[4, 5]) # 9A lambda can be called right where it’s written:
println (do (x, y) => x * y)(6, 7) # 42typeof reports both lambdas and named functions as lambda.
Passing lambdas
Section titled “Passing lambdas”Higher-order list methods take a lambda:
words = ["kiwi", "banana", "fig"]
println words.filter(do (w) => w.size() > 3) # ["kiwi", "banana"]println words.map(do (w) => w.uppercase()) # ["KIWI", "BANANA", "FIG"]println words.sort(do (a, b) => a.size() < b.size()) # ["fig", "kiwi", "banana"]When an arrow lambda is an argument, it may end with an optional end, which some people find easier to scan:
println [1 to 6].filter(do (n) => n % 2 == 0 end) # [2, 4, 6]Named functions can be passed the same way, with no conversion:
fn is_even(n) n % 2 == 0end
println [1 to 6].filter(is_even) # [2, 4, 6]Your own functions can accept and call lambdas:
fn retry(times, action) result = null repeat times as attempt do result = action(attempt) break when result end resultend
println retry(5, do (n) => n >= 3) # trueEvery callable also has a .call(arguments) method that takes the arguments as a list:
double = do (x) => x * 2println double.call([21]) # 42Closures
Section titled “Closures”A lambda can use variables from the place where it was created, and it keeps them alive after that function returns. Changes go both ways: the lambda sees later updates, and its own updates are visible outside.
fn make_counter() count = 0 do () count += 1 count endend
counter = make_counter()counter()counter()println counter() # 3
other = make_counter()println other() # 1Each call to make_counter creates a separate count, so the two counters are independent.
Lambdas that update top-level variables work the same way:
total = 0[1, 2, 3].each(do (n) => total += n)println total # 6Which variables a lambda shares
Section titled “Which variables a lambda shares”A lambda shares a variable it assigns with the code around it only if that code assigned the variable first, before the lambda in the source. A variable the lambda assigns that the code around it hasn’t assigned yet belongs to the lambda, even if the same name is used later:
fn run() record = do () status = "recorded" # run() hasn't assigned status yet: this is the lambda's own end status = "running" record() statusend
println run() # runningReading a variable is different: a lambda can read a variable assigned after it, which is how a lambda calls itself or another lambda defined later.
To give a lambda its own variable even when the code around it already has one of that name, declare it with var:
name = "outer"rename = do () var name = "inner" nameendprintln rename() # innerprintln name # outerThis matters most for lambdas passed to task::spawn, which run on another thread: a variable the task shares with the code that started it is updated by both at once.
Closures in loops
Section titled “Closures in loops”Each iteration of a for loop gives its closures their own copy of the loop variable, so a lambda created in the loop keeps the value from its iteration:
callbacks = []for i in [1 to 3] do callbacks.push(do () => i)endprintln callbacks.map(do (f) => f()) # [1, 2, 3]Other variables assigned in the loop body are shared, as usual: a lambda sees their latest value.
Returning a lambda
Section titled “Returning a lambda”A function can return a lambda as its last expression, in either form:
fn make_adder(n) do (x) => x + nend
add5 = make_adder(5)println add5(10) # 15An arrow lambda may also be closed with its own end on the same line (do (x) => x + n end); an end on a later line closes the enclosing block.
The block form:
fn make_multiplier(n) do (x) x * n endend
println make_multiplier(3)(4) # 12