【发布时间】:2019-11-12 11:44:13
【问题描述】:
我正在查看一些前奏函数来教同事递归,我发现有些是以一种相当奇怪的方式编写的。示例:
{-# NOINLINE [1] zipWith #-}
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith f = go
where
go [] _ = []
go _ [] = []
go (x:xs) (y:ys) = f x y : go xs ys
为什么它被写成对go 的调用,而go 是在之后定义的? IMO 更自然的定义方式是:
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith _ [] _ = []
zipWith _ _ [] = []
zipWith f (x:xs) (y:ys) = f x y : zipWith f xs ys
我猜这是由于一些 inline / loop-fusion / lazy / whatever-feature 允许 GHC 更好地优化,但这就是 Haskell 对我来说变得非常模糊的地方.任何人都可以解释(尽可能简单)这种函数定义的原因,如果有的话。
编辑
许多 cmets,@AndrewRay 的回答以及在这个 question 中,都指出内联的方向是这种辅助局部函数的原因。尽管如此,zipWith 被标记为 pragma NOINLINE [1] zipWith,直到 GHC's user guide: 7.13.5.5. Phase control,意味着在第一阶段之前不内联,并且可能在之后内联(无论这意味着什么)。在链接的问题中,PO 指的是foldr,它是使用相同的技巧实现的,但没有任何 PRAGMA。我认为作者正在避免内联,但我对此的了解很少。
编辑 2
添加了source定义zipWith的链接
【问题讨论】:
-
避免每次都传递
f。 -
@WillemVanOnsem 这是一个快速的答案!你能详细说明一下吗?这似乎并不明显,因为
go实际上已通过,并且go在其定义中包含f......不是吗? -
“更自然”在旁观者的眼中。对我来说
go很自然。它压缩了它的两个列表参数。用什么拉链?有了它知道的东西。那个“东西”与压缩的递归机制无关。 -
@n.'pronouns'm。奥卡姆剃刀没有任何主观性。
go是一个新实体,它没有任何概念——他们甚至没有想出一个真实的名字。所以这是一种精心设计,而且在外延上是无关紧要的——不自然。 -
您甚至没有最易读的
go版本。首先定义go (x:xs) (y:ys),然后其他两个基本情况减少为单个情况go _ _ = []。