Lambdas, Closures and everything in between · Issue #1048 · ziglang/zig (original) (raw)

I've been thinking about this topic for a few weeks now, and after quite a bit of research I think I have a solution that fits Zig's needs :).

This is building on #229 (and similar issues), since this is talking about implementation specifically and the issue is old I felt it was worth creating a new one.

Step 1: Make all functions anonymous; I.e. you can define a function like; const Foo = (bar: i32) i32 => { ... }; however of course we could support using fn Foo(bar: i32) i32 { ... } as the short form. In this way you can define functions inline, these aren't closure functions and when compiled will be placed outside the scope of course.

// i.e. to sort sort(array, (a: i32, b: i32) bool => { return a < b; });

Step 2: Lambdas; Building onto anonymous functions you can define 'inline' lambda functions which are just a single statement that returns like;

sort(array, ($) => 0<0 < 0<1);

The $ just acts as a wild card effectively matching whatever the types the input requires, if the input is a 'var' type then it makes a function def that is fn X(a: var, b: var) var, perhaps? Or maybe that is just a compile error, I'm not sold either way.

Step 3: Closures; In the case where you actually want a closure you would define it like any other function but indicate type of function input;

var x = someRuntimeValue(); // 'Long form' where(array, (a: i32, x = x) bool => { return a < x }); // And as a lambda (implicit 'var' return) where(array, ($, x = x) => $0 < x)); // The above cases are by value, if you wanted by reference you would just do where(array, ($, x = &x) => $0 < x));

The above is synonymous to the following Zig code if we allow some kind of implicit cast;

const Closure = struct { x: i32, // note: if doing the by value it would be x: *i32 f: fn(i32) bool, }; where(array, Closure { .x = x, .f = (a: i32, closure: &const Closure) bool => { return a < closure.x } });

HOWEVER, this is where the problem occurs, you require this pointer to exist in the definition, and so we need someway to get around this call and it's been suggested in the past that you can pass around some kind of 'closure' type that allows you to call it like a function but is really just this struct, personally this hides information from the coder and I feel goes against Zig's core, and furthermore would you allow the above 'closure' type to be passed into a function with definition (a: i32) bool?

Instead I propose that we can use LLVM Trampolining in quite a few cases to 'excise' a parameter from the call, which would be the closure information and the call would rather become something like;

const Closure = struct { x: i32, // note: if doing the by value it would be x: *i32 // Note: no f }; fn Foo(env: &Closure, a: i32) bool { return a < env.x; }

var x = runtimeValue(); const c = Closure { .x = x }; where(array, @trampoline(Foo, c));

Note: of course in this case I'm using trampoline as if it was an inbuilt, I'm not actually sure if we want it as an inbuilt, but in reality it is more like generating the LLVM code that trampolines the function. A trampoline (said that word too many times now) just basically stores the value on the stack in advance to the call this would be much more efficient then a typical closure as it would avoid the need for a function pointer and would avoid the ugliness of indirection. HOWEVER, there is not much information on how this effects things like 'arrays of closures' which may instead require a different approach. Note: this is how GCC implements nested functions this is a relevant section.

So basically I propose that we approach this in the order I've given, and perhaps implement that builtin before integrating the closure x = x syntax.