pythonhaskellfunctional-programmingcombinators

Python: Haskell-like . / $


In Haskell I would write:

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

in Python I would have to use either many brackets or useless variables ... is there anything like . and $ in Python?


Solution

  • I would just use whatever idiomatic Python tools are available, such as list comprehensions, as others have pointed out, instead of trying to pretend you're writing Haskell, but if you really must, you could use a compose combinator even in Python:

    # 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))
    

    which you could use like this:

    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))
    

    which admittedly does work:

    >>> 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]
    

    ...but as you can see, Python lacks certain features such as currying and arbitrarily definable infix operators, so even though semantically, the above snippet of code is equivalent its Haskell counterpart, it is quite unreadable even for Haskellers.


    As to the $ operator: it has little relevance in Python ā€” its primary purpose in Haskell is related to operator precedence, which is a non-issue in Python because you can't really use operators most of the time anyway, and all of the built-in operators have predefined precedence.

    And whereas $ can additionally be used as a higher order function in Haskell:

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

    ...replicating this in Python with its (deprecated) apply "combinator" will, again, lead to code that is just ugly:

    >>> 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]
    

    ā€” again, several fundamental limitations of Python are at play here: