我认为这是Haskell中阶乘函数的一般形式:
factorial :: (Integral a) => a -> a
factorial n = product [1..n]
我知道这是最优雅的方式,但是当我编写自己的递归函数时,它会明显变慢:
factorial :: (Integral a) => a -> a
factorial 1 = 1
factorial n = n * factorial (n - 1)
第一个解决方案不是必须在内部完成第一个解决方案所做的一切吗?怎么这么快?是否有可能在不使用花式列表符号或产品功能的情况下编写与第一个解决方案一样快的内容?
第一个版本比GHC更容易优化。尤其是, 产品用途 foldl
:
product = foldl (*) 1
并适用于 [1..n]
(这是公正的 1 `enumFromTo` n
它受制于 聚变。简而言之,GHC精心设计了重写规则,旨在优化中间数据结构,从而立即消耗所创建列表的代码片段(在 factorial
, foldl (*) 1
是消费者和 1 `enumFromTo` n
制片人)。
请注意,你可以做GHC做的事情(factorial = foldl (*) 1 . enumFromTo 1
)并获得相同的表现。
此外,你的第二个功能甚至不是尾递归。通过传递累加器,你可以很容易地解决这个问题:
factorial :: (Integral a) => a -> a
factorial n = go n 1
where
go 0 m = m
go n m = go (n-1) (n*m)
与此相辅相成的是,对于大多数数字类型,您都希望算术是严格的。归结为添加 BangPatterns
至 n
和 m
。
第一个版本比GHC更容易优化。尤其是, 产品用途 foldl
:
product = foldl (*) 1
并适用于 [1..n]
(这是公正的 1 `enumFromTo` n
它受制于 聚变。简而言之,GHC精心设计了重写规则,旨在优化中间数据结构,从而立即消耗所创建列表的代码片段(在 factorial
, foldl (*) 1
是消费者和 1 `enumFromTo` n
制片人)。
请注意,你可以做GHC做的事情(factorial = foldl (*) 1 . enumFromTo 1
)并获得相同的表现。
此外,你的第二个功能甚至不是尾递归。通过传递累加器,你可以很容易地解决这个问题:
factorial :: (Integral a) => a -> a
factorial n = go n 1
where
go 0 m = m
go n m = go (n-1) (n*m)
与此相辅相成的是,对于大多数数字类型,您都希望算术是严格的。归结为添加 BangPatterns
至 n
和 m
。
也许是这样的:
f n = foldl (*) 1 [1..n]
您可以在foldr或foldl上更改foldl'它将改变速度