TDPL, p. 167:
只要函数中的可变状态完全是暂时的(即,在堆栈上分配)并且私有的(即,不通过引用传递给可能污染它的函数),则该函数可以被认为是纯函数。
import std.stdio : writeln;
struct M{
int[4] _data;
pure ref int opIndex(size_t i){ return _data[i]; }
}
pure M foo(ref M m){
m[0] = 1234;
return m;
}
void main(){
M m1 = M([7, 7, 7, 7]);
writeln(m1);
foo(m1);
writeln(m1);
}
// output:
// M([7, 7, 7, 7])
// M([1234, 7, 7, 7])
可变状态是暂时的,因为它在堆栈上,对吗?但这不是私人的。那么如何允许foo()
修改m1
呢?
pure
自TDPL发布以来已经扩展了一点,因为TDPL所描述的pure
限制太大,无法在简单的数学函数等之外使用。您可以查看当前定义的在线文档,但它本质上归结为:
-
pure
函数不能访问任何模块级或静态变量,这些变量可以在程序过程中发生变化(它们必须是const
值类型或pure
函数访问的immutable
)。 -
pure
函数不能调用pure
以外的函数 -
pure
函数无法执行I/o。
就是这样。没有其他限制。但是,如果要对pure
函数进行优化,即使在语句中多次使用它,也只调用一次,则需要额外的限制。即:
- 函数的参数必须为
immutable
或隐式转换为immutable
。
理论上,可以扩展到要求函数的参数必须是immutable
或隐式转换为immutable
(因此,具有const
参数的函数可以在给定immutable
参数时进行优化),但目前不是这样。
这样的pure
函数有时被称为"强"pure
,而那些不能优化的函数被称为"弱"pure
。TDPL强烈描述pure
函数。弱pure
函数的添加是为了使pure
更通用。
虽然弱pure
函数可以改变它们的参数,但它们不能改变全局状态,所以当强pure
函数调用它们时(不能改变它们的参数),对于相同的参数,强pure
函数的返回值总是相同的保证仍然有效。从本质上讲,因为弱pure
函数不能改变全局状态,所以它们是调用它们的强pure
函数的私有状态的一部分。因此,它非常符合Andrei在5.11.1.1节中描述的pure
is as pure
Does在TDPL中,除了函数的私有状态已经扩展到允许函数可以改变其私有状态而不改变全局状态。
自TDPL以来添加的关于pure
的另一个主要注意事项是函数属性推断。pure
、nothrow
和@safe
是为模板化函数推断出来的(尽管对于普通函数来说不是)。因此,如果模板函数可以是pure
,那么现在它是 pure
。它的纯度取决于它的实例化对象。因此,可以将pure
与模板化函数一起使用,而以前通常不能这样做,因为如果将其设置为pure
,它将无法与非纯函数一起工作。但是,如果没有设置为pure
,那么就不能将其与 pure
函数一起使用,因此这是pure
的一个主要问题。幸运的是,属性推断现在修复了这个问题。只要模板化函数在实例化时遵循上面列出的规则,那么它就被认为是pure
。
this
引用被认为是函数参数的一部分,由于该函数是弱纯函数,因此可以修改参数。考虑到this
的状态是输入的一部分,该函数仍然满足输入相同输出相同的条件。
考虑这个完全合法的例子,它输出2
:
import std.stdio : writeln;
struct S
{
int foo = 0;
pure void set(size_t i){ foo = i; }
}
void main()
{
S s;
s.set(2);
writeln(s.foo);
}
据我所知,在TDPL发布之后,pure的定义得到了扩展。这本书描述了强纯函数。在那之后,发生了两个发展:添加了弱纯函数,允许改变它们的参数。此外,还为模板函数添加了纯度推断,这样即使模板函数没有使用pure
装饰,也可以使用模板函数的实例化,只要它是纯的。