[英]Haskell Length function implementation
我正在学习 Haskell 编程,并试图了解列表的工作原理,因此我尝试编写两个可能的length
函数:
myLength :: [a] -> Integer
myLength = foldr (\x -> (+) 1) 0
myLength1 :: [a] -> Integer
myLength1 [] = 0
myLength1 (x:xs) = (+1) (myLength1 xs)
哪一个更好?
从我的角度来看, myLength1
更容易理解,并且在列表上操作看起来很自然。
另一方面, myLength
更短,并且不使用递归; 这是否意味着myLength
比myLength1
运行得更快?
请记住foldr
这种“伪实现”:
foldr :: function -> initializer -> [a] -> b
foldr _ i [] = i
foldr f i (x:xs) = x `f` (foldr f i xs)
现在我们有了你的代码
myLength :: [a] -> Integer
myLength = foldr (\x -> (+) 1) 0
myLength1 :: [a] -> Integer
myLength1 [] = 0
myLength1 (x:xs) = (+1) (myLength1 xs)
由于foldr
本身也是递归的,因此 myLength1 和 myLength 几乎相同,但在第一种情况下,递归调用由 foldr 完成,而不是由您自己明确完成。 他们应该在同一时间跑。
两个函数都做同样的事情:foldr 使用递归,最终会以类似于直接递归函数的方式执行。 可能有人会说 foldr 版本更简洁(一旦你习惯了它们,高阶函数通常比直接递归更具可读性)。
但是这两个函数非常糟糕:它们最终都会构建一个很大的 thunk(一个未评估的值) 1 + (1 + (1 + ... + 0)..))
这将占用大量内存( O (n) space ) 并且会减慢评估速度。 为避免这种情况,您应该从列表的开头开始添加 1,如下所示:
betterLength xs = go 0 xs
where
go n [] = n
go n (_:xs) = n `seq` go (n+1) xs
seq
确保在递归调用 go 函数之前评估 n ,因此没有+1
累积。 使用BangPatterns
扩展,你可以这样写:
betterLength xs = go 0 xs
where
go n [] = n
go !n (_:xs) = go (n+1) xs
也可以使用 fold 执行此版本:
betterLength = foldl' (\n _ -> n + 1) 0
其中foldl'
从L EFT就是严格(”)。
使用foldr
,它可以实现为:
length' xs = foldr (\_ n -> 1 + n) 0 xs
解释:
lambda 函数(\\_ x -> n + 1)
每次有一个元素时都会将累加器加一。 例如:
lenght' [1..4]
将应用为: 1 + ( 1 + ( 1 + ( 1 + 0)))
回想一下foldr
是这样定义的:
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f v [] = v
foldr f v (x:xs) = f x (foldr f v xs)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.