【问题标题】:How to use pure in D 2.0如何在 D 2.0 中使用 pure
【发布时间】:2009-06-17 18:31:54
【问题描述】:

在玩 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 函数中没有报错?

【问题讨论】:

  • 我一直在尝试清理pure 标签,因为它有时指的是纯虚函数,有时指的是pure,有时指的是pure - 等等。但我对d2 一无所知。您能否确认我的标签编辑是否合适? purely-functional 可以解决这个问题吗?我创建了pure-function,所以如果purely-functional 有效,我认为使用现有标签会更好。

标签: d pure-function


【解决方案1】:

从 v2.050 开始,D 放宽了 pure 的定义,也接受了所谓的“弱纯”函数。这是指“do not read or write any global mutable state”的函数。弱纯函数与函数式语言意义上的纯函数不同。唯一的关系是它们使真正的纯函数,也就是“强纯”函数能够调用弱函数,就像 OP 的例子一样。

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

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

当然,现在您可以使用(强烈)pure 函数 run2 而无需修改。

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

【讨论】:

  • 非常酷。有趣的是,没有好的方法来处理堆栈溢出中过时的答案。
  • 等等,弱纯函数可以修改类实例变量吗?在这种情况下,msg 是否超出了“addMsg”函数的范围,因此无法修改?如果这是可能的,那么在另一个函数中编写纯函数或委托来修改该函数的状态有什么问题?
  • @Andrew:没有超出范围。成员函数中有一个隐含的this 参数,msg 可以从this 访问。
【解决方案2】:

其他人已经指出 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);
    }
}

希望对您有所帮助。

【讨论】:

  • 我想这可能是最好的了。我只是希望我们可以避免产生所有的垃圾和时间花在复制上。
  • 你这么说很有趣:大多数函数式语言产生大量垃圾的原因完全相同。这就是为什么他们的垃圾收集器往往如此高效。这不是一件容易解决的事情。如果需要变异,请不要使用 pure。
  • 顺便说一句,这不再是真的。
【解决方案3】:

请查看纯函数的定义:

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

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

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

【讨论】:

  • “他们可以同时修改类实例” .. 什么??怎么样?
  • 自动 tp = 新 TestPure; void threadFunc() { tp.addMsg("Hello world"!); } 新线程(&threadFunc);新线程(&threadFunc);无法保证执行 threadFunc 的两个线程不会同时尝试写入 tp 实例。因此,addMsg 不能是纯的。
  • 呃...上面的评论除了没有拼凑在一起:dump.thecybershadow.net/ca761137c80da1cee3f2657b215f758d/…
  • 我同意 TestPure addMsg 通常不能是纯的,因为它改变了它的状态。我只是希望编译器能够以某种方式弄清楚 run2 实际上是纯的,因为它无法更改任何其他线程可见的数据,因为它是 TestPure 对象的创建者。是否有一些其他限定符可以添加到 addMsg 函数中来帮助这里的编译器?
  • 有问题的纯函数是run2而不是addMsg。鉴于它在本地构造的对象上按顺序调用 addMsg,这应该没问题(从竞争条件和纯粹的角度来看)。
【解决方案4】:

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

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

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

【讨论】:

    【解决方案5】:

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

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

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

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

    【讨论】:

    • 您的逻辑意味着纯函数应该只能返回适合处理器寄存器的值类型...
    • 以下确实编译并显示每次调用返回不同的对象。纯 int* test_pure2() { int* p = new int; p = 42;返回 p; } int i1 = test_pure2(); int* i2 = test_pure2(); writeln(i1, i2);
    • 纯函数将返回相同值的不同实例。很明显,两个实例的指针不能相同。我认为这里的意思很清楚 - 你不应该使用结果的地址(否则 - 请参阅我上面的评论)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-07
    • 1970-01-01
    • 2020-01-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多