本文将列举不同编程语言的几个小例子,尝试解释什么是列表解析。并且试图宣扬我的观点:“列表解析”不论从概念上还是技术上,完全是函数式编程的一根阑尾——多余而且某种程度上还是有害的。
何为列表解析?
以python中的列表解析为例:
1 |
S = [2*n for n in range(0,9) if ( (n % 2) == 0)] print S # prints [0, 4, 8, 12, 16] |
它产生一个0到8的列表,将奇数从该列表中移除,最后将剩余元素乘2,最后返回所得列表。
Python的列表解析(LC)的语法如下:
1 |
[myExpression for myVar in myList if myPredicateExpression] |
总而言之,这种特殊语法产生一个列表,并且允许程序员对其中元素进行过滤,以及将其中元素作为参数传给一个函数,但是所有这些都已“表达式”的形式出现。
(译者注:这个语法本身也是一个“表达式”,所以可以嵌套使用。)
列表解析的函数式的写法是这样的:
1 |
map( f, filter(list, predicate)) |
其他语言的列表解析是相似的。这儿有几个来自维基百科的例子。在下面的例子里,x^2>3作为条件,然后把每个元素乘以2返回结果.
Haskell
1 |
s = [ 2*x | x <- [0..], x^2 > 3 ] |
F#
1 |
seq { for x in 0..100 do if x*x > 3 then yield 2*x } ;; |
OCaml
1 |
[? 2 * x | x <- 0 -- max_int ; x * x > 3 ?];; |
Clojure
1 |
(take 20 (for [x (iterate inc 0) :when (> (* x x) 3)] (* 2 x))) |
Common Lisp
1 |
(loop for x from 1 to 20 when (> (* x x) 3) collect (* 2 x)) |
Erlang
1 |
S = [2*X || X <- lists:seq(0,100), X*X > 3]. |
Scala
1 |
val s = for (x <- Stream.from(0); if x*x > 3) yield 2*x |
这里是维基百科对
List comprehension
的解释,引用如下:
A list comprehension is a
syntactic construct
available in some programming languages for creating a list based on existing lists.
列表理解(LC)有以下特征:
*1.一个直接的列表生成器,可以对元素进行过滤,并且对每个元素应用一个函数
*2.是某些语言里面的特殊语法
*3.这种语法是一个单独的表达式,而不是由单独的函数组成
为什么列表理解是有害的?
- 列表理解就像一个不透明的俚语一样,它妨碍沟通,造成误会
- 列表理解是编程里面一个冗余的概念。它只是一个简单的列表生成器。他可以被简单的功能函数formmap(func,filter(list,predicate))代替,或者被一些语句代替,比如perl:for (0..9) { if ( ($_ % 2) == 0) {push @result, $_*2 }}.
- 这种存在于多种语言中的特殊语法,其实不是必要的。如果需要这样的函数,那么它可以直接是一个一般的函数,比如LC(function,list,predicate).
列表解析语法并不是很有必要。一个更好更一致的方法是用函数式语言的精髓,使用普通函数的组合。
1 |
map( f, filter(list, predicate)) |
这是python的语法
:
1 |
map(lambda x: 2*x , filter( lambda x:x%2==0, range(9) ) ) # result is [0, 4, 8, 12, 16] |
在Mathematica中,可以这样写
1 |
Map[ #*2 |
在Mathematica中,算术操作符可以不使用Map而直接映射到列表,因此上面的代码可以这样写:
1 |
Select[Range@9, EvenQ] * 2 |
还可以写成线性前缀风格:
1 |
(#*2 |
或者线性后缀风格:
1 |
9 // Range // (Select[#, EvenQ] |
在上面,我们就像Unix里的管道那样排列函数在一起。我们从9开始,使用“Range”来获取一个1到9的列表,然后使用一个函数来过滤出偶数,接着我们使用一个函数来把过滤出来的每个数字乘以2。符号“//”是一个后缀符号,类似于bash(shell)的“|”符号,同时,“@”是一个与“|”相反的符号。
(☛ Short Intro of Mathematica For Lisp Programers)
无需特殊语法的列表理解函数
在函数式语言中,假如我们想要“列表解析”这一特性。通常地,默认情况这可以这样做
1 |
map(func, filter(inputList, Predicate)) |
但这种用法会很频繁,我们想为此创建一个更方便的函数。作为一个独立的函数,它更容易被编译器优化。因此,我们可以创建一个函数LC像这样:
1 |
LC(func, inputList, Predicate) |
这个关系到一种语言是否应该创建一个更方便的新函数,否则就需要3个函数的组合。Common Lisp和Scheme Lisp是极端对立的典型例子。
注意,这里没涉及到新的语法。
假设,某人对下面的有争论:
实际上, 这个语法:
1 [x+1 for x in [1,2,3,4,5] if x%2==0]远比这个语法方便:
1