在使用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函数中抱怨它?
答案 0 :(得分:6)
自v2.050起,D放宽了pure
的定义,以接受所谓的“弱纯”函数。这指的是“do not read or write any global mutable state”的功能。弱函数与函数语言意义上的纯函数不一样。唯一的关系是他们创造了真正的纯函数,a.k.a。“强烈纯粹”的函数可以调用弱函数,就像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;
}
答案 1 :(得分: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;
}
}
您需要复制前一个数组,因为在纯函数中,此引用实际上是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);
}
}
希望有所帮助。
答案 2 :(得分:3)
请查看纯函数的定义:
纯函数是为相同参数产生相同结果的函数。为此,一个纯函数:
- 具有全部不变的参数或可隐式转换为不变的
- 不会读取或写入任何全局可变状态
使用纯函数的一个好处是它们可以安全地并行化。但是,并行执行函数的多个实例并不安全,因为它们可能同时修改类实例,从而导致同步问题。
答案 3 :(得分:0)
我认为您的代码在概念上是正确的。但是,您可能已经发现编译器的语义分析不如您的大脑那样好的情况。
考虑班级来源不可用的情况。在这种情况下,编译器无法告诉addMsg
仅修改成员变量,因此它不允许您从纯函数调用它。
要在您的情况下允许它,它必须具有此类用法的特殊情况处理。添加的每个特殊情况规则都会使语言更复杂(或者,如果没有记录,则会降低其可移植性)
答案 4 :(得分:-1)
只是预感,但此功能并不总是返回相同的结果。
请参阅,它返回对某个对象的引用,虽然该对象将始终包含相同的数据,但是对同一函数的多次调用返回的对象并不相同;也就是说,它们没有相同的内存地址。
当你返回对象的引用时,你实际上是在返回一个内存地址,这个地址在几次调用时会有所不同。
另一种思考方式,返回值的一部分是对象的内存地址,它取决于某些全局状态,如果函数的输出依赖于全局状态,那么它不是纯粹的。天哪,它甚至不必依赖它;只要函数读取全局状态,那么它就不是纯粹的。通过调用“new”,您正在阅读全球状态。