Back to Home

UFCS in the D programming language

dlang · ufcs · standardization

UFCS in the D programming language

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

Consider a class A and a function that takes a pointer to an instance of it:

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

Ask any C programmer how he would call it. Surely you will hear something like this:

void main() {
	auto b = new A;
	foo(b);
}

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

b.foo();

This opens up great scope for building very interesting, consistent (and even fast) designs.

Caution: at the end is a color image!

And if there are more than 1 arguments? 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 function call is preserved, and from the point of view of machine code, these forms are almost identical ... Almost ... 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 swings 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 pass to the tastiest ... But for a start - 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 the action on the elements provided for 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 the function and describe how its argument will be named. Here, it will be an element of the array. The symbols => mean the beginning of the function body, and the last part, I think, is understandable. After working out the program, we will see what we expect:

[44, 17, 10]

Let's use the UFCS style, since it is in place here, 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 does not have parentheses that indicate its functional nature. The fact is that it was originally declared with the @property attribute. This means that a function that does not take explicit arguments can be called without parentheses, and often looks like a class or structure field. In classes, this allows you to perform actions when accessing this field, and in UFCS style, this helps to emphasize the role of the function as a command. I attributed the same attribute to our function and changed its name in order to convey 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'll 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;
}

Guessed? Right? Ok, I open the cards.

In fact, the standard language library does not use such a thing as an array. A more abstract concept is used, such as Range . In concept D, a range is an abstract type that can store and provide access to some ordered elements. In other words, this is what unites arrays, and the stack, and a simply connected list ... One working scheme for the whole set of containers! Whether it is a class or a structure, it will work everywhere. But here's the problem ... How do I determine if an argument type is a range?

Template contracts come to the rescue! They are designed to check the conditions for incoming types so that users do not shove their fingers into the outlet that they do not need to shove.

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

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

Namely, it checks whether the type belongs to the so-called input ranges (I'm not sure which analogue can be used in Russian). Thus, the types, for their use in the map, must have all the attributes of the input ranges, and this:

- have a function empty, to determine the void of the range
- have a front function to return the top element
- have a popFront function to remove the top element

This is what I missed in class code! But those who are particularly careful will notice that arrays do not have these built-in methods that define them as input ranges. Right, no.

But there are UFCS! Thanks to it, we can use the empty, front and popFront functions (and many others defined in the std.range.primitives module) with respect to arrays and use them on an equal basis 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 by looking at the code kindly provided by 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 will output “Gav!” In the terminal and convert the image, as shown in the figure below.

image

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

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

Let’s say special thanks to him!

Read Next