【问题标题】:Python: Haskell-like . / $Python:类似 Haskell 的 . / $
【发布时间】:2015-10-28 13:04:52
【问题描述】:

在 Haskell 中我会写:

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

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

【问题讨论】:

  • 对于我们这些不熟悉 Haskell 的人,您能否描述一下该操作的作用,可能带有输入/输出示例?
  • @CoryKramer, . 允许函数部分应用,mapfilter 接收(就像在 Python 中一样)一个函数和一个列表,所以 map(function).filter(function2, list) 意味着 map 应用 function在将function2 应用于list 的元素之后,返回filter 的每个元素。这是因为 haskell 默认是惰性的
  • 在 Haskell 中. 是函数组合,大致上f . g 大致代表Python lambda x: f(g(x))。相反$ 是应用程序f $ x 只是f(x),但允许避免括号,例如f $ x+y+z 意思是f(x+y+z)
  • 尽管如此,即使有办法欺骗 Python 来模拟 Haskell 的这些句法元素,我也不确定使用它们是否是个好主意。我会尝试在 Haskell 中使用惯用的 Haskell 代码,在 Python 中使用惯用的 Python 代码。
  • 那边的do是多余的。

标签: python haskell functional-programming combinators


【解决方案1】:

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

您可以使用列表推导来执行过滤和求幂。

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

【讨论】:

  • 您也可以通过调整范围来移除if 保护:range(2, 21, 2)
  • 我知道我可以在 Python 中将这个特定的表达式写成一个简单的表达式,但是一般来说有什么方法可以做到这一点(当遇到更复杂的问题时)?
  • @Danielhauck 有许多库使用非常讨厌的 hack 来模仿 haskell 的风格,但它们都比生产就绪的库更有趣。一般来说,您不会得到 Haskell 的简洁性(尽管您可以在 Haskell 中将其编写为列表推导式 [i**2 | i <- [2,4..20]],与 python 非常相似)。一般来说,如果使用迭代器,我可以推荐使用生成器表达式而不是函数组合,但与 Haskell 相比,Python 中严重缺乏函数组合和部分应用程序。
【解决方案2】:

我只会使用任何可用的惯用 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 缺少某些概念,例如柯里化和可任意定义的中缀运算符,因此即使在语义上,上述代码的 sn-p 也是等价的(甚至相同)对 Haskell sn-p 来说,它读起来很糟糕。


至于$ 运算符:它在 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 的几个基本限制在这里发挥作用:

  • 惰性不是内置的,因此不会自动处理,因此如果不使用list()“强制”星图,您将不会得到“正常”列表;
  • apply 不是(a -> b) -> a -> b 而是(a1 -> a2 -> ... -> aN -> b) -> (a1, a2, ..., aN) -> b,所以需要用[] 包裹列表元素并使用starmap 而不是普通的map;这也是缺乏咖喱的结果;
  • lambda 语法很冗长,因为 Guido 的个人偏好是反对 lambda,mapreduce 等等;

【讨论】:

    【解决方案3】:

    具有讽刺意味的是(因为列表推导是 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 函数接受一个参数并返回一个值。考虑到为fg 定义的类型签名,编译器很容易检查f . g 是否有意义。然而,Python 没有这样的类型签名(即使在 3.5 中,类型提示也是可选的,并且仅在静态分析期间使用,而不是在运行时使用)。

    此外,Python 函数可以接受任意数量的参数(没有柯里化),并且元组是可变长度的,而不是固定长度的。假设g 返回一个元组。 f ∘ g(我个人对组合运算符的选择是否应该被采用,并且允许使用 Unicode 运算符)是否应该等同于 f(g(...))f(*g(...))?两者都是有道理的,并指出了对两种不同类型的构图的“需求”。如果g 的返回值对于f 的值太多或太少怎么办? f 的关键字参数呢?它们应该从g 返回的字典中获取吗?在 Python 中定义看似简单的操作变得相当复杂。


    另一件事我可能完全错了。我的印象是,虽然 Python 中的每个函数都被编译为一段不同的代码,但 Haskell 可以为每个组合编译优化的代码,因此 f . g 不只是天真地转换为 \x -&gt; 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
    

    【讨论】:

    • 我更喜欢 Haskell 中的这个:for_ [2,4..20] (print . (^2))。或者,尖锐的风格,for_ [2,4..20] $ \x -&gt; print (x^2)。这强调了该列表被用作Traversable,而不是真正用作Monad
    【解决方案4】:

    对于这种情况,您最好使用@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))
    

    【讨论】:

      【解决方案5】:

      由于 map 函数返回可迭代和过滤的列表,你也可以嵌套它们 -

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

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

      【讨论】:

      • 是的,只是为可能希望看到此方法的人展示。另外我认为这更像是pythonic :)
      猜你喜欢
      • 1970-01-01
      • 2021-05-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多