Haskell中的map/zip与列表推导式比较

21

你最可能写哪种代码?

r = zip xs $ map sqrt xs
或者
r = [(x, sqrt x) | x <- xs]

互联网上的示例代码似乎表明前者更常见且更受欢迎。

7个回答

27

在 #haskell 里花费过多时间的人可能会将其写成:

r = map (id &&& sqrt) xs

(&&&) 是在 Control.Arrow 中定义的有趣组合子。它的实际类型签名很复杂,因为它被泛化到了 Arrow 的所有实例上。但它经常与 (->) 的 Arrow 实例一起使用,从而产生了下面的类型签名:

(&&&) :: (a -> b) -> (a -> c) -> a -> (b, c)

7
我称之为“不透明的Haskell”。在一定程度上,它很难读懂,而这个程度因人而异。 - Dan
1
事实上,我倾向于定义一个函数,因为没有更好的名称,所以我称之为“preserving :: (a -> b) -> a -> (a,b)”,简单地定义为“preserving = (id &&&)”。(有时我也会交换元组顺序并将其定义为“(&&& id)”;我应该保持一致。)我总是有点惊讶它似乎在任何标准中都不存在。 - Antal Spector-Zabusky
8
丹,我认为那可能是在实际代码中最简单使用的箭头组合子。它的名称很生动形象:你可以大声朗读这行代码,“将id和sqrt映射到xs”。 - Carl
1
我同意,我认为这非常易读。它确实迫使读者学习&&&的含义,但那很有趣 :-). - mk12

18

尽管我通常不太经常使用它们,但在这种情况下,我认为我更喜欢列表推导式的版本,因为它看起来更简洁。

如果你喜欢点-自由风格,你也可能会喜欢这个:

f = zip `ap` map sqrt

ap函数位于Control.Monad中,在这种情况下,它可以被认为是S组合子,该组合子在SKI演算中概括了应用:

ap f g x == f x (g x)
ap const const == id

正如Conal所指出的那样,这也可以从Monad推广到Applicative,因此(import Control.Applicative):

f = zip <*> map sqrt

1
很好,我最喜欢这种表示法。我总是把ap读作“应用”,所以它变成了:“将zip、apply和map与sqrt一起应用到输入”。 - Dan
2
或者,从“Monad”简化/概括到“Applicative”,f = zip <*> map sqrt。尽管我个人更喜欢像卡尔的答案中那样使用map (id &&& sqrt) xs - Conal
没错,在我看来这是一个不错的阅读方式。@Conal 把你的建议添加到了答案里。 - danlei

13

我可能会写 map/zip,但后来希望我写了列表推导式。


12

我可能会写成

map (\x -> (x, sqrt x)) xs

如果您喜欢使用点符号风格,上述内容等价于(在导入Control.MonadControl.Monad.Instances后):

map (ap (,) sqrt) xs

还有一个尚未提到的替代方案是

zipWith (,) xs (map sqrt xs)

11

对于某些问题(特别是欧拉计划),这种情况经常出现,我编写了以下小助手:

with :: (a -> b) -> a -> (a,b)
with f a = (a, f a)

这样可以让你的示例被写成:

r = map (with sqrt) xs

8

我更倾向于使用“老派”的Haskell方法,所以我会使用zip `ap` map sqrt,然后稍后重构它以使用<*>而不是ap

Applicative是新的Monad。(在“Cool Haskell Kids现在使用什么?”的意义上)


3
箭头是新的单子,对吗? - Vlad Patryshev

7

我很少使用列表推导式,但两者都很好用。只需使用使您的代码更易于阅读的那个。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接