A few months ago I discovered a new way to write named functions that can be recursive using a simple trick : using a variable of type (Callable) ? to which we assign a closure holding a reference to this variable.
To understand better I have to explain KC3 variables. They can only be set (bound) once and are read only after that. A variable can have a name and a function in a variable will inherit the latter's name. A variable has a literal value : it is a cast of the ident ?
, e.g. (U8) ?
; (List) ?
; (MyStruct) ?
or even the redundant (Tag) ?
which you don't have to specify so it's equivalent to just ?
.
Once we have a variable, usually you bind it to a frame using =
, like this : my_var = (MyStruct) ?
. The variable is then named my_var and has a data type of MyStruct which is probably a struct, and does not have a value yet. But we can allocate it since we have the type which gives us the size. So the variable is put onto the latest function frame and is unbound.
So to come back to our recursive functions, after adding support for lexical variables and closure environment capture, this allows for recursive function definitions by itself, without lambda calculus magic. The variable is stored in the parent frame but it is referenced (think smart pointers) by our closure frame of captured variables. So two references to the same object. They are reference counted and mutex locked for MP-safety. In this case the variable is not a struct type but a Callable which is the type of all KC3 values that can be named in a call (Fn or Cfn).
Here comes an example :
ikc3> factorial = (Callable) ?
ikc3> factorial <- fn {
ikc3> (0) { 1 }
ikc3> (n) {
ikc3> if (n < 0) do
ikc3> 1
ikc3> else
ikc3> n * factorial(n - 1)
ikc3> end
ikc3> }
ikc3> }
...
ikc3> factorial(5)
120
What happens when we bind a function to a variable ? The variable is on the heap, is reference counted and has a full tag which can hold a Callable. The callable also is reference counted and everything is freed as soon as possible for an economy crash course on memory. In case you missed it, everything is bloated. KC3 just hacks its way to low and high level and mixes perfectly in my opinion.