我刚刚在2天前创建了Haskell,所以我还不确定如何优化我的代码。
作为练习,我重写了foldl
和foldr
(我将在此处foldl
,但foldr
是相同的,将last
替换为{{ 1}}和head
与init
)。
代码是:
tail
我唯一担心的是我怀疑Haskell不能在这里应用尾调用优化,因为递归调用不是在函数结束时进行的。
如何优化尾调用? Haskell的内置实现module Main where
myFoldl :: ( a -> ( b -> a ) ) -> a -> ( [b] -> a )
myFoldl func = ( \x -> (\theList
-> if (length theList == 0) then
x
else
myFoldl func (func x (last theList) ) (init theList)
) )
是否与我的实现不同?
答案 0 :(得分:26)
您在代码示例中使用括号并强调尾部递归表明您将从Lisp或Scheme转到Haskell。如果你是从像Scheme这样急切的语言来到Haskell,请注意:尾部调用几乎不像Haskell那样可以预测性能,因为它们是一种急切的语言。您可以使用由于懒惰而在线性空间中执行的尾递归函数,和您可以使用由于懒惰而在常量空间中执行的非尾递归函数。 (已经困惑了?)
您的定义中的第一个缺陷是使用length theList == 0
。这会强制评估列表的整个脊柱,并且是O(n)时间。最好使用模式匹配,就像Haskell中这个天真的foldl
定义一样:
foldl :: (b -> a -> b) -> b -> [a] -> b
foldl f z [] = z
foldl f z (x:xs) = foldl f (f z x) xs
然而,这在Haskell中表现得非常糟糕,因为在f z x
的调用者要求结果之前我们实际上并不计算foldl
部分;所以这个foldl
在每个列表元素的内存中累积了未评估的thunk,并且没有从尾递归中获益。事实上,尽管是尾递归,但这个天真的foldl
长列表可能会导致堆栈溢出! (Data.List
module有一个foldl'
函数,没有这个问题。)
与此相反,许多Haskell非尾递归函数表现得非常好。例如,根据伴随的find
的非尾递归定义,取foldr
的这个定义:
find :: (a -> Boolean) -> [a] -> Maybe a
find pred xs = foldr find' Nothing xs
where find' elem rest = if pred elem then Just elem else rest
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f z [] = z
foldr f z (x:xs) = f x (subfold xs)
where subfold = foldr f z
由于懒惰,这实际上在线性时间和恒定空间中执行。为什么?
rest
的值。我现在要传授的教训是:不要将你渴望的语言中的表现假设引入Haskell。你只有两天了;首先要集中精力理解语言的语法和语义,而不要扭曲自己编写优化版本的函数。你最初会不时遇到foldl
式的堆栈溢出,但你会及时掌握它。
编辑[9/5/2012]:更简单的演示,即懒惰find
在恒定空间中运行,尽管不是尾递归。首先,简化定义:
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f z [] = z
foldr f z (x:xs) = f x (foldr f z xs)
find :: (a -> Bool) -> [a] -> Maybe a
find p xs = let step x rest = if p x then Just x else rest
in foldr step Nothing xs
现在,使用等式推理(即,使用等于等于,基于上面的定义),并以惰性顺序(最外面的第一个)进行评估,让我们计算find (==400) [1..]
:
find (==400) [1..]
-- Definition of `find`:
=> let step x rest = if x == 400 then Just x else rest
in foldr step Nothing [1..]
-- `[x, y, ...]` is the same as `x:[y, ...]`:
=> let step x rest = if x == 400 then Just x else rest
in foldr step Nothing (1:[2..])
-- Using the second equation in the definition of `foldr`:
=> let step x rest = if x == 400 then Just x else rest
in step 1 (foldr step Nothing [2..])
-- Applying `step`:
=> let step x rest = if x == 400 then Just x else rest
in if 1 == 400 then Just 1 else foldr step Nothing [2..]
-- `1 == 400` is `False`
=> let step x rest = if x == 400 then Just x else rest
in if False then Just 1 else foldr step Nothing [2..]
-- `if False then a else b` is the same as `b`
=> let step x rest = if x == 400 then Just x else rest
in foldr step Nothing [2..]
-- Repeat the same reasoning steps as above
=> let step x rest = if x == 400 then Just x else rest
in foldr step Nothing (2:[3..])
=> let step x rest = if x == 400 then Just x else rest
in step 2 (foldr step Nothing [3..])
=> let step x rest = if x == 400 then Just x else rest
in if 2 == 400 then Just 2 else foldr step Nothing [3..]
=> let step x rest = if x == 400 then Just x else rest
in if False then Just 2 else foldr step Nothing [3..]
=> let step x rest = if x == 400 then Just x else rest
in foldr step Nothing [3..]
.
.
.
=> let step x rest = if x == 400 then Just x else rest
in foldr step Nothing [400..]
=> let step x rest = if x == 400 then Just x else rest
in foldr step Nothing (400:[401..])
=> let step x rest = if x == 400 then Just x else rest
in step 400 (foldr step Nothing [401..])
=> let step x rest = if x == 400 then Just x else rest
in if 400 == 400 then Just 400 else foldr step Nothing [401..]
=> let step x rest = if x == 400 then Just x else rest
in if True then Just 400 else foldr step Nothing [401..]
-- `if True then a else b` is the same as `a`
=> let step x rest = if x == 400 then Just x else rest
in Just 400
-- We can eliminate the `let ... in ...` here:
=> Just 400
请注意,当我们继续执行列表时,连续评估步骤中的表达式不会变得越来越复杂或越来越长;步骤 n 中表达式的长度或深度与 n 不成比例,它基本上是固定的。这实际上说明了find (==400) [1..]
如何在恒定空间中懒惰地执行。
答案 1 :(得分:14)
惯用的Haskell看起来与此截然不同,避免使用if-then-else,嵌套的lambdas,括号和解构函数(head,tail)。相反,你会写它像:
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl f z0 xs0 = go z0 xs0
where
go z [] = z
go z (x:xs) = go (f z x) xs
依赖于模式匹配,where子句,尾递归,保护声明。