DIP27

From D Wiki
Revision as of 15:30, 27 February 2013 by Deadalnix (talk | contribs)
Jump to: navigation, search
Title: Functions
DIP: 27
Version: 2
Status: Draft
Created: 2013-02-26
Last Modified: 2013-02-27
Author: Amaury SÉCHET
Links: DIP23

Abstract

This DIP is the first of 3. It intends to define what is a function in D. The DIP propose a radically simple approach by deciding that all function are first class function. The DIP also propose some way to mitigate the code breakage involved and allow optional parenthesis when no confusion is possible.

Rationale

D is a very complex languages. It promote several paradigms while being a system language. If this is a force of D, this is also a source of trouble. In fact every feature can combine itself why many other feature, some of them from different paradigms - other way of thinking - and every single special case, cause a combinatorial explosion of special cases when other features are involved.

This problem is experienced by many D users that try to use advanced feature together in a non trivial project. Combined together, special cases lead to surprising behaviors, unnecessary blockade, and sometime expose compiler bugs.

If it is impairing users, it is also impairing polishing of the languages, an many corner cases have to be considered, and cause difficulties to compiler implementers and to create libraries that uses generic techniques.

To solve, or at least reduce that problem, D specification must promote simplicity (as opposed to complexity, not as opposed to difficulty). To achieve this goal, this DIP reduce all function to a single entity : the D first class function. It get rid of function like defined in C or C++, as they are useless and cause unnecessary complexity.

Function definition and uses

Function are still defined with the same syntax :

ReturnType functionName(Parameters) {
    // Function body.
}

However, this is now strictly equivalent to :

enum functionName = ReturnType function(Parameters) {
    // Function body.
}

The function has a function type and can be used as this anywhere a function is expected :

static assert(is(typeof(functionName) == ReturnType function(Parameters)); // Pass

ReturnType function(Parameters) foo = functionName; // OK

void buzz(ReturnType function(Parameters) qux);
buzz(foo); // OK
buzz(bar); // OK

auto a = functionName;
static assert(is(typeof(a) == ReturnType function(Parameters)); // Pass

auto b = functionName();
static assert(is(typeof(b) == ReturnType); // Pass

Note that what is above is simply consequences of the simple rule expressed above.

Transitional measure to mitigate breakage

The unary & operator is defined as a NOOP when used on identifiers that resolve as function declarations. As this behavior clashes with the address of behavior, and is a special case, it must disappear after proper deprecation process is followed.

void foo() {}

static assert(is(typeof(foo) == void function()); // Pass
static assert(is(typeof(&foo) == void function()); // Transitional behavior.
static assert(is(typeof(&foo) == void function()); // Error (foo has no address). Final behavior.
static assert(is(typeof(&foo) == void function()*); // Pass with variant behavior expressed in possible variation section.

Ongoing release process improvement should helps quite a lot in that regard.

Is Expression

The function type specialization used in is expression has to be modified as well. It does match the first class function we defined above, and alias parameters as defined on dlang.org website.

void foo() {}
auto bar = foo;

static assert(is(typeof(foo) == function)); // Pass
static assert(is(typeof(bar) == function)); // Pass
static assert(is(void function() == function)); // Pass
static assert(is(typeof(foo) P == function)); // Pass, P is an empty tuple.
static assert(is(void function(uint) P == function)); // Pass, P is an a tuple of one element : uint.

Optional parentheses

Redundant parenthesis can be a burden for argument-less function class (or single argument function calls used as UFCS). Implicit function call is performed is the following cases :

  • When .identifier lookup fails on the function :
uint foo() {
    return 0;
}

void bar(uint function() a) { writeln("function bar"); }
void bar(uint a) { writeln("uint bar"); }

foo.bar(); // function bar

void buzz(uint a) {}
foo.buzz(); // Implicit call to foo is added as lookup failed on the function.
  • When used in a foreach :
import std.algorithm;

void main() {
    foreach(i; iota(5).map!(n => n * n)) {
        import std.stdio;
        writeln(i); // Prints 1 then 4, 9, 16 and 25.
    }
}

You must note that functions are regular first class function. So optional parentheses apply regardless of how is defined the function :

auto foo = function uint() {
    writeln("foo called !");
    return 42;
}

void bar(uint function() a) { writeln("function bar"); }
void bar(uint a) { writeln("uint bar"); }

foo.bar(); // function bar

void buzz(uint a) {}
foo.buzz(); // foo called !

Possible variation

Function declaration are defined here as first class functions enums. They could have been defined as immutable first class function. They main difference is that they have an address in this case. I played with both using customized SDC and this seems unnecessary. It has the drawback that the compiler must ensure that the function pointer have a storage somewhere and no real benefit.

Conclusion

This DIP have the advantage to keeps most of the convenience that exists in the current situation, while reducing drastically the complexity of the situation. It has been tested with actual code (sadly, SDC don't support many D features, so it is hard to field test on big projects) among several approach.

This DIP is incomplete by itself. 2 more are coming, on properties and delegates, so the topic can be completely covered.

Copyright

This document has been placed in the Public Domain.