(input) (output "Adders: 15 25" "Two captures: 47" "No captures: 7" "Through a parameter: 110" "From an array: 1 2 3" "Index evaluated once: 21 i 1" "Through a struct member: 8" "Shadowed in a block: 9 then 15" "Nested: 33" "Named capture: 7" "Captured pointer: 11 then 12" "From a bare fn: 20 42 7") (return 0) ;;; A closure is a code pointer beside its captures, so what this ;;; checks is that the captures survive the lifting -- that two ;;; closures of one shape keep their own environments, that a closure ;;; outlives the call that built it, and that calling one through a ;;; parameter, an array element or a struct member resolves the same ;;; way as through a local. ;;; ;;; A receiver is also an ordinary expression: `[table (++ i)]' has to ;;; evaluate its index exactly once, the way it would for an array of ;;; function pointers. (include stdio.h) (fn double-it ((n int)) int (return (* n 2))) ;;; a bare function is a closure that captures nothing, so it converts ;;; wherever one is expected -- here a declared return type (fn as-closure () (closure ((int)) int) (return double-it)) (fn make-adder ((n int)) (closure ((int)) int) (return (closure ((b int)) int (n) (return (+ n b))))) (fn make-affine ((k int) (b int)) (closure ((int)) int) (return (closure ((x int)) int (k b) (return (+ (* k x) b))))) (fn make-const-7 () (closure () int) (return (closure () int () (return 7)))) ;;; A closure arriving as a parameter: its type is written, so the call ;;; resolves without knowing where it came from (fn apply-twice ((f (closure ((int)) int)) (x int)) int (return (f (f x)))) (struct handlers ((on-tick (closure ((int)) int)))) (pub fn main () int (var add-10 (closure ((int)) int) (make-adder 10)) (var add-20 (closure ((int)) int) (make-adder 20)) (printf "Adders: %d %d\n" (add-10 5) (add-20 5)) (var affine (closure ((int)) int) (make-affine 5 2)) (printf "Two captures: %d\n" (affine 9)) (var seven (closure () int) (make-const-7)) (printf "No captures: %d\n" (seven)) (printf "Through a parameter: %d\n" (apply-twice (make-adder 50) 10)) (var table (¤ (closure ((int)) int) 3)) (var i int 0) (for (= i 0) (< i 3) (++ i) (= (¤ table i) (make-adder i))) (printf "From an array: %d %d %d\n" ((¤ table 0) 1) ((¤ table 1) 1) ((¤ table 2) 1)) ;; the index must be evaluated once, so `i' ends at 1 and not 2 -- ;; read in a separate statement, since reading and bumping it in one ;; printf would be unsequenced whatever the closure did (= i 0) (var once int ([table (++ i)] 20)) (printf "Index evaluated once: %d i %d\n" once i) (var h (struct handlers) #((struct handlers) : .on-tick (make-adder 5))) (printf "Through a struct member: %d\n" ((. h on-tick) 3)) ;; a block opens a scope: the inner `add-10' ends with it, and the ;; call after it is the closure again (do (var add-10 int 9) (printf "Shadowed in a block: %d then " add-10)) (printf "%d\n" (add-10 5)) ;; a closure built inside a closure, capturing that one's capture (var outer (closure ((int)) int) (closure ((x int)) int () (var inner (closure ((int)) int) (make-adder x)) (return (inner 3)))) (printf "Nested: %d\n" (outer 30)) ;; a capture can name what it holds rather than borrow a variable's ;; name, and the expression is evaluated where the closure is written (var pt (struct handlers)) (var sum-once (closure () int) (closure () int ((sum (+ 3 4))) (return sum))) (printf "Named capture: %d\n" (sum-once)) ;; capturing a pointer is how by-reference is spelled; the caller owns ;; what it points at (var counter int 11) (var peek (closure () int) (closure () int ((at (& counter))) (return (* at)))) (printf "Captured pointer: %d then " (peek)) (++ counter) (printf "%d\n" (peek)) ;; ...and in an initializer, as an argument, and as a return (var from-fn (closure ((int)) int) double-it) (printf "From a bare fn: %d %d %d\n" (apply-twice from-fn 5) ((as-closure) 21) (apply-twice (lambda ((n int)) int (return (+ n 1))) 5)) (return 0))