部分应用与模式匹配:为什么这些 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 演算术语中,memfib
和 memfib_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 没有说明任何性能,它只是保证程序的结果在逻辑上保持相同。它是做什么的。
我正在尝试了解一些关于 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 演算术语中,memfib
和 memfib_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 没有说明任何性能,它只是保证程序的结果在逻辑上保持相同。它是做什么的。