📜 ⬆️ ⬇️

D language chips

I am very pleased that there are articles on the D language on Habré. But, in my opinion, help and article translations for a little more than for beginners do not give anything in terms of popularizing the language. I think it is better for the sophisticated public to represent, 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, for my taste anyway. D has a lot of interesting things to talk about, and in this article I will start with functions, but not quite ordinary ones.

Writing about language chips, especially a fairly new one, is very fraught, first of all, because of “does the term use the author?”, “Is it useful at all?” And “is it really so correct, the author understood this feature?”. So do not hit hard, I myself will not be long in this language, I will be happy to indicate inaccuracies and challenge 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 are delegates, closures


Anonymous functions in D are fine. The implementation in the standard is fully consistent with what is commonly called the words "anonymous functions" or "delegates." The similarity with successful implementations from other languages ​​is well seen. The principle of "just not like everyone else", developers D does not care and that 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 there? The power2 variable is assigned a delegate defined at the place of assignment. The powerY variable with automatic type detection is assigned a delegate that uses the local variable Y in the calculation, that is, the delegate in D, it is also a closure. And mulY is just a closure, not a delegate, which, because of this fact, is passed to the do_something_with_x function by reference. By the way, the function do_something_with_x is a higher order function. So many buzz words in one small banal example, cool, right?
The first writefn is banal. In the second writefn, we have an anonymous function defined and immediately called, this is a very popular move, for example in JavaScript. Well, the last line with writefn is interesting. There, in the parameter of the function do_something_with_x, an anonymous function is defined, and the type of parameters is not specified. This is normal, as 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 (Partial application)


As already mentioned, everything is simple with delegates, they are represented directly in the syntax of the language. Now a little different. There is no direct implementation in the syntax of the language featured in the header; there is an implementation in the standard library, but not such as is presented below. The library involved chips, which will be given in the last section of the article. And here we will go the other way, 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 feature for objects using the opCall method. If this method is defined in a class, then an instance of the class acquires the properties of the function. There are methods with the help of which, for example, you can take an index (like: obj [index]) and many more. Operators are redefined in the same way. In general, this is a topic for a separate large article. I want to say that it was overlooked 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 common functions, the generalized case of exponentiation and multiplication. And there is a class whose objects, thanks to the special method opCall, can be used as functions whose behavior is set when the object is created. The class we got with the properties of a higher order function, takes a parameter the function that determines the behavior. As well as with the properties of partial application, one of the parameters is determined at the time of creating the function object.
Thus, two function objects were created, one raising the number to the second power, the second multiplying the number by three. Practically everything that can be done with ordinary functions can also be done with objects of this type, as further in the example, to transfer them to a higher order function.
')

Generic Functions, Patterns, Mixins


And finally, as usual, the most fun. Imagine that there is such a task, to write functions, the first will apply the following formula to all elements of an array of floating-point numbers: “sin (X) + cos (X)”, and the second for an array of integers is this: "(X ^^ 3) + X * 2 ". And a small nuance at once, that formulas will change from release to release. What does the D programmer answer to this? "Yes, no question, as many formulas as you like," and 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 can be mistaken, but there is no direct analog, at least in popular, compiled languages. Macros C is closest, but it won't look so beautiful there. Two D chips are involved here: templates and mixins, which give a very elegant implementation of a generic function. Templates are quite ordinary, except that they look not so scary as in C ++. A mixin, though reminiscent of a macro, but 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 calculated at compile time.
In general, D can do a lot during compilation. 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 this way. This of course has a side effect, for a newbie in the language, viewing their source code is equivalent to reading a “filing letter”. But later, when the essence of this method becomes clear, only one emotion remains - admiration.

On this I, perhaps, take my leave. I would be glad if someone continues the topic of chips in D, there they are still nemer :)

Source: https://habr.com/ru/post/246623/


All Articles