D language chips
I am very glad that articles about the D language appear on Habr. But, in my opinion, translations of the help and articles for a little more than for beginners do not give anything in terms of popularizing the language. I think it is better for sophisticated audiences to imagine, albeit more complex, but some interesting things - chips. Most of what can be called D chips are in other languages, but much of D is implemented more effectively and efficiently, to my taste anyway. D has a lot of interesting things to talk about, and I will start in this article with functions, but not quite ordinary ones.
Writing about language chips, especially new enough, is very fraught, first of all, for the reason “Does the author use that term?”, “Is it useful at all?” and "did the author understand this feature correctly?" So do not hit hard, I myself will not be long in this language, I will be glad to point out inaccuracies and dispute my statements in the comments. But I think, in any case, it will be more useful than the description of the next Hello World.
With anonymous functions in D, everything is fine. The implementation in the standard is fully consistent with what is commonly called the words “anonymous functions” or “delegates”. The resemblance to successful implementations from other languages is clearly visible. The principle "just not like everyone else", the D developers do not care and this is good.
What is here? The variable power2 is assigned a delegate defined at the place of assignment. The powerY variable with automatic type detection is assigned a delegate, which uses the local variable Y in the calculation, that is, the delegate in D is also a closure. And mulY is just a closure, not a delegate, which due to this fact is passed to the do_something_with_x function by reference. By the way, the do_something_with_x function has a higher order function. So many buzzwords in one small banal example, cool, right?
The first writefn is commonplace. In the second writefn, we define an anonymous function and immediately call it, this is a very popular move, for example in JavaScript. Well, the last line with writefn is interesting. There, in the function parameter do_something_with_x, an anonymous function is defined, and the type of parameters is not specified. This is normal, because the type of the anonymous function or, if you want a delegate, is clearly prototyped in the definition of the do_something_with_x function.
As I already wrote, everything is simple with delegates; they are represented directly in the language syntax. Now a little different. There is no direct implementation in the feature language syntax in the header; there is an implementation in the standard library, but not the one presented below. The library uses chips that will be given in the last section of the article. And here we will go the other way, by the way of the snake :). As you know, in Python, any function is an object, and any object, if the __call__ method is defined in its class, can be called as a function. The D language provides us with a similar opportunity for objects using the opCall method. If this method is defined in the class, then the class instance acquires the properties of the function. There are methods with which, for example, you can take an index (like: obj [index]) and much more. In the same way, operators are redefined. Generally, This is a topic for a separate large article. I want to say that this was spied on in Python, but I know that this concept is much older. So, partial application:
In the example, there are two ordinary functions, the generalized case of exponentiation and multiplication. And there is a class whose objects, thanks to the special opCall method, can be used as functions whose behavior is specified when creating the object. The class we have obtained with the properties of a higher-order function takes a parameter function that defines the behavior. And also with the properties of partial application, one of the parameters is determined at the time of creation of the function object.
Thus, two function objects are created, one raises the number to the second power, the second multiplies the number by three. Almost everything that can be done with ordinary functions can be done with objects of the same type, as in the example below, pass them to a higher-order function.
And finally, as usual, the funniest thing. Imagine that there is such a task, write functions, the first will apply to all elements of an array of floating point numbers the following formula: “sin (X) + cos (X)”, and the second for an array of integers such: “(X ^^ 3) + X * 2 ". And there’s a slight nuance that formulas will change from release to release. What will the programmer in D answer to this? “Yes, it’s not a question, as many formulas as you like,” and he will write one generalized function.
I could be wrong, but there is no direct analogue, in any case in popular, compiled languages. C macros are the closest, but there it won't look so pretty. Two D chips are involved here at once: templates and mixins that give the pair a very elegant implementation of the generalized function. Templates are fairly ordinary, except that they may not look as intimidating as in C ++. A mixin, although it resembles a macro, is implemented differently. It is not the preprocessor that is responsible for generating the resulting code, but the compiler itself, and therefore the mixin string can be computed at compile time.
In general, D can do a lot at compile time. And mixins paired with templates represent a very powerful tool that is widely used in the standard library (phobos) of the language. Most simple functions are implemented that way. This of course has a side effect, for a beginner in the language, viewing their source code is tantamount to reading a "philkina letter". But later, when the essence of this method becomes clear, there remains only one emotion - admiration.
On this I, perhaps, take my leave. I would be glad if someone continues the topic of chips in D, there are still plenty of them :)
Writing about language chips, especially new enough, is very fraught, first of all, for the reason “Does the author use that term?”, “Is it useful at all?” and "did the author understand this feature correctly?" So do not hit hard, I myself will not be long in this language, I will be glad to point out inaccuracies and dispute my statements in the comments. But I think, in any case, it will be more useful than the description of the next Hello World.
Anonymous functions, they are delegates, closures
With anonymous functions in D, everything is fine. The implementation in the standard is fully consistent with what is commonly called the words “anonymous functions” or “delegates”. The resemblance to successful implementations from other languages is clearly visible. The principle "just not like everyone else", the D developers do not care and this is good.
import std.stdio;
int do_something_with_x(int X, int delegate(int X) func)
{
return func(X);
}
void main() {
int Y = 10;
int delegate(int X) power2 = delegate(int X) { return X * X; };
auto powerY = delegate(int X) { return X ^^ Y; };
int mulY(int X) {
return X * Y;
}
writefln("power 2: %d", power2(4));
writefln("power 3: %d", (int X) { return X * X * X; }(4));
writefln("do_something_with_x; power2: %s", do_something_with_x( 2, power2 ));
writefln("do_something_with_x; powerY: %s", do_something_with_x( 2, powerY ));
writefln("do_something_with_x; muxY: %s", do_something_with_x( 2, &mulY ));
writefln("do_something_with_x; anon: %s", do_something_with_x( 2, (X){ return X*(-1); } ));
}
What is here? The variable power2 is assigned a delegate defined at the place of assignment. The powerY variable with automatic type detection is assigned a delegate, which uses the local variable Y in the calculation, that is, the delegate in D is also a closure. And mulY is just a closure, not a delegate, which due to this fact is passed to the do_something_with_x function by reference. By the way, the do_something_with_x function has a higher order function. So many buzzwords in one small banal example, cool, right?
The first writefn is commonplace. In the second writefn, we define an anonymous function and immediately call it, this is a very popular move, for example in JavaScript. Well, the last line with writefn is interesting. There, in the function parameter do_something_with_x, an anonymous function is defined, and the type of parameters is not specified. This is normal, because the type of the anonymous function or, if you want a delegate, is clearly prototyped in the definition of the do_something_with_x function.
Partial functions
As I already wrote, everything is simple with delegates; they are represented directly in the language syntax. Now a little different. There is no direct implementation in the feature language syntax in the header; there is an implementation in the standard library, but not the one presented below. The library uses chips that will be given in the last section of the article. And here we will go the other way, by the way of the snake :). As you know, in Python, any function is an object, and any object, if the __call__ method is defined in its class, can be called as a function. The D language provides us with a similar opportunity for objects using the opCall method. If this method is defined in the class, then the class instance acquires the properties of the function. There are methods with which, for example, you can take an index (like: obj [index]) and much more. In the same way, operators are redefined. Generally, This is a topic for a separate large article. I want to say that this was spied on in Python, but I know that this concept is much older. So, partial application:
import std.stdio;
int power(int X, int Y) {
return X ^^ Y;
}
int mul(int X, int Y) {
return X * Y;
}
class partial
{
private int Y;
int function(int X, int Y) func;
this(int function(int X, int Y) func, int Y) {
this.func = func;
this.Y = Y;
}
int opCall(int X) {
return func(X, Y);
}
}
int do_partial_with_x(int X, partial func) {
return func(X);
}
void main()
{
auto power2 = new partial(&power, 2);
auto mul3 = new partial(&mul, 3);
writefln("power2: %d", power2(2) );
writefln("mul3: %d", mul3(2) );
writefln("do_partial_with_x: %d", do_partial_with_x(3, power2) );
writefln("do_partial_with_x: %d", do_partial_with_x(3, new partial(&mul, 10)) );
}
In the example, there are two ordinary functions, the generalized case of exponentiation and multiplication. And there is a class whose objects, thanks to the special opCall method, can be used as functions whose behavior is specified when creating the object. The class we have obtained with the properties of a higher-order function takes a parameter function that defines the behavior. And also with the properties of partial application, one of the parameters is determined at the time of creation of the function object.
Thus, two function objects are created, one raises the number to the second power, the second multiplies the number by three. Almost everything that can be done with ordinary functions can be done with objects of the same type, as in the example below, pass them to a higher-order function.
Generalized functions, templates, mixins
And finally, as usual, the funniest thing. Imagine that there is such a task, write functions, the first will apply to all elements of an array of floating point numbers the following formula: “sin (X) + cos (X)”, and the second for an array of integers such: “(X ^^ 3) + X * 2 ". And there’s a slight nuance that formulas will change from release to release. What will the programmer in D answer to this? “Yes, it’s not a question, as many formulas as you like,” and he will write one generalized function.
import std.math;
import std.stdio;
T[] map(T, string Op)(T[] in_array) {
T[] out_array;
foreach(X; in_array) {
X = mixin(Op);
out_array ~= X;
}
return out_array;
}
void main() {
writeln("#1 ", map!(int, "X * 3")([0, 1, 2, 3, 4, 5]));
writeln("#2 ", map!(int, "(X ^^ 3) + X * 2")([0, 1, 2, 3, 4, 5]));
writeln("#3 ", map!(double, "X ^^ 2")([0.0, 0.5, 1.0, 1.5, 2.0, 2.5]));
writeln("#4 ", map!(double, "sin(X) + cos(X)")([0.0, 0.5, 1.0, 1.5, 2.0, 2.5]));
}
I could be wrong, but there is no direct analogue, in any case in popular, compiled languages. C macros are the closest, but there it won't look so pretty. Two D chips are involved here at once: templates and mixins that give the pair a very elegant implementation of the generalized function. Templates are fairly ordinary, except that they may not look as intimidating as in C ++. A mixin, although it resembles a macro, is implemented differently. It is not the preprocessor that is responsible for generating the resulting code, but the compiler itself, and therefore the mixin string can be computed at compile time.
In general, D can do a lot at compile time. And mixins paired with templates represent a very powerful tool that is widely used in the standard library (phobos) of the language. Most simple functions are implemented that way. This of course has a side effect, for a beginner in the language, viewing their source code is tantamount to reading a "philkina letter". But later, when the essence of this method becomes clear, there remains only one emotion - admiration.
On this I, perhaps, take my leave. I would be glad if someone continues the topic of chips in D, there are still plenty of them :)