Smalltalk has lexically scoped closures, which are enclosed in brackets and evaluated by sending one of the value messages. Optionally, formal parameters may be specified preceding a vertical bar at the start of the closure:
"Smalltalk" "Rough ML equivalent" [ 3 ]. "fn () => 3;" [ 3 ] value. "(fn () => 3)();" [ :x :y | x + y ]. "fn (x, y) => x + y;" a := [ :x :y | x + y ]. "val a = fn (x, y) => x + y;" a value: 1 value: 2. "a(1, 2)"
Closures are lexically scoped, but they may have arbitrary side effects, including the effect of changing bindings in enclosing environments:
"Executing this code..." "Yields this value for i" i := 5. "5" [ i := 7 ] value. "7" [ :i | i := 9 ] value: 2. "2, then 9 in local scope; 7 in outer scope"
Unlike ML, which has both closures and "special forms" like if/then/else, Smalltalk uses closures to implement control structures:
Transcript open. "Open a Transcript window" 5 timesRepeat: [ Transcript show: 'hi'; cr. ]. x = 0 ifTrue: [ Transcript show: 'Cannot divide by zero' ] ifFalse: [ Transcript show: (1.0 / x) asString. ]. i := 0. [ i < 10 ] whileTrue: [ i := i + 1. ].
These structures work because closures are not evaluated until sent one of the value messages. Delayed evaluation is a crucial property of closures!
Closures with many arguments are evaluated using up to 4 value: keywords:
seal := [ :a :b :c :d | a + b * c + d ]. seal value: 1 value: 2 value: 3 value: 4.
For argument lists longer than that, or if you just don't feel like typing value: too many times, you can use the valueWithArguments: message, which takes an array:
walrus := [ :a :b :c :d :e | a + b * c + d * e ]. walrus valueWithArguments: #( 10 20 30 40 50 ). "Note #() syntax"