我一直在关注this MSDN video with Brian Beckman,我想更好地理解他所说的话:
每个不懈的程序员都经历了这个阶段的学习 函数可以用表查找替换
现在,我是一名从未上过大学的C#程序员,所以也许在某个地方,我错过了其他人学会理解的东西。
Brian的意思是:
函数可以用表查找替换
是否有这方面的实际例子,是否适用于所有功能?他给出了我能理解的sin函数的例子,但是我如何用更一般的术语来理解它?
答案 0 :(得分:14)
Brian刚刚表明函数也是数据。一般来说,函数只是一个集合到另一个集合的映射:y = f(x)
是集合{x}到集合{y}的映射:f:X->Y
。表格也是映射:[x1, x2, ..., xn] -> [y1, y2, ..., yn]
。
如果函数在有限集上运行(这是编程中的情况)那么它可以用表示该映射的表替换。正如Brian提到的那样,每个命令式程序员都会经历这个理解阶段,只是出于性能原因,可以用表查找替换这些函数。
但这并不意味着所有功能都可以或者应该用表替换。这只意味着你理论上可以为每个功能做到这一点。所以结论是函数是数据,因为表是(当然是在编程的上下文中)。
答案 1 :(得分:5)
Mathematica中有一个可爱的技巧,可以创建一个表作为评估函数调用为重写规则的副作用。考虑一下经典的慢速斐波那契
fib[1] = 1
fib[2] = 1
fib[n_] := fib[n-1] + fib[n-2]
前两行为输入1和2创建表条目。这与说
完全相同fibTable = {};
fibTable[1] = 1;
fibTable[2] = 1;
在JavaScript中。 Mathematica的第三行说“请用模式变量fib[n_]
替换事件的实际参数后,用n_
安装一个重写规则,该规则将替换任何fib[n-1] + fib[n-2]
的出现。”重写器将迭代此过程,并在指数次重写后最终生成fib[n]
的值。这就像我们在JavaScript中使用
function fib(n) {
var result = fibTable[n] || ( fib(n-1) + fib(n-2) );
return result;
}
请注意,它会先检查表,看看我们在进行递归调用之前显式存储的两个值。 Mathematica评估器自动执行此检查,因为规则的呈现顺序很重要 - Mathematica首先检查更具体的规则,然后检查更一般的规则。这就是为什么Mathematica有两个赋值形式=
和:=
:前者用于特定规则,其右侧可以在规则定义时进行评估;后者适用于一般规则,在适用规则时必须评估其右侧。
现在,在Mathematica中,如果我们说
fib[4]
它被重写为
fib[3] + fib[2]
然后到
fib[2] + fib[1] + 1
然后到
1 + 1 + 1
最后到3,在下次重写时不会改变。你可以想象,如果我们说fib[35]
,我们将生成巨大的表达式,填满内存并融化CPU。但诀窍是用以下内容替换最终的重写规则:
fib[n_] := fib[n] = fib[n-1] + fib[n-2]
这表示“请将fib[n_]
的每次出现替换为一个表达式,该表达式将为fib[n]
的值安装新的特定规则,并生成该值。”这个运行得快得多,因为它扩展了规则库 - 值表! - 在运行时。
我们也可以用JavaScript做同样的事情
function fib(n) {
var result = fibTable[n] || ( fib(n-1) + fib(n-2) );
fibTable[n] = result;
return result;
}
这比之前的fib定义要快得多。
这被称为“automemoization”[原文如此 - 不是“记忆”,而是“记忆”,就像为自己创建备忘录一样]。
当然,在现实世界中,您必须管理创建的表的大小。要检查Mathematica中的表,请执行
DownValues[fib]
要在JavaScript中检查它们,请执行
fibTable
在REPL中,例如Node.JS支持的。
答案 2 :(得分:2)
在函数式编程的上下文中,存在引用透明性的概念。对于任何给定参数(或参数集),可以将其引用为透明的函数替换为其值,而不更改程序的行为。
例如,考虑一个带有1个参数 n 的函数 F 。 F是引用透明的,因此 F(n)可以替换为在 n 评估的 F 的值。这对程序没有任何影响。
在C#中,这看起来像是:
public class Square
{
public static int apply(int n)
{
return n * n;
}
public static void Main()
{
//Should print 4
Console.WriteLine(Square.apply(2));
}
}
(我不熟悉C#,来自Java背景,所以如果这个例子在语法上不太正确的话,你将不得不原谅我。)
这里显而易见的是,函数 apply 在使用参数2调用时不能具有除4以外的任何其他值,因为它只返回其参数的平方。函数仅的值取决于其参数 n ;换句话说,参考透明度。
我问你,Console.WriteLine(Square.apply(2))
和Console.WriteLine(4)
之间的区别是什么。答案是,没有任何区别,因为所有意图都是目的。我们可以浏览整个程序,将Square.apply(n)
的所有实例替换为Square.apply(n)
返回的值,结果将完全相同。
那么Brian Beckman对于用表查找替换函数调用的声明是什么意思呢?他指的是引用透明功能的这个属性。如果Square.apply(2)
可以替换为4
而不会影响程序行为,那么为什么不在第一次调用时缓存值,并将其放在由函数参数索引的表中。值Square.apply(n)
的查找表看起来有点像这样:
n: 0 1 2 3 4 5 ...
Square.apply(n): 0 1 4 9 16 25 ...
对于Square.apply(n)
的任何调用,我们只需在表中找到 n 的缓存值,而不是调用该函数,并用此值替换函数调用。很明显,这很可能会使该计划的速度大幅提升。