在D语言中使用动态类型,这是一种静态类型语言。

8

我正在为D语言实现一个动态类型库,期间遇到了一个有趣的问题。

目前,我已经成功地创建了一个名为dynamic()的函数,它返回一个对象的动态版本。

例如:

import std.stdio, std.dynamic.core;

class Foo
{
    string bar(string a) { return a ~ "OMG"; }
    int opUnary(string s)() if (s == "-") { return 0; }
}

void main(string[] argv)
{
    Dynamic d = dynamic(new Foo());
    Dynamic result = d.bar("hi");
    writeln(result);  // Uh-oh
}

我遇到的问题是 writeln 试图使用编译时反射来确定如何处理 result

它首先尝试什么?是 isInputRange!(typeof(result))

问题是它返回了true!为什么?因为我必须假设所有它需要的成员都存在,除非我能在运行时证明否定——这时已经太晚了。所以程序尝试在 result 上调用 frontpopFrontempty,从而导致程序崩溃。

我想不出修复这个问题的方法。有人有什么想法吗?

5个回答

2
你正在尝试使两个根本不同的概念相互配合,即模板和动态类型。模板非常依赖静态类型,isInputRange通过检查类型具有哪些属性或方法来工作。在编译时,您的动态类型被视为具有每个属性或方法,因此它被视为符合每个静态鸭子类型接口。 因此,在静态类型环境中使Dynamic起作用,您必须在某些地方提供更多的静态信息。
我能想到的一些解决方案是:
1. 为使用频繁的函数提供自己的动态类型实现。整个问题的原因是您正试图使用假定具有静态类型的通用函数与动态类型。 2. 显式地将动态类型变为char范围,并自行处理底层数据转换为字符串。(如果不存在isInputRange问题,则必须拥有自定义toString方法,否则其结果再次为Dynamic类型)。这可能会使writeln(d)正常工作。 3. 为dynamic提供包装器,使您能够将动态类型传递到各种模板函数中。(这些只会展示一个静态界面并将所有调用转发给Dynamic)。
Dynamic d;
// wrap d to turn it into a compile-time input range (but NOT eg a forward range)
Dynamic d2=dynamic(map!q{a*2}(dynInputRange(d))); 
// profit

4. 在Dynamic中添加一个成员模板,允许静态地禁用某些成员函数名称。

例如:

static assert(!isForwardRange!(typeof(d.without!"save")));

1
你能为isInputRange提供一个重载吗?就像这样(请注意,我还没有查看isInputRange的实现):
template isInputRange(T : Dynamic) {
    enum isInputRange = false;
}

如果这是由你的dynamic.core提供的,我认为应该选择这个重载而不是std库中的那个。

1
没错,但问题在于这需要预先知道每种预防措施。显然,对于最终使用我的库的人来说,这是行不通的... - user541686
不幸的是,这个技巧行不通,std.stdio无法捕捉到特化。 - Lutger

1

使用 std.variant 有什么问题,它实现了所有动态类型所需的功能(以及相当一部分的语法糖)?


1
std.variant 不支持具有任意字段的类型。 - Vladimir Panteleev
OP想要创建一个对象,在编译时obj.anything是有效的(即使在运行时可能无效)。就我所知,std.variant中没有任何允许这样做的方法。 - Vladimir Panteleev

0

对于一般情况,Dynamic必须在编译时接受任何方法查找,正如你所说。假设你可以防止isInputRange谓词评估为true,现在当你尝试从输入范围创建动态对象时,就会生成错误的代码。

我认为这是无法修复的,至少不是以一般方式。在这种特殊情况下,我能想到的最好的解决方案是Dynamic提供自己的toString版本,并且writeln将优先选择它而不是inputRange特化。我相信writeln目前并没有这样做,至少对于结构体来说不是,但很可能应该这样做。

另一个妥协办法是在opDispatch约束中禁止一些方法(例如popFront),而Dynamic将提供opIndex或成员对象来访问这些特殊情况。这可能并不像听起来那样糟糕,因为特殊情况很少,使用它们会导致明显的编译器错误。

我认为挽救Dynamic这种方法解析的最佳方式是修复writeln并接受Dynamic将无法与所有模板代码一起使用。


让writeln更喜欢toString而不是isInputRange的问题在于每个类都从Object继承了一个通用的toString方法,它只会输出类名。因此,如果要更改writeln,则必须区别对待结构体和类。 - tgehr

0

你有研究过 std.variant 吗?

import std.stdio, std.variant;

class Foo {
    string Bar(string a) {
        return a ~ " are Cool!";
    }
}

void main() {
    Variant foo = new Foo();
    Variant result = foo.peek!Foo.Bar("Variants");

    writeln(result); // Variants are Cool!
}

http://www.d-programming-language.org/phobos/std_variant.html


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接