DIP30

From D Wiki
Revision as of 20:56, 13 March 2013 by Mleise (talk | contribs)
Jump to: navigation, search
Title: Delegates
DIP: 30
Version: 1
Status: Draft
Created: 2013-14-27
Last Modified: 2013-14-27
Author: Amaury SÉCHET
Links: DIP27, DIP28

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 entity 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.