部分应用与模式匹配:为什么这些 Haskell 函数的行为不同?

Partial application versus pattern matching: why do these Haskell functions behave differently?

我正在尝试了解一些关于 Haskell 函数的内容。

首先,这是一个以典型 "slow" 方式定义的斐波那契函数(即递归,没有记忆,也没有无限列表技巧)

slowfib :: Int -> Integer
slowfib 0 = 0
slowfib 1 = 1
slowfib n = slowfib (n-2) + slowfib (n-1)

接下来,一个规范的记忆版本。 (与 tutorals/books/etc 中的典型示例略有不同,因为我更喜欢 !! 运算符的前缀版本。)

memfib = (!!) (map fib [0..])
  where
    fib 0 = 0
    fib 1 = 1
    fib k = memfib(k-2) + memfib(k-1)

上述解决方案使用了 !! 运算符的部分应用,这是有道理的:我们希望 memfib 最终成为一个接受参数的函数,并且我们在定义它时不包含定义中的参数。

到目前为止一切顺利。现在,我想我可以写一个等效的记忆函数 在定义中包含一个参数,所以我这样做了:

memfib_wparam n = ((!!) (map fib [0..])) n
  where
    fib 0 = 0
    fib 1 = 1
    fib k = memfib_wparam(k-2) + memfib_wparam(k-1)

(在 Lambda 演算术语中,memfibmemfib_wparams 只是彼此的 eta 转换。我认为???)

这行得通,但记忆消失了。事实上,memfib_wparam 的表现甚至比 showfib 更糟糕:不仅速度更慢,而且内存使用量增加了一倍以上。)

*Main> slowfib 30
832040
(1.81 secs, 921,581,768 bytes)
*Main> memfib 30
832040
(0.00 secs, 76,624 bytes)
*Main> memfib_wparam 30
832040
(2.01 secs, 2,498,274,008 bytes)

这是怎么回事?更重要的是,我对 Haskell 函数定义错误的更广泛理解是什么?我假设我在 memfib_wparam 中使用的语法只是我在 memfib 中所做的语法糖,但显然不是。

不同之处在于绑定 fib 函数的时间。

where 绑定定义可以访问外部函数的参数(即参数在 where 内 "in scope")。这意味着 fib 应该可以访问 n,这反过来意味着 fib 被定义 n 被传递之后,这意味着每个 n 都有不同的 fib,这意味着每个 n.

都有不同的 map fib [0..] 调用

如果你想扩展你的 memfib,这将是 "right" 的方法(即不过度扩展 n 的范围):

memfib = \n -> theCache !! n
    where
        theCache = map fib [0..]

        fib 0 = 0 
        fib 1 = 1 
        fib k = memfib(k-2) + memfib(k-1)

如果您要与 lambda 演算进行比较,关键区别在于 eta-reduction/expansion 没有说明任何性能,它只是保证程序的结果在逻辑上保持相同。它是做什么的。