10

在玩 D 2.0 时,我发现了以下问题:

示例 1:

pure string[] run1()
{
   string[] msg;
   msg ~= "Test";
   msg ~= "this.";
   return msg;
}

这可以按预期编译和工作。

当我尝试将字符串数组包装在一个类中时,我发现我无法让它工作:

class TestPure
{
    string[] msg;
    void addMsg( string s )
    {
       msg ~= s;
    }
};

pure TestPure run2()
{
   TestPure t = new TestPure();
   t.addMsg("Test");
   t.addMsg("this.");
   return t;
}

此代码不会编译,因为 addMsg 函数不纯。我无法使该函数成为纯函数,因为它会更改 TestPure 对象。我错过了什么吗?或者这是一个限制?

以下确实编译:

pure TestPure run3()
{
    TestPure t = new TestPure();
    t.msg ~= "Test";
    t.msg ~= "this.";
    return t;
}

~= 运算符不会被实现为 msg 数组的不纯函数吗?为什么编译器在 run1 函数中没有抱怨呢?

4

5 回答 5

6

从 v2.050 开始,D 也放宽了pure接受所谓的“弱纯”函数的定义。这是指“不读取或写入任何全局可变状态”的函数。弱纯函数函数式语言意义上的纯函数不同。唯一的关系是他们制作了真正的纯函数,也就是“强纯”函数能够调用弱函数,就像 OP 的例子一样。

有了这个,addMsg 可以标记为 (weakly) pure,因为只有局部变量this.msg被改变:

class TestPure
{
    string[] msg;
    pure void addMsg( string s )
    {
       msg ~= s;
    }
};

当然,现在你可以不加修改地使用 (strongly)pure功能run2

pure TestPure run2()
{
   TestPure t = new TestPure();
   t.addMsg("Test");
   t.addMsg("this.");
   return t;
}
于 2011-03-07T21:15:58.390 回答
4

其他人已经指出 addMsg 不是纯的,也不能是纯的,因为它会改变对象的状态。

使其变得纯粹的唯一方法是封装您所做的更改。最简单的方法是通过返回突变,有两种方法可以实现。

首先,你可以这样做:

class TestPure
{
    string[] msg;
    pure TestPure addMsg(string s)
    {
        auto r = new TestPure;
        r.msg = this.msg.dup;
        r.msg ~= s;
        return r;
    }
}

您需要复制前一个数组,因为在纯函数内部,this 引用实际上是 const。请注意,您可以通过分配最终大小的新数组然后复制自己的元素来更好地进行复制。你可以像这样使用这个函数:

pure TestPure run3()
{
    auto t = new TestPure;
    t = t.addMsg("Test");
    t = t.addMsg("this.");
    return t;
}

这样,突变被限制在每个纯函数中,变化通过返回值传递出去。

编写 TestPure 的另一种方法是使成员 const 并在将其传递给构造函数之前执行所有突变:

class TestPure
{
    const(string[]) msg;
    this()
    {
        msg = null;
    }
    this(const(string[]) msg)
    {
        this.msg = msg;
    }
    pure TestPure addMsg(string s)
    {
        return new TestPure(this.msg ~ s);
    }
}

希望有帮助。

于 2009-06-18T04:19:46.793 回答
3

请查看纯函数的定义:

纯函数是对相同参数产生相同结果的函数。为此,一个纯函数:

  • 具有所有不变或隐式可转换为不变的参数
  • 不读取或写入任何全局可变状态

使用纯函数的效果之一是它们可以安全地并行化。但是,并行执行函数的多个实例并不安全,因为它们可能同时修改类实例,从而导致同步问题。

于 2009-06-17T18:36:43.720 回答
0

认为您的代码在概念上是正确的。但是,您可能已经发现编译器的语义分析不如您的大脑的情况。

考虑类的源不可用的情况。在这种情况下,编译器将无法告诉addMsg您只修改成员变量,因此它不允许您从纯函数中调用它。

为了在您的情况下允许它,它必须对这种类型的使用进行特殊情况处理。添加的每个特殊情况规则都会使语言更加复杂(或者,如果未记录,则使其不那么可移植)

于 2009-06-17T22:05:21.957 回答
-1

只是预感,但这个函数并不总是返回相同的结果。

看,它返回对某个对象的引用,虽然该对象将始终包含相同的数据,但多次调用相同函数返回的对象并不相同;也就是说,它们没有相同的内存地址。

当您返回对该对象的引用时,您实际上是在返回一个内存地址,这在多次调用中会有所不同。

另一种思考方式是,返回值的一部分是对象的内存地址,它依赖于一些全局状态,如果一个函数的输出依赖于全局状态,那么它就不是纯的。见鬼,它甚至不必依赖它;只要一个函数读取一个全局状态,那么它就不是纯粹的。通过调用“新”,您正在阅读全局状态。

于 2009-06-17T18:42:30.560 回答