如果函数的所有输入都是值类型或不可变引用类型,如果它返回值类型或引用类型的新实例,并且没有副作用,则只能记忆函数。时期。
记忆依赖于输入和输出之间的确定性映射。每次调用F(a, b, c)
a、b 和 c 包含相同的值时,都必须返回相同的结果,以便记忆化成为可能。
如果参数是引用类型,那么即使它的值没有改变,多次调用使用它的函数可能会产生不同的结果。一个简单的例子:
public int MyFunction(MyType t)
{
return t.Value;
}
Console.WriteLine(MyFunction(t));
t.Value++;
Console.WriteLine(MyFunction(t));
类似地,如果一个函数依赖于它外部的值,那么使用相同参数多次调用该函数可能会返回不同的结果:
int Value = 0;
public int MyFunction(int input)
{
return Value;
}
Console.WriteLine(MyFunction(1));
Value++;
Console.WriteLine(MyFunction(1));
如果你的记忆函数做的不是返回值或新的引用类型,那么天堂会帮助你:
int Value = 0;
public int MyFunction(int input)
{
Value++;
return input;
}
如果您调用该函数 10 次,Value
将是 10。如果您将其重构为使用记忆化,然后调用它 10 次,Value
将是 1。
你可以开始研究如何记忆状态,这样你就可以伪造一个记忆引用类型的函数。但是你真正记住的是函数工作的一组值。您可以类似地破解具有副作用的记忆函数,使其副作用发生在记忆之前。但这都是自找麻烦。
如果您想将 memoization 实现为采用引用类型的函数,正确的方法是重构仅适用于值类型的函数部分,并 memoize 该函数,例如:
public int MyFunction(MyType t)
{
return t.Value + 1;
}
对此:
public int MyFunction(MyType t)
{
return MyMemoizableFunction(t.Value);
}
private int MyMemoizableFunction(int value)
{
return value + 1;
}
您采取的任何其他实现记忆的方法a)通过更晦涩的方式做同样的事情,或者b)将不起作用。