Python: Haskell-喜欢。 / $

Python: Haskell-like . / $

在Haskell中我会写:

main = do mapM_ print . map (\x -> x^2) . filter (\x -> (mod x 2) == 0) $ [1..20]

in Python 我将不得不使用许多括号或无用的变量...在 Python 中是否有类似 .$ 的东西?

(我不熟悉 Haskell,但如果我正确理解您的代码片段...)

您可以使用列表理解来执行过滤和取幂。

[i**2 for i in range(1,21) if i%2 == 0]

因为 map 函数 returns 可迭代的列表和过滤器也可以嵌套它们 -

map(function1, (filter(function2,list)))

有关更多信息,我建议您阅读 map function documentation also filter function documentation

对于这种情况,您最好使用@CoryKramer 所说的列表理解。

要在 Python 中应用部分应用程序,您应该使用 functools.partial,就像这样

from functools import partial
def compose(func1, *func2):
    return func1 if not func2 else lambda x: func1(compose(*func2)(x))

myMap = partial(map, lambda x: x**2)
myFilter = partial(filter, lambda x: x%2 == 0)

myFunction = compose(myMap, myFilter)

myFunction(range(20))

我会 使用任何可用的惯用 Python 工具,例如其他人指出的列表理解,而不是试图假装你在写Haskell,但如果你真的必须,你甚至可以在 Python:

中使用 compose 组合函数
# this is essentially just foldr (or right `reduce`) specialised on `compose2`
def compose(*args):
    ret = identity
    for f in reversed(args):
        ret = compose2(f, ret)
    return ret

def identity(x):    return x
def compose2(f, g): return lambda x: f(g(x))

你可以这样使用:

from functools import partial

# equiv. of:  map (\x -> x^2) . filter (\x -> (mod x 2) == 0) $ [1..20]
compose(partial(map, lambda x: x**2), partial(filter, lambda x: x % 2 == 0))(range(1, 21))

这确实有效:

>>> compose(partial(map, lambda x: x**2), partial(filter, lambda x: x % 2 == 0))(range(1, 21))
[4, 16, 36, 64, 100, 144, 196, 256, 324, 400]

... 但是 如您所见,Python 缺少某些概念,例如柯里化和任意可定义的中缀运算符,因此即使在语义上,上面的片段代码与 Haskell 片段等效(甚至相同),它读起来非常糟糕。


至于 $ 运算符: 它在 Python 中几乎没有关系 — 它在 Haskell 中的主要目的与运算符优先级有关,这在 Python 中不是问题,因为大多数时候你不能真正使用运算符,而且所有内置运算符都有预定义的优先级。

$ 还可以用作 Haskell 中的高阶函数:

zipWith ($) [(3*), (4+), (5-)] [1,2,3]

...在 Python 中复制它及其(已弃用)apply "combinator" 将再次导致代码丑陋:

>>> list(starmap(apply, zip([lambda x: 3 * x, lambda x: 4 + x, lambda x: 5 - x], map(lambda x: [x], [1, 2, 3]))))
[3, 6, 2]

— 同样,Python 的几个基本限制在这里发挥作用:

  • laziness 不是内置的,因此不会自动处理,所以如果没有 "forcing" 使用 list() 的星图,你不会得到 "normal" 列表;
  • apply不是(a -> b) -> a -> b而是(a1 -> a2 -> ... -> aN -> b) -> (a1, a2, ..., aN) -> b,所以需要用[]包裹列表元素,使用starmap而不是正常的map;这也是缺乏柯里化的结果;
  • lambda 语法冗长,因为 Guido 个人偏好反对 lambda,mapreduce 等;

具有讽刺意味的是(因为列表理解是 Python 从 Haskell 等语言中借用的东西),我可能会用两种语言编写类似的代码:

# Python
for xsquared in [x**2 for x in range(1, 21) if x % 2 == 0]:
    print(xsquared)
# legal, but not idiomatic; you don't construct a list just
# to throw it away.
# map(print, [x**2 for x in range(1, 21) if x % 2 == 0])

-- Haskell
main = (mapM_ print) [ x^2 | x <- [1..20], x `mod` 2 == 0 ]

或每个更简短:

# Python
for xsquared in [x**2 for x in range(2, 21, 2)]:
    print(xsquared)

-- Haskell
main = (mapM_ print) [x^2 | x <- [2,4..20]]

Python 中的函数比 Haskell 中的函数更难编写。 Haskell 函数有一个参数,return 有一个值。鉴于 fg 的定义类型签名,编译器很容易检查 f . g 是否有意义。然而,Python 没有这样的类型签名(即使在 3.5 中,类型提示也是可选的,并且仅在静态分析期间使用,而不是在运行时使用)。

此外,Python 函数可以接受任意数量的参数(无柯里化),并且元组是可变长度的,而不是固定长度的。假设 g return 是一个元组。 f ∘ g(我个人对组合运算符的选择是否应该被采用,并且允许使用 Unicode 运算符)是否应该等同于 f(g(...))f(*g(...))?两者都有意义,并指向两种不同类型的组合的 "need"。如果 g 的 return 值对于 f 来说太多或太少怎么办? f 的关键字参数怎么样?它们应该取自 return 由 g 编辑的字典吗?在 Python.

中定义看似简单的操作变得相当复杂

还有一件事我可能完全错了。我的印象是 Python 中的每个函数都被编译为一段不同的代码,而 Haskell 可以为每个组合编译优化代码,因此 f . g 不只是天真地转换为\x -> f (g x)。至少在Python,

def f(x):
    return x + 5

def g(x):
    return 3 * x

这是编译器可以为 f∘g

生成的内容
def fg(x):
    return f(g(x))

这将远低于我所理解的 Haskell 编译器生成的等效内容:

def fg(x):
    return 3*x + 5