用表查找替换函数

时间:2012-12-07 15:51:52

标签: functional-programming lookup-tables imperative-programming

我一直在关注this MSDN video with Brian Beckman,我想更好地理解他所说的话:

  

每个不懈的程序员都经历了这个阶段的学习   函数可以用表查找替换

现在,我是一名从未上过大学的C#程序员,所以也许在某个地方,我错过了其他人学会理解的东西。

Brian的意思是:

  

函数可以用表查找替换

是否有这方面的实际例子,是否适用于所有功能?他给出了我能理解的sin函数的例子,但是我如何用更一般的术语来理解它?

3 个答案:

答案 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)

在函数式编程的上下文中,存在引用透明性的概念。对于任何给定参数(或参数集),可以将其引用为透明的函数替换为其值,而不更改程序的行为。

Referential Transparency

例如,考虑一个带有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 的缓存值,而不是调用该函数,并用此值替换函数调用。很明显,这很可能会使该计划的速度大幅提升。