bcc
bcc copied to clipboard
Investigating Delegates and Closures
I'm currently exploring how we might add support for delegates and closures.
A delegate defines a function signature - a bit like a function pointer. A closure is a bit like an anonymous function.
Here's a basic delegate:
SuperStrict
Framework brl.standardio
Delegate MyDelegate(a:Int)
f2(f1, 5)
Function f1(a:Int)
Print a
End Function
Function f2(d:MyDelegate, a:Int)
d(a)
End Function
When run, it would output 5
.
Here is an example that uses both a delegate and a closure :
SuperStrict
Framework brl.standardio
Delegate IntOperation:Int(i:Int)
Print "2 + 4 = " + CurriedAdd(2)(4)
Function CurriedAdd:IntOperation(a:Int)
Return Closure(b)
Return a + b
End Closure
End Function
When run, it would output 2 + 4 = 6
.
The closure would also have a shorthand form for single statements, so in the example above you could also write
Function CurriedAdd:IntOperation(a:Int)
Return (b) >> Return a + b
End Function
As you can see above, we'd also be introducing some support for being able infer types.
Implementation
All of this functionality can actually be accomplished in BlitzMax already, but of course you would end up spending all your time writing a lot of boilerplate code to do it. The plan would be for bcc to generate all the necessary boilerplate and coerce the original code to use it.
Example 1 translated
A Delegate is basically a single method Interface, so our delegate would translate to
Interface _DMyDelegate
Method callback(a:Int)
End Interface
where callback
takes an Int and returns nothing.
The call to f2()
would result in a mapping of the f1
function pointer to an instance of DMyDelegate.
Type _F1_DMyDelegate Implements _DMyDelegate
Method callback(a:Int)
f1(a)
End Method
End Type
The actual call to f2()
would change to
f2(New _F1DMyDelegate, 5)
where we'd now pass an instance of DMyDelegate into it.
The f2
function itself would change the function pointer like call to d
into
d.callback(a)
so you'd end up with something that looks like :
SuperStrict
Framework brl.standardio
f2(New _f1_DMyDelegate, 5)
Function f1(a:Int)
Print a
End Function
Function f2(d:_DMyDelegate, a:Int)
d.callback(a)
End Function
Interface _DMyDelegate
Method callback(a:Int)
End Interface
Type _f1_DMyDelegate Implements _DMyDelegate
Method callback(a:Int)
f1(a)
End Method
End Type
which, if you run it, outputs 5
.
For the closure example, the delegate would translate to the following interface
Interface _IntOperation
Method callback:Int(i:Int)
End Interface
The implementation for the closure also needs to store the a
variable, so we end up with the following
Type _1_IntOperation Implements _IntOperation
Field a:Int
Method New(a:Int)
Self.a = a
End Method
Method callback:Int(b:Int)
Return a + b
End Method
End Type
You can see here that we need to store the "environment" required to access any external variables that were referred to in the closure.
In this case a
is the external variable.
The closure itself is replaced by a call to the instance of the delegate which now contains it
Function CurriedAdd:_IntOperation(a:Int)
Return New _1_IntOperation(a)
End Function
And finally, the function pointer-like call is modified accordingly
Print "2 + 4 = " + CurriedAdd(2).callback(4)
This results in the following code
SuperStrict
Framework brl.standardio
Print "2 + 4 = " + CurriedAdd(2).callback(4)
Function CurriedAdd:_IntOperation(a:Int)
Return New _1_IntOperation(a)
End Function
Interface _IntOperation
Method callback:Int(i:Int)
End Interface
Type _1_IntOperation Implements _IntOperation
Field a:Int
Method New(a:Int)
Self.a = a
End Method
Method callback:Int(b:Int)
Return a + b
End Method
End Type
which, if you run it, produces the output 2 + 4 = 6
.