Winter now has proper runtime support for first class functions, and anonymous functions (lambda expressions) with lexical closures.
A simple example:
def main() float : let f = \(float x) : x*x in f(2.0)
returns 4.0.
In this example an anonymous function (that computes the square of its argument) is created and assigned to f. It is then called with the argument 2.0.
Since functions are 'first class', they can be returned from functions:
def makeFunc(float x) function<float> : \() : x def main() float : let f = makeFunc(2.0) in f()
In this example makeFunc returned a function.
To support lexical scoping, variables are captured:
def makeFunc() function<int, int> : let a = [10, 11, 12, 13]va in \(int x) int : a[x] def main(int x) int : let f = makeFunc() in f(x)
In the example above the anonymous function returned from makeFunc() captures the variable-length array a, which is indexed into when the function is actually called with f(x).
Closures are allocated on the heap by default. If they can be proven not to escape the current function (which is currently computed with a simple type-based escape analysis), they are allocated on the stack.
The runtime layout for a closure looks like this currently (in LLVM IR):
%anon_func_29_float__closure = type { i64, i64, float (float, %base_captured_var_struct*)*, void (%base_captured_var_struct*)*, %anon_func_29_float__captured_var_struct }
It has 5 fields: a refcount, some flags (stack or heap allocated), the function pointer, a destructor pointer for cleaning up the captured vars, and a structure containing the captured vars.
All this abstraction doesn't have to come at the cost of runtime inefficiency. Take this program:
def main(array<float, 4> a, array<float, 4> b) array<float, 4> : map(\(float x) : x*x, a)It generates the optimised assembly:
vmovups (%rdx), %xmm0 vmulps %xmm0, %xmm0, %xmm0 vmovups %xmm0, (%rcx) movq %rcx, %rax retq