什么是可以通过列表推导完成的事情的一个可靠的例子,这对于高阶函数来说是棘手的?

时间:2013-05-28 04:59:48

标签: python haskell clojure functional-programming list-comprehension

我从许多Python家手中听说他们更喜欢列表推导,因为他们可以使用高阶函数执行所有操作,例如filter和reduce,。所以这个问题解决了这些问题:你可以用它们做些什么的一个可靠的例子,这对于HOF来说很棘手?

6 个答案:

答案 0 :(得分:39)

答案是没有这样的例子。列表推导所能做的一切都是对高阶函数的机械转换。实际上,这就是Haskell实现列表推导的方式:它将它们置于高阶函数中。

给出这样的列表理解:

[(x, y) | x <- [1..3], y <- [4..6]]

哈斯克尔贬低它:

concatMap (\x -> concatMap (\y -> [(x, y)]) [4..6]) [1..3]

同样,如果你输入谓词:

[(x, y) | x <- [1..3], y <- [4..6], x + y /= 5]

......然后去了:

concatMap (\x -> concatMap (\y -> if (x + y) == 5 then [(x, y)] else []) [4..6]) [1..3]

事实上,这个desugaring是Haskell规范的一部分,您可以找到here

答案 1 :(得分:20)

正如已经说过的,你可以用列表推导做的所有事情都可以用到高阶函数中,但在Python中这样做的很大一部分问题是Python缺乏对你那种无点编程的支持可以与Haskell中的filtermap和朋友一起使用。这是一个有点人为的例子,但我想你会明白这个想法。

我们来看看这段Python代码:

[(x,y) for x,y in zip(xrange(20), xrange(20, 0, -1)) if x % 2 == 0 and y % 2 == 0]

所有这一切都是打印出来的:

[(0, 20), (2, 18), (4, 16), (6, 14), (8, 12), (10, 10), (12, 8), (14, 6), (16, 4), (18, 2)]

以下是带过滤器的等效版本:

filter(lambda ns : ns[0] % 2 == 0 and ns[1] % 2 == 0, zip(xrange(20), xrange(20, 0, -1)))

我希望你能同意我的意见,这是更加丑陋的。如果没有定义单独的函数,那么你可以做的就是减少丑陋。

但是让我们看看Haskell中的等效版本:

[(x,y) | (x,y) <- zip [0..20] [20,19..0], x `mod` 2 == 0 && y `mod` 2 == 0]

好的,几乎和Python列表理解版一样好。等效过滤器版本怎么样?

import Data.Function
let f = (&&) `on` (==0) . (`mod` 2)
filter (uncurry f) $ zip [0..20] [20,19..0]

好的,我们必须进行导入,但是一旦你理解了它的作用,代码就是(imo)更加清晰,尽管有些人可能仍然希望指向f,或者甚至是带有过滤器的lambda 。在我看来,无点版本更简洁,概念更清晰。但我想说的主要观点是,Python中并没有真正明白这一点,因为如果不引入单独的库就无法部分应用函数,而且缺少组合运算符,所以在 Python中优先选择列表推导而不是map / filter,但在Haskell中,它可以根据具体问题采取任何一种方式。

答案 2 :(得分:10)

在Haskell中,列表推导是条件和函数的“语法糖”(或者可以简单地转换为符号,然后单独地进行desugared)。以下是翻译它们的“官方”指南:http://www.haskell.org/onlinereport/haskell2010/haskellch3.html#x8-420003.11

因此,由于列表推导可以使用简单的高阶函数机械地和直接地转换为等价的代码,因此根据定义,没有任何东西可以用它们来做,没有它们很难做到。

答案 3 :(得分:6)

其他人都是正确的;与map,reduce,filter等函数相比,list comprehensions本身并没有提供更好的序列操作。但是,他们并没有真正解决为什么Python程序员胜过列表推导而不是高阶函数的问题。

Python提倡它并且Python程序员使用它们的原因是因为根据语言创建者Guido的说法,列表推导(以及设置理解和dict压缩和生成器表达式)比函数表达式更容易阅读和编写。 Python的理念是可读性胜过一切。

Guido一般不喜欢函数式编程结构,并且对添加lambda语法持谨慎态度。这只是风格和品味的问题,而不是表现力或力量。他的观点塑造了Python及其编写方式。

有关详细信息,以下是Guido从Python 3及更高版本中删除lambdamapfilterreduce的提案。它没有实现(除了删除reduce,它不再是内置函数),但是他在这里列出了他的推理:http://www.artima.com/weblogs/viewpost.jsp?thread=98196

他总结如下:

  

过滤器(P,S)几乎总是写得更清楚,因为[如果是P(x)]则为x中的x,并且这具有巨大的优点,即最常见的用法涉及比较的谓词,例如, x == 42,为此定义一个lambda只需要读者更多的努力(加上lambda比列表理解慢)。

答案 4 :(得分:1)

比较

    [[x*x, x*x+x ..] | x <- [2..]]

    map (\x-> map (*x) $ enumFrom x) $ enumFrom 2

第一个显然更具可读性。你问“棘手”,而不是“不可能”。对于filter,没有任何迹象表明我们是否在中过滤,或者 out 在给定测试中通过或失败的元素。对于LC,它在视觉上是显而易见的。

所以每当有LC配方时,它都是首选的IMO,只是为了它的可读性。 Haskell的LC语法特别简洁明了,比Python的IMO更清晰(更少 noise )。羞于不要使用它。 :)

答案 5 :(得分:0)

出于这个问题的确切原因,我很少使用列表推导。但是,在一种情况下,我发现它们是唯一简洁的语法:<-的左侧是可重提模式时。示例:

data Foo = Bar Int | Baz String

getBazs :: [Foo] -> [String]
getBazs xs = [x | Baz x <- xs]

要在没有列表理解的情况下编写代码,您将需要做很多事情,像这样:

data Foo = Bar Int | Baz String

getBazs :: [Foo] -> [String]
getBazs = foldr go []
          where go (Baz x) acc = x:acc
                go _       acc =   acc

但是与列表理解不同,它不是一个“好的生产者”,因此其输出列表将无法与任何东西融合。要解决此问题,您必须手动添加重写规则,或切换到导入其他好的生成器函数:

import Data.Maybe

data Foo = Bar Int | Baz String

getBazs :: [Foo] -> [String]
getBazs = mapMaybe go
          where go (Baz x) = Just x
                go _       = Nothing

对于相同的结果,最终结果是比基本的列表理解要花更多的时间思考和更多的代码。