列表推导式是否比列表抽象更好?

4

为什么人们更喜欢像
(for [x '(1 2 3)] (* 2 x))这样的列表推导式,而不是(map #(* %1 2) '(1 2 3))

这种编程方式有什么好处吗?
1. 是否更易读?
2. 在某些情况下是否更快?
3. 是否更适合某些操作和数据结构?

5个回答

8

对于你给出的例子,没有任何好处;但是通常情况下,for 在连接两个(或多个)序列或进行过滤时非常有用 - 带有 :let:whenfor 通常比嵌套的 mapfilter 更易读。


2
列表推导式只是对标准函数式程序的“语法糖”,但它们提供了对列表常见操作的直观理解。 -- Guy Lapalme 它们唯一的目的是提高可读性。不要指望使用它们会有任何显著的性能提升。
这篇论文提供了在类Lisp语言中实现列表推导式的一些见解。 (链接)

1

一些通用想法

首先,列表推导只是语法糖,因此除了可读性之外,不应该有任何固有的区别。

但请注意,列表推导实际上将至少三个高阶函数(即mapfilterconcatMap)统一为一个语法。因此,在需要这些函数的复杂组合的非平凡情况下,该语法可以具有很大的可读性优势。

此外,我们可以方便地使用值绑定来存储结果并使事情更加清晰:

[ (x, y) | x <- [1..10],
           y <- [1..10], 
           let dist = (x - 5)² + (y - 5)²,
           dist < 10² ]

无论如何,列表推导式可以比仅处理列表更加通用。基本上,它们可以使用统一和方便的语法处理任何Monad。例如,F#甚至将其扩展到控制任意程序流。

其次,比通常的高阶函数更抽象,列表推导式实际上可能会更快,因为编译器可以利用优化规则,否则需要程序员考虑。

[ x + 1 | x <- numbers, even x ]

直接的翻译是

map (\x -> x + 1) (filter even x)

在这里,列表被迭代了两次,产生了一个毫无意义的中间结果。

然而,编译器可以识别并优化上述模式,并生成直接的、优化的filterMap版本。


0

列表推导式用于需要以更复杂的方式迭代序列的情况。在您的示例中,它们是相同的,因此我建议使用map,因为它更易于阅读。

对于这个例子,使用map会变得很丑陋。

(for [x '(1 2 3) y '(3 2 1)] (* 2 x y))

3
一个map无法做到,但mapcat可以。一般来说,可以用mapmapcatfiltertake-while的某种组合来替换列表推导式。对于更复杂的情况,结果可能很难阅读。 - Pavel Minaev
虽然它的功能与上面的for循环不同,但我认为你可以放弃(list),只需使用map就能获得相同的效果。示例中的代码遍历x和y的所有组合,就像Java中的两个嵌套for循环。 - Arthur Ulfeldt
你是正确的。我们需要两个映射来执行那些嵌套循环操作。这只是为了使代码更易读吗?列表推导式会展开成什么? - unj2

0

我知道这是一个Clojure问题,但是马丁·奥德斯基等人的书《Scala编程》对for推导式如何映射到mapfilter函数有一个不错的讨论。


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