Difference between revisions of "DIP27"
(→Fonction definition and uses) |
(Mostly spelling and grammar. Conclusion section needs clarification.) |
||
(5 intermediate revisions by 2 users not shown) | |||
Line 7: | Line 7: | ||
|- | |- | ||
|Version: | |Version: | ||
− | | | + | |2 |
|- | |- | ||
|Status: | |Status: | ||
Line 16: | Line 16: | ||
|- | |- | ||
|Last Modified: | |Last Modified: | ||
− | |2013-02- | + | |2013-02-27 |
|- | |- | ||
|Author: | |Author: | ||
Line 26: | Line 26: | ||
== Abstract == | == Abstract == | ||
− | This DIP is the first of 3. It intends to define what is a function in D. The DIP | + | This DIP is the first of 3. It intends to define what is a function in D. The DIP proposes a radically simple approach by deciding that all functions are first class functions. The DIP also proposes some way to mitigate the code breakage involved and allows optional parenthesis when no confusion is possible. |
== Rationale == | == Rationale == | ||
− | D is a very complex | + | D is a very complex language. It promotes several paradigms while being a system language. If this is a power of D, this is also a source of trouble. In fact, every feature can be combined why many other features, some of them from different paradigms - other ways of thinking - and every single special case causes a combinatorial explosion of special cases when other features are involved. |
− | This problem is experienced by many D users that try to use advanced | + | This problem is experienced by many D users that try to use advanced features together in a non-trivial project. Combined together, special cases lead to surprising behaviors, unnecessary blockade, and sometimes expose compiler bugs. |
− | If it is impairing users, it is also impairing polishing of the | + | If it is impairing users, it is also impairing polishing of the language, and many corner cases have to be considered and cause difficulties to compiler implementers and to the creation of libraries that use 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 | + | To solve, or at least reduce that problem, D's specification must promote simplicity (as opposed to complexity, not as opposed to difficulty). To achieve this goal, this DIP reduces all functions to a single entity : the D first class function. It gets rid of functions as defined in C or C++, as they are useless and cause unnecessary complexity. |
== Function definition and uses == | == Function definition and uses == | ||
Line 51: | Line 51: | ||
enum functionName = ReturnType function(Parameters) { | enum functionName = ReturnType function(Parameters) { | ||
// Function body. | // Function body. | ||
− | } | + | }; |
</syntaxhighlight> | </syntaxhighlight> | ||
Line 75: | Line 75: | ||
== Transitional measure to mitigate breakage == | == Transitional measure to mitigate breakage == | ||
− | The unary & operator is defined as a NOOP when used on identifiers that resolve | + | The unary &-operator is defined as a NOOP when used on identifiers that resolve to function declarations. As this behavior clashes with the address-of behavior and is a special case, it must disappear after a proper deprecation process has been followed. |
<syntaxhighlight lang="d"> | <syntaxhighlight lang="d"> | ||
Line 83: | Line 83: | ||
static assert(is(typeof(&foo) == void function()); // Transitional behavior. | 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()); // Error (foo has no address). Final behavior. | ||
− | static assert(is(typeof(&foo) == void function()*); // Pass with | + | static assert(is(typeof(&foo) == void function()*); // Pass with alternate behavior expressed in the "Possible variation" section. |
</syntaxhighlight> | </syntaxhighlight> | ||
− | Ongoing release process | + | Ongoing release process improvements should help quite a lot in that regard. |
+ | |||
+ | == Is Expression == | ||
+ | |||
+ | The function type specialization used in the is-expression has to be modified as well. It must match the first class function we defined above, and alias parameters as defined on the [http://dlang.org/expression.html#IsExpression dlang.org website]. | ||
+ | <syntaxhighlight lang="d"> | ||
+ | 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 a tuple of one element : uint. | ||
+ | </syntaxhighlight> | ||
== Optional parentheses == | == Optional parentheses == | ||
− | Redundant | + | Redundant parentheses can be a burden for the argument-less function class (or single argument function calls used in UFCS). An implicit function call is performed in the following cases : |
− | + | * When .identifier lookup fails on the function : | |
<syntaxhighlight lang="d"> | <syntaxhighlight lang="d"> | ||
uint foo() { | uint foo() { | ||
Line 104: | Line 118: | ||
void buzz(uint a) {} | void buzz(uint a) {} | ||
− | foo.buzz(); // | + | foo.buzz(); // An implicit call to foo is added as lookup failed on the function. |
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | * When used in a foreach : | |
<syntaxhighlight lang="d"> | <syntaxhighlight lang="d"> | ||
import std.algorithm; | import std.algorithm; | ||
Line 119: | Line 133: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | Note that functions are regular first class functions. As such, optional parentheses apply regardless of how the function is defined : | |
<syntaxhighlight lang="d"> | <syntaxhighlight lang="d"> | ||
auto foo = function uint() { | auto foo = function uint() { | ||
writeln("foo called !"); | writeln("foo called !"); | ||
+ | return 42; | ||
} | } | ||
Line 136: | Line 151: | ||
== Possible variation == | == Possible variation == | ||
− | Function | + | Function declarations are defined here as first class function enums. They could have been defined as immutable first class functions. They main difference is that they have an address in this case. I played with both using customized versions of SDC and this seems unnecessary. It has the drawback that the compiler must ensure that the function pointers have a storage somewhere and no real benefit. |
== Conclusion == | == Conclusion == | ||
− | This DIP | + | This DIP has the advantage of keeping most of the convenience that exists in the current situation, while drastically reducing the complexity of the situation [requires clarification: language/compiler implementation?]. It has been tested on actual code that employs several of D's paradigms. (Sadly, SDC lacks support for some D features, so it is hard to field test on big projects). |
This DIP is incomplete by itself. 2 more are coming, on properties and delegates, so the topic can be completely covered. | This DIP is incomplete by itself. 2 more are coming, on properties and delegates, so the topic can be completely covered. |
Latest revision as of 22:54, 17 October 2016
Title: | Functions |
---|---|
DIP: | 27 |
Version: | 2 |
Status: | Draft |
Created: | 2013-02-26 |
Last Modified: | 2013-02-27 |
Author: | Amaury SÉCHET |
Links: | DIP23 |
Contents
Abstract
This DIP is the first of 3. It intends to define what is a function in D. The DIP proposes a radically simple approach by deciding that all functions are first class functions. The DIP also proposes some way to mitigate the code breakage involved and allows optional parenthesis when no confusion is possible.
Rationale
D is a very complex language. It promotes several paradigms while being a system language. If this is a power of D, this is also a source of trouble. In fact, every feature can be combined why many other features, some of them from different paradigms - other ways of thinking - and every single special case causes a combinatorial explosion of special cases when other features are involved.
This problem is experienced by many D users that try to use advanced features together in a non-trivial project. Combined together, special cases lead to surprising behaviors, unnecessary blockade, and sometimes expose compiler bugs.
If it is impairing users, it is also impairing polishing of the language, and many corner cases have to be considered and cause difficulties to compiler implementers and to the creation of libraries that use generic techniques.
To solve, or at least reduce that problem, D's specification must promote simplicity (as opposed to complexity, not as opposed to difficulty). To achieve this goal, this DIP reduces all functions to a single entity : the D first class function. It gets rid of functions as 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 to function declarations. As this behavior clashes with the address-of behavior and is a special case, it must disappear after a proper deprecation process has been 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 alternate behavior expressed in the "Possible variation" section.
Ongoing release process improvements should help quite a lot in that regard.
Is Expression
The function type specialization used in the is-expression has to be modified as well. It must match the first class function we defined above, and alias parameters as defined on the 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 a tuple of one element : uint.
Optional parentheses
Redundant parentheses can be a burden for the argument-less function class (or single argument function calls used in UFCS). An implicit function call is performed in 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(); // An 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.
}
}
Note that functions are regular first class functions. As such, optional parentheses apply regardless of how the function is defined :
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 declarations are defined here as first class function enums. They could have been defined as immutable first class functions. They main difference is that they have an address in this case. I played with both using customized versions of SDC and this seems unnecessary. It has the drawback that the compiler must ensure that the function pointers have a storage somewhere and no real benefit.
Conclusion
This DIP has the advantage of keeping most of the convenience that exists in the current situation, while drastically reducing the complexity of the situation [requires clarification: language/compiler implementation?]. It has been tested on actual code that employs several of D's paradigms. (Sadly, SDC lacks support for some D features, so it is hard to field test on big projects).
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.