DIP30
Title: | Delegates |
---|---|
DIP: | 30 |
Version: | 1 |
Status: | Draft |
Created: | 2013-14-27 |
Last Modified: | 2013-14-27 |
Author: | Amaury SÉCHET |
Links: | DIP27, DIP28 |
Contents
Abstract
This DIP is the last of 3. It intend to define delegates in D. Just like DIP27 and DIP28 it intend to be simple and avoid special cases, by redefining delegate in a more general way : an struct composed of a function pointer and a first argument. This allow us to handle method, closure and UFCS as delegate introducing only one entoty in the language.
Rationale
Current delegate definition suffer several problems. It don't ensure transitivity of type qualifiers :
struct Foo {
void delegate() dg;
this(immutable void delegate() dg) immutable {
this.dg = dg;
}
}
void main() {
int a;
auto f = Foo({
a++;
});
f.dg();
import std.stdio;
writeln(a); // Print 1
}
This problem, just as the inability to express many construct as delegate come from the fact the the delegate's hidden argument isn't typed properly.
Fully typed delegates
Delegate's context is assumed to be of type void* . However, it can be specified as a type after the parameter list as follow :
void delegate() // delegate with an implicit parameter of type void*
void delegate() uint // delegate with a parameter of type uint
function and delegate keyword do not bind to the parameter type. It is possible to resolve this using an alias, but it should be rare enough to be a problem.
void delegate() void* function() // a function that return a delegate with a void* as context.
alias fn = void* function();
void delegate() fn // a delegate that uses a function as context.
When the type of the context is void*, the type qualifier can be specified alone :
static assert(is(void delegate() immutable == void delegate() immutable(void*))); // Pass
As the context is an argument, it can be ref. All delegates with ref, pointer, interface or class as context type can implicitly cast to a delegate with void* as context type (granted type qualifier matches).
void delegate() uint* a;
void delegate() b = a; // OK
void delegate() uint c;
void delegate() d = c; // Error
void delegate() immutable(uint)* e;
void delegate() f = e; // Error
void delegate() immutable g = e; // OK
void delegate() ref uint h;
void delegate() i = h; // OK
Method as delegates
Method are delegates. As simple as this.
struct Foo {
void bar() {}
}
Foo foo;
class Qux {
uint buzz() immutable {}
}
Qux qux;
static assert(is(typeof(foo.bar) == void delegate() ref Foo)); // Pass
static assert(is(typeof(qux.buzz == uint delegate() immutable(Qux))); // Pass
static assert(is(typeof(foo.bar) : void delegate())); // Pass
static assert(is(typeof(qux.buzz : uint delegate() immutable)); // Pass
Closures as delegates
Closures are delegates with a pointer on a tuple as context. As D tuples are not expressible in D, no sample code on that one. pure have the same semantic as for function and is not saying anything about the context anymore.
void fun() {
uint a, b;
pure uint foo() {
return a + b;
}
static assert(is(typeof(foo) : uint delegate()); // Pass
static assert(is(typeof(foo) : uint delegate() immutable); // Fail
static assert(is(typeof(foo) : uint delegate() pure); // Pass
immutable uint c, d;
uint bar() immutable {
// return a + b; // Error, context is immutable.
return d + d;
}
static assert(is(typeof(bar) : uint delegate()); // Fail
static assert(is(typeof(bar) : uint delegate() immutable); // Pass
static assert(is(typeof(bar) : uint delegate() pure); // Fail, bar isn't defined as pure (but could have been).
}
UFCS as delegates
expression.funName now create a delegate. If the first parameter is ref, a pointer, an interface or a class, thing just goes simply.
void foo(ref uint a) {}
uint a;
void delegate() dg = a.foo; // OK
static assert(typeof(a.foo) == void delegate() ref uint); // Pass
If the first parameter is a value type, then an accordingly typed delegate is created :
void bar(uint a) {}
uint a;
void delegate() gd = a.bar // Fail
void delegate() uint valueDg = a.bar // OK
static assert(typeof(a.foo) == void delegate() uint); // Pass
The created delegate is an rvalue. Usual copy mechanism apply, this is especially important for struct defining a postblit :
struct Foo {
this(this) {
writeln("postblit !");
}
}
void bar(Foo f) {}
Foo().bar(); // No postblit, only rvalues.
Foo f;
f.bar(); // postblit.
auto dg = f.bar; // postblit !
dg(); // postblit !
No special rule here, the postblit is called when creating an rvalue from an lvalue.
Optional parentheses
Optional are defined as for first class function, whatever theses rules are. No exception.
Conclusion
This DIP propose a unified view of many different languages construct via delegates. It solve issues with the current design while simplifying D overall. Coupled with DIP27, it dramatically simplify the situation of callable object in D, which is now unnecessary complex (and have hole in it).
Copyright
This document has been placed in the Public Domain.