Duck in D

I am new to D and I was wondering if it is convenient to do compilation with checking with duck text.

For example, I would like to define a set of methods and require that these methods be defined for the type passed to the function. It is slightly different from the interface in D because I would not have to declare that "type X implements the Y interface" anywhere - the methods will simply be found or the compilation will fail. In addition, it would be nice if this happened on any type, and not just on structures and classes. The only resource I could find was this email stream , which suggests that the following approach would be a decent way to do this:

 void process(T)(T s) if( __traits(hasMember, T, "shittyNameThatProbablyGetsRefactored")) // and presumably something to check the signature of that method { writeln("normal processing"); } 

... and invites you to do this in the library call "Implementation" so that the following is possible:

 struct Interface { bool foo(int, float); static void boo(float); ... } static assert (Implements!(S, Interface)); struct S { bool foo(int i, float f) { ... } static void boo(float f) { ... } ... } void process(T)(T s) if (Implements!(T, Interface)) { ... } 

Can this be done for functions that are not defined in the class or structure? Are there other / new ways to do this? Was something like that done?

Obviously, this set of constraints is similar to a system like Go. I am not trying to start any fiery wars. I just use D so that Go works well too.

+6
source share
2 answers

This is actually a very common thing in D. This is how ranges work. For example, the most basic type of range - the input range - should have 3 functions:

 bool empty(); //Whether the range is empty T front(); // Get the first element in the range void popFront(); //pop the first element off of the range 

You can then use the std.range.isInputRange template functions to check if the type is valid. For example, the most basic overload of std.algorithm.find looks like

 R find(alias pred = "a == b", R, E)(R haystack, E needle) if (isInputRange!R && is(typeof(binaryFun!pred(haystack.front, needle)) : bool)) { ... } 

isInputRange!R true if R is a valid input range and is(typeof(binaryFun!pred(haystack.front, needle)) : bool) is true if pred accepts haystack.front and needle and returns a type that is implicitly converted to bool . Thus, this overload is based solely on the static printing of ducks.

As for isInputRange itself, it looks something like

 template isInputRange(R) { enum bool isInputRange = is(typeof( { R r = void; // can define a range object if (r.empty) {} // can test for empty r.popFront(); // can invoke popFront() auto h = r.front; // can get the front of the range })); } 

This is the template of the same name, so when it is used, it is replaced by a symbol with its name, which in this case is an enumeration of type bool . And that bool is true if the type of the expression is not void . typeof(x) results in void if the expression is invalid; otherwise it is a type of expression x . And is(y) leads to true if y not void . Thus, isInputRange will isInputRange out to be true if the code in the typeof expression is compiled, and false otherwise.

The expression in isInputRange verifies that you can declare a variable of type R that R has a member (be it a function, a variable, or something else) with the name empty , which can be used in the condition that R has a function called popFront that does not accepts arguments and that R has a front member that returns a value. This is the expected API from the input range, and the expression inside typeof will compile if R follows this API, and therefore isInputRange will be true for this type. Otherwise, it will be false .

Standard library

D has quite a few such templates of the same name (usually called traits) and uses them heavily in its template restrictions. std.traits , in particular, is quite a lot. So, if you want more examples of how such traits are written, you can look there (although some of them are quite complex). The internals of such traits are not always particularly good, but they encapsulate duck typing tests, so the template constraints are much cleaner and more understandable (they would be much, much uglier if such tests were inserted directly into them).

So, this is the normal approach for static duck printing in D. Usually you need to figure out how to spell correctly, but this is the standard way to do this, and it works. There were people who suggested trying to come up with something similar to your Implements!(S, Interface) proposal, but nothing had happened so far, and such an approach would actually be less flexible, which would make it poorly suitable for many ( although this could be done to work with the basic ones). Despite this, the approach I described here is currently the standard way to implement it.

Also, if you know little about ranges, I would suggest reading this .

+7
source

Implementations (S, Interface) are possible, but have not received enough attention to get into the standard library or get better language support. Probably, if I will not be the only one who says that this is a way to go for duck printing, we will have the opportunity to get it :)

Proof of concept implementation for messing around:

http://dpaste.1azy.net/6d8f2dc4

 import std.traits; bool Implements(T, Interface)() if (is(Interface == interface)) { foreach (method; __traits(allMembers, Interface)) { foreach (compareTo; MemberFunctionsTuple!(Interface, method)) { bool found = false; static if ( !hasMember!(T, method) ) { pragma(msg, T, " has no member ", method); return false; } else { foreach (compareWhat; __traits(getOverloads, T, method)) { if (is(typeof(compareTo) == typeof(compareWhat))) { found = true; break; } } if (!found) { return false; } } } } return true; } interface Test { bool foo(int, double); void boo(); } struct Tested { bool foo(int, double); // void boo(); } pragma(msg, Implements!(Tested, Test)()); void main() { } 
+2
source

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


All Articles