【发布时间】:2023-04-02 10:19:02
【问题描述】:
为什么人们更喜欢像(for [x '(1 2 3)] (* 2 x)) 这样的列表推导而不是(map #(* %1 2) '(1 2 3))?
这种编程有什么好处吗?
1. 是否更具可读性?
2. 在某些情况下会更快吗?
3. 对某些类型的操作和数据结构更好吗?
【问题讨论】:
标签: functional-programming clojure list-comprehension
为什么人们更喜欢像(for [x '(1 2 3)] (* 2 x)) 这样的列表推导而不是(map #(* %1 2) '(1 2 3))?
这种编程有什么好处吗?
1. 是否更具可读性?
2. 在某些情况下会更快吗?
3. 对某些类型的操作和数据结构更好吗?
【问题讨论】:
标签: functional-programming clojure list-comprehension
对于您给出的示例,没有任何好处;但一般来说,for 在您连接两个(或更多)序列时很有用,或者当您需要进行一些过滤时 - for 与 :let 和 :when 通常比一连串更具可读性嵌套map 和filter。
【讨论】:
列表推导仅仅是 超出标准的“语法糖” 功能程序,但它们给出了一个 直观阅读常用操作 在列表中。 ——盖伊·拉帕尔梅
他们唯一的目的是提高可读性。不要期望通过使用它们来显着提高性能。
This 论文对在类似 Lisp 的语言中实现列表推导提供了一些见解。
【讨论】:
首先,列表推导式只是语法糖,所以除了可读性之外应该没有本质上的区别。
但请注意,列表推导实际上将至少三个高阶函数统一,即map、filter 和concatMap 合并为一个语法。因此,在需要这些复杂组合的重要情况下,语法可以具有很大的可读性优势。
除此之外,还可以方便地使用值绑定来存储结果并使事情更加清晰:
[ (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 版本。
【讨论】:
列表推导式用于需要以更复杂的方式遍历序列的情况。在您的示例中,它们是相同的,因此我会推荐地图,因为它更易于阅读。
map cant 因为 do 而变得丑陋,例如:
(for [x '(1 2 3) y '(3 2 1)] (* 2 x y))
【讨论】:
map 不能,但mapcat 可以。通常,总是可以用map、mapcat、filter 和take-while 的某种组合来替换列表推导式。只是对于更复杂的情况,结果可能很难阅读。
我知道这是一个 Clojure 问题,但 Martin Odersky 等人的《Programming in Scala》一书中很好地讨论了 for 理解如何映射到 map 和 filter 函数。
【讨论】: