← Code Compare

Closures

A closure is a function that captures variables from the scope where it was created and carries them along after that scope is gone. The shared task here is make_adder(n), which returns a new function that remembers n and adds it to its argument - call make_adder(10) and apply the result to 5 to get 15. Notice how every BEAM language treats functions as first-class values you can build at runtime and return; the only real difference is the syntax for an anonymous function (fun ... end, fn ->, fn(x) {}, lambda, or Lua's function) and how each one is later applied.

Show: ErlangElixirGleamLFELuerl
Erlang
-module(adder).
-export([make_adder/1, demo/0]).

%% Returns an anonymous fun that closes over N.
make_adder(N) ->
    fun(X) -> X + N end.

demo() ->
    Add10 = make_adder(10),
    %% Apply the captured fun to 5.
    Add10(5).
    %% => 15

Erlang builds the closure with fun(X) -> X + N end, which captures the surrounding N; binding it to Add10 lets you apply it like any function with Add10(5).

Elixir
defmodule Adder do
  # Returns an anonymous function that closes over n.
  def make_adder(n) do
    fn x -> x + n end
  end
end

add10 = Adder.make_adder(10)
# Anonymous functions are applied with a dot: add10.(5)
add10.(5)
# => 15

Elixir's fn x -> x + n end captures n from the enclosing function, and because it is an anonymous value it is invoked with the dot-call syntax add10.(5).

Gleam
import gleam/io
import gleam/int

// Returns a typed function value that closes over n.
pub fn make_adder(n: Int) -> fn(Int) -> Int {
  fn(x) { x + n }
}

pub fn main() {
  let add10 = make_adder(10)
  // Applied like a normal function; the type fn(Int) -> Int is checked.
  io.println(int.to_string(add10(5)))
  // prints "15"
}

Gleam types the closure explicitly as fn(Int) -> Int, so the captured n and the returned function are checked at compile time; the result is applied with ordinary call syntax add10(5).

LFE
(defmodule adder
  (export (make-adder 1) (demo 0)))

;; lambda captures n from the surrounding scope.
(defun make-adder (n)
  (lambda (x) (+ x n)))

(defun demo ()
  (let ((add10 (make-adder 10)))
    ;; funcall applies the closure to 5.
    (funcall add10 5)))
  ;; => 15

LFE creates the closure with (lambda (x) (+ x n)), which captures n, and because the result is held in a variable it is applied with funcall rather than a plain call.

Luerl
-- The inner function closes over the upvalue n.
local function make_adder(n)
  return function(x)
    return x + n
  end
end

local add10 = make_adder(10)
-- Closures are called like any other function.
print(add10(5))
-- => 15

Lua closures capture surrounding locals as upvalues, so the inner function(x) keeps n alive after make_adder returns; add10(5) then applies it directly.