← Code Compare

Functions & Closures

The same small task in every language: define a named function square, write a closure factory make_adder(n) that captures n and returns a new function, then map a list through both — squaring each element, then adding 5. Watch for how a closure captures n (does it need an explicit return? a named lambda keyword? an arrow?), how anonymous functions read ({ ... } topic blocks vs. lambda/fun/|x|/sub {}), and how functions compose over a list (map with a lambda vs. a comprehension).

Show: gujiGoOCamlPerlRakuRustPython
guji
# A named function: the expression form (single expression, `= expr`)
sub square($x: Int): Int = $x * $x

# A closure factory: returns an anonymous sub that captures $n
sub make_adder($n: Int) {
    sub($x: Int) { $x + $n }
}

sub main(): Int {
    $add5 = make_adder(5)
    @nums = [1, 2, 3, 4]
    # `{ $_.square() }` is a topic-block lambda; $_ is its sole argument
    @out = @nums.map({ $_.square() }).map({ $add5($_) })
    print("squared+5: { @out }")
    print("add5(10) = { $add5(10) }")
    0
}

Functions are first-class values, so make_adder simply returns an anonymous sub that closes over the immutable $n. guji has two anonymous forms: the { ... } topic block (one implicit argument $_) used here for map, and the explicit sub($x) { ... }. Thanks to data-first uniform call syntax, $_.square() is exactly square($_), so calls chain left-to-right with .. The expression form sub square(...) = ... is the idiomatic shape for one-liner functions.

Go
package main

import "fmt"

func square(x int) int { return x * x }

// A closure factory: the returned func captures n.
func makeAdder(n int) func(int) int {
	return func(x int) int { return x + n }
}

func main() {
	add5 := makeAdder(5)
	nums := []int{1, 2, 3, 4}
	out := make([]int, len(nums))
	for i, v := range nums {
		out[i] = add5(square(v))
	}
	fmt.Println("squared+5:", out)
	fmt.Println("add5(10) =", add5(10))
}

Go has true closures: the function literal returned by makeAdder captures n by reference. Function types are spelled out explicitly (func(int) int), which is the price of Go's lack of inference for return types. Go has no map/comprehension in the standard library idiom, so the transformation is an explicit for ... range loop building a preallocated slice — the conventional Go style.

OCaml
let square x = x * x

(* A closure factory: the returned function captures n. *)
let make_adder n = fun x -> x + n

let () =
  let add5 = make_adder 5 in
  let nums = [1; 2; 3; 4] in
  let out = List.map (fun x -> add5 (square x)) nums in
  Printf.printf "squared+5: [%s]\n"
    (String.concat "; " (List.map string_of_int out));
  Printf.printf "add5(10) = %d\n" (add5 10)

Every function in OCaml is curried and a first-class value, so make_adder n returning fun x -> x + n is the natural way to build a closure — make_adder could even be written let make_adder n x = x + n and partially applied. Types are fully inferred (square : int -> int) with no annotations. List.map takes the function first and the list second, the opposite order from the data-first languages.

Perl
use strict;
use warnings;

sub square { my ($x) = @_; $x * $x }

# A closure factory: the returned sub captures $n.
sub make_adder {
    my ($n) = @_;
    return sub { my ($x) = @_; $x + $n };
}

my $add5 = make_adder(5);
my @nums = (1, 2, 3, 4);
my @out  = map { $add5->(square($_)) } @nums;
print "squared+5: [", join(", ", @out), "]\n";
print "add5(10) = ", $add5->(10), "\n";

An anonymous sub { ... } closes over the lexical my $n, which is exactly how Perl closures work. Arguments arrive in @_ and are unpacked with my ($x) = @_; a code reference is invoked with the ->() arrow. Perl's map { ... } @list runs its block with each element bound to the topic variable $_ — the same topic idea guji spells $_ inside { ... }.

Raku
sub square(Int $x --> Int) { $x * $x }

# A closure factory: the returned block captures $n.
sub make-adder(Int $n) {
    -> Int $x { $x + $n }
}

my &add5 = make-adder(5);
my @nums = 1, 2, 3, 4;
my @out  = @nums.map({ add5 square $_ });
say "squared+5: @out[]";
say "add5(10) = { add5(10) }";

Raku makes the closure a pointy block -> Int $x { ... }, stored in a &-sigilled variable so it calls like a sub. The .map({ ... }) block uses the implicit topic $_ — the construct guji borrowed for its topic-block lambdas. Sigils and the optional gradual types (Int, --> Int) make Raku the most visible ancestor of guji's $/@/% and annotation style.

Rust
fn square(x: i32) -> i32 {
    x * x
}

// A closure factory: `move` captures n into the returned closure.
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
    move |x| x + n
}

fn main() {
    let add5 = make_adder(5);
    let nums = [1, 2, 3, 4];
    let out: Vec<i32> = nums.iter().map(|&x| add5(square(x))).collect();
    println!("squared+5: {:?}", out);
    println!("add5(10) = {}", add5(10));
}

Rust closures are |x| ...; returning one needs impl Fn(i32) -> i32 and a move so the closure owns the captured n rather than borrowing it. Transformation is lazy and iterator-based: nums.iter().map(...).collect() only runs when collected into a Vec. Like guji and Perl, the closure is passed to map data-first on the iterator, but the explicit Fn trait bound reflects Rust's no-GC ownership model.

Python
def square(x):
    return x * x

def make_adder(n):
    return lambda x: x + n  # closure capturing n

add5 = make_adder(5)
nums = [1, 2, 3, 4]
out = [add5(square(x)) for x in nums]
print(f"squared+5: {out}")
print(f"add5(10) = {add5(10)}")

Python closures are everyday: make_adder returns a lambda that captures the enclosing n. lambda is limited to a single expression, so anything larger becomes a nested def. Rather than map, the idiomatic transformation is a list comprehension [add5(square(x)) for x in nums], which most Python programmers find clearer than map(...).