bcc icon indicating copy to clipboard operation
bcc copied to clipboard

Investigating Delegates and Closures

Open woollybah opened this issue 6 years ago • 2 comments

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.

woollybah avatar Dec 14 '18 17:12 woollybah