我是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.
答案 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> true
为R
,如果is(typeof(binaryFun!pred(haystack.front, needle)) : bool)
接受true
,pred
为haystack.front
needle
和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
,则void
为typeof(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()
{
}