Overview
Delegates are a language concept that allows programming in a more functional style by bridging the gap between object methods, component methods, and first-class functions. Virgil allows the programmer to pass first-class function references (called delegates) around the program. Delegates can reference a method of a particular object or a component method with equal ease.
Delegate Type
local f1: function(int): int; local f2: function(int, int): int; local f3: function();
Delegates are values that represent references to component and object methods
within a Virgil program. They are given a special type
function(type, ...): type
that encodes the parameter types and
return type of the delegate. The type only describes the input types and output
type of the function reference, not its name or its associated object type. This
allows a delegate to bridge object and component methods in a uniform manner. The
syntax for the type mirrors the syntax for method declarations: instead of the
keyword method
, we use the keyword function
, followed
by the types of the parameters (without names), then by an optional return type.
Component Delegates
component C { method m() { } } component D { method main() { local f: function() = C.m; } }
Delegates can be bound to a particular method of a particular component. In order to create a delegate that is a reference to a method of a particular component, we can simply refer to the method as if it were a field of the component; i.e. supplying the method name without any parameters. Inside of the component, the method can simply be referred to by its name; outside of the component, as before with fields of components, we must specify the name of the component first. The rule of thumb for delegates on components is simple: a delegate method of a component is accessed like a field of the component.
Object Delegates
class C { method m() { } } component D { method main(o: C) { local f: function() = o.m; } }
Delegates can be bound to a particular method of a particular object as well. In this case, the delegate is essentially two references bound together: a reference to the object and a reference to the object's method. In order to create a delegate that is a reference to a method of a particular object, we can simply refer to the method as if it were a field of the object; i.e. supplying the method name without any parameters. The rule of thumb for delegates on objects is simple: a delegate method of an object is accessed like a field of the object.
Recall that objects support virtual dispatch, where the exact method
implementation for a particular invocation is selected dynamically according to the receiver
object's type. This method resolution step happens
when a delegate value is constructed, rather than when the delegate is invoked. This
means that any attempt to create a delegate from a null
object reference
will generate a NullCheckException
.
Invocation Syntax
expr(args, ...) e.m(args, ...) m(args, ...)
Combined with the syntax for denoting a delegate value,
the syntax for invoking a delegate method generalizes the common syntax for
calling methods in Java and C++. The syntax for invoking a delegate is
simply the delegate expression followed by the arguments enclosed in
parentheses (
... )
. In the code to the right,
the first line is the general syntax; the first expression could be any
valid Virgil expression that evaluates to a delegate value. The
Virgil compiler will typecheck the expression representing the delegate
to determine how many and what type of arguments the delegate accepts,
which it uses to typecheck the arguments.
The general syntax on the first line, coupled with the syntax for denoting
delegate values that you saw in the previous sections means that the second
and third line are simply special cases that just so happen to look
exactly like familiar method call forms in Java and C++!
This syntactic nicety also helps simplify the compiler; all method invocations
are treated as delegate invocations internally.
Exceptions
Delegates are references, and have a default value of null
. All
delegate values are dynamically checked against null
at
invocation. Attempting to invoke a null
delegate will generate a
NullCheckException
at runtime.
Go back to the tutorial.