鸭子在D打字

时间:2013-05-16 03:48:04

标签: metaprogramming d duck-typing

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

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

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.

2 个答案:

答案 0 :(得分:7)

这实际上是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!R是有效的输入范围,则{p> trueR,如果is(typeof(binaryFun!pred(haystack.front, needle)) : bool)接受truepredhaystack.front needlebool并返回一个可隐式转换为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,则voidtypeof(x)。如果表达式无效,void会生成x;否则,它是表达式is(y)的类型。如果true不是y,则void会产生isInputRange。因此,如果true表达式中的代码编译,typeof将最终成为false,否则将isInputRange

R中的表达式验证您可以声明R类型的变量,empty具有名为{{1的成员(可以是函数,变量或其他)的成员可以在条件中使用,R具有名为popFront的函数,该函数不带参数,R具有返回值的成员front。这是输入范围的预期API,如果typeof跟在该API之后,R内的表达式将被编译,因此,对于该类型,isInputRange将为true 。否则,它将是false

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

所以,这是在D中进行静态鸭子输入的常规方法。确实需要一些练习来弄清楚如何编写它们,但这是标准的方法,它可以工作。曾经有人建议尝试提出与你的Implements!(S, Interface)建议类似的东西,但实际上还没有真正的东西,这样的方法实际上不那么灵活,使得它不适合于许多特征(虽然它肯定可以用于基本的)。无论如何,我在这里描述的方法目前是标准的方法。

另外,如果您对范围了解不多,我建议您阅读this

答案 1 :(得分: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()
{
}