6

我是 D 的新手,我想知道是否可以方便地进行编译时检查的鸭子类型。

例如,我想定义一组方法,并要求为传递给函数的类型定义这些方法。它与 D 中的稍有不同,interface因为我不必在任何地方声明“类型 X 实现接口 Y”——这些方法只会被发现,否则编译会失败。此外,最好允许这种情况发生在任何类型上,而不仅仅是结构和类。我能找到的唯一资源是这个电子邮件线程,这表明以下方法将是一个不错的方法:

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

...并建议您可以将其转换为库调用 Implements 以便以下操作成为可能:

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)) { ... }

是否可以对未在类或结构中定义的函数执行此操作?还有其他/新的方法吗?有没有做过类似的事情?

显然,这组约束类似于 Go 的类型系统。我并不想引发任何激烈的战争——我只是以 Go 也适用的方式使用 D。

4

2 回答 2

8

这实际上是 D 中非常常见的事情。这就是范围的工作方式。例如,最基本的范围类型 - 输入范围 - 必须具有 3 个功能:

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

然后模板化函数std.range.isInputRange用于检查类型是否为有效范围。例如,最基本的重载std.algorithm.find看起来像

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

isInputRange!Ris trueifR是一个有效的输入范围,并且is(typeof(binaryFun!pred(haystack.front, needle)) : bool)is trueifpred接受haystack.frontneedle返回一个可隐式转换为的类型bool。所以,这个重载完全基于静态鸭子类型。

至于isInputRange它本身,它看起来像

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
    }));
}

它是一个同名模板,所以当它被使用时,它会被替换为带有它的名字的符号,在这种情况下它是一个类型的枚举bool。那bool就是true如果表达式的类型是 non- void。如果表达式无效,则typeof(x)结果;void否则,它是表达式的类型xis(y)结果是trueify是非. void因此,如果表达式中的代码编译,isInputRange则最终将是,否则。truetypeoffalse

中的表达式isInputRange验证您可以声明一个类型为 的变量R,该变量具有一个可在条件中使用的R命名成员(可以是函数、变量或其他) ,具有一个不带参数的命名函数,并且具有返回值的成员。这是输入范围的预期API,如果遵循该 API,则其中的表达式将编译,因此将适用于该类型。否则,它将是。emptyRpopFrontRfronttypeofRisInputRangetruefalse

D 的标准库有很多这样的同名模板(通常称为特征),并在其模板约束中大量使用它们。std.traits特别是其中有不少。因此,如果您想要更多关于如何编写此类特征的示例,您可以查看那里(尽管其中一些相当复杂)。这些特征的内部结构并不总是特别漂亮,但它们确实很好地封装了鸭子类型测试,因此模板约束更加清晰和易于理解(如果直接将此类测试插入其中,它们会变得更加丑陋)。

因此,这是在 D 中进行静态鸭子类型的常规方法。确实需要一些练习才能弄清楚如何将它们写好,但这是标准的方法,并且有效。有人建议尝试提出与您的Implements!(S, Interface)建议类似的东西,但目前还没有真正实现,而且这种方法实际上不太灵活,因此不适合许多特征(尽管它当然可以与基本的一起工作)。无论如何,我在这里描述的方法是目前的标准方法。

另外,如果您对范围不太了解,我建议您阅读内容。

于 2013-05-16T06:13:25.303 回答
2

实现!(S,接口)是可能的,但没有得到足够的关注来进入标准库或获得更好的语言支持。可能如果我不是唯一一个告诉它是鸭子打字的方法的人,我们将有机会拥有它:)

要修补的概念实现证明:

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()
{
}
于 2013-05-16T10:11:15.690 回答