📜 ⬆️ ⬇️

UFCS in the programming language D

Surely you have already seen some posts about D. Templates, pseudo-members, streams ... Today I will tell you about a feature of the language like UFCS, or Universal Function Call Syntax. Let's start with the simple.

Consider a class A and a function that takes a pointer to its instance:

class A { int a; } void foo(A a) {} 

Ask any C programmer how he would call her. You will probably hear something like this:
')
 void main() { auto b = new A; foo(b); } 

The point is that in D the first argument can act as a basic identifier, whose method is this function, i.e. function can be called as follows:

 b.foo(); 

This opens up a lot of room for building very interesting, consistent (and even fast) structures.

Caution: at the end of the color image!

And if arguments are more than 1? 2?

 class A { int a; } void foo(A a, int bar, string text) {} void main() { auto b = new A; int var = 42; b.foo(var, "This police box inside than outside"); // ! } 

It should be noted that the usual form of calling a function is preserved, and from the point of view of machine code, these forms are almost identical ... Practically ... But this is a completely different story.

Further more! We can parameterize the function:

 void foo(T)(T a, string text = "Second argument\n") { writeln(T.stringof); writeln(a); writeln(text); } void main() { int var = 42; var.foo!(typeof(var))(); //int //42 //! var.foo("For this view this string is first argument!");//     } 

With a simple built-in type, it rolls easily. What about arrays? Of course! After all, the code is the same for both cases!

 void foo(T)(T[] a) { writeln(T.stringof); writeln(a); writeln(a[0]); } void main() { int[] vars = [42, 15, 8]; vars.foo(); //int[] //[42, 15, 8] //42 } 

Now we come to the most delicious ... But first, a small digression.

The standard language library has a huge number of functions that work with arrays. In our case, consider the map function. This function takes as a parameter a certain function and data array, performs an action on the elements provided by this function and forms a new array of new data. It lies in the std.algorithm module.

 int foo(T)(T a) { return a + 2; } void main() { int[] vars = [42, 15, 8]; auto output = map!((a) => a.foo())(vars); writeln(output); } 


Here we see our map function. In brackets after the sign! We describe a functional literal that will be executed on all elements. In our case, this is (a) => a.foo (). The first brackets symbolically represent the “declaration” of a function and describe how its argument will be named. Here, it will be an element of the array. The symbols => signify the beginning of the function body, and the last part I, I think, is understandable. After working out the program, we will see what we expect:

 [44, 17, 10] 

Let's use the UFCS style, so how is it here to place, and there is no third-party use of intermediate results of the functional chain.

 @property int addTwo(T)(T a) { return a + 2; } void main() { int[] vars = [42, 15, 8]; vars .map!((a) => a.addTwo) .writeln; // ! } 

You may notice that the writeln function has no brackets that indicate its functional nature. The fact is that initially it was declared with the @property attribute. This means that a function that takes no explicit arguments can be called without parentheses, and often looks like a field of a class or structure. In classes, this allows you to perform actions when accessing this field, and in the UFCS style, this helps emphasize the role of the function as a command. I attributed the same attribute to our function and changed its name in order to transfer its command nature.

Now take a look at this piece of code, and without looking further try to answer what is wrong with this parameterized class Container? As a hint, I will give you a piece of his ad:

 class Container(T) { this() { vars = [42, 15, 8]; } //…. T[] vars; } @property int addTwo(T)(T a) { return a + 2; } void main() { auto container = new Container!(int); //       ,                container .map!((a) => a.addTwo) .writeln; } 

Guess? Right? Ok, open the cards.

In fact, the standard language library does not use such a concept as an array. A more abstract concept is used, such as Range . In the concept of D, a range is a kind of abstract type capable of storing and providing access to some ordered elements. In other words, this is what unites both arrays, and stack, and a singly-connected list ... One work scheme for the entire set of containers! Whether it is a class, or a structure, it will work everywhere. But here's the problem ... How do you determine that the type of argument is a range?

Template contracts come to the rescue! They are designed to check the conditions on the incoming types, so that users do not shove fingers into an outlet that you do not need to shove.

Thus, the map function has its own template contract, which determines whether the type of the argument is a range.

auto map (Range) (Range r)
if (isInputRange! (Unqual! Range));

Namely, the type is checked for the so-called input ranges (I'm not sure which analogue can be used in Russian). Thus, types, for their use in a map, must have all the attributes of input ranges, and these are:

- have the function empty, to determine the emptiness of the range
- have the front function to return the top item
- have the popFront function to remove the top item

That is what I missed in the class code! But especially attentive ones will notice that arrays do not have these built-in methods that define them as input ranges. True, no.

But there is a UFCS! Thanks to him, we can use the functions empty, front and popFront (and many others, defined in the module std.range.primitives) with respect to arrays and use them along with other containers!

It should be said that it is useful for functions that will be called through UFCS to return a result for later use by other functions. This can be seen, if you look at the code courtesy of the user aquaratixc:

 @property auto sayGav(Range)(Range c) { writeln("Gav!"); return c; } void main() { auto img = load(`Lenna.png`); img .takeArea(0, 0, 256, 256) .map!(a => toNegative(a, Color4f(1.0f,1.0f,1.0f)).front) .array .toSurface(256, 256) .drawPoint(Color4f(1.0f,1.0f,1.0f), 125, 125) .createImage(256, 256) .sayGav .savePNG("testing.png"); } 

As a result of the work, the program displays “Gav!” In the terminal and converts the image, as shown in the figure below.

image

PS Habravchanin vintage noticed that the line of the form
  auto output = map!((a) => a.foo())(vars); 

can be written like this:
  auto output = map!q{a.foo}(vars); 

We tell him for this special thanks!

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


All Articles