Python: Haskell-like . / $
Asked Answered
P

5

6

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?

Prophet answered 28/10, 2015 at 13:4 Comment(5)
For those of us not familiar with Haskell, can you describe what that operation does, possibly with an example input/output?Selfhypnosis
@CoryKramer, . allows function partial application, map and filter recieve (like in Python) a function and a list so map(function).filter(function2, list) means that map applies function to each element that returns filter after applying function2 to elements of list. That's cause haskell is lazy by defaultResent
In Haskell . is function composition, roughly f . g standing roughly for Python lambda x: f(g(x)). Instead $ is application f $ x is simply f(x), but allows one to avoid parentheses e.g. f $ x+y+z meaning f(x+y+z).Copyedit
Still, even if there were a way to trick Python in simulating these syntactic elements of Haskell, I'm not sure it would be a good idea to use them. I'd try to use idiomatic Haskell code in Haskell and idiomatic Python code in Python.Copyedit
That do over there is redundant.Rheumatic
R
7

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:

  • laziness isn't built-in and thus not handled automatically, so without "forcing" the starmap using list(), you don't get a "normal" list back;
  • apply is not (a -> b) -> a -> b but (a1 -> a2 -> ... -> aN -> b) -> (a1, a2, ..., aN) -> b, so you need to wrap the list elements with [] and use starmap not the normal map; this is also a result of the lack of currying;
  • lambda syntax is verbose because Guido's personal preference is against lambdas, map, reduce, and so on;
Rheumatic answered 28/10, 2015 at 13:23 Comment(0)
S
8

(I'm not familiar with Haskell, but if I understand your code snippet correctly...)

You can use a list comprehension to perform the filtering and exponentiation.

[i**2 for i in range(1,21) if i%2 == 0]
Selfhypnosis answered 28/10, 2015 at 13:7 Comment(3)
You can also remove the if guard by adjusting the range: range(2, 21, 2).Lodged
I know that I can write this particular one as a simple expression in Python, but is there any way to do this in general (when having more complicated problems)?Prophet
@Danielhauck There are a number of libraries out there that use pretty nasty hacks to emulate haskell's style but all of them are more of fun side projects than production ready libraries. You just aren't going to get Haskell's terseness in general (although you could have written it as a list comprehension in Haskell as [i**2 | i <- [2,4..20]], pretty similar to python). In general I can recommend using generator expressions rather than function composition if working with iterators, but function composition and partial application is severely lacking in Python compared to Haskell.Brassie
R
7

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:

  • laziness isn't built-in and thus not handled automatically, so without "forcing" the starmap using list(), you don't get a "normal" list back;
  • apply is not (a -> b) -> a -> b but (a1 -> a2 -> ... -> aN -> b) -> (a1, a2, ..., aN) -> b, so you need to wrap the list elements with [] and use starmap not the normal map; this is also a result of the lack of currying;
  • lambda syntax is verbose because Guido's personal preference is against lambdas, map, reduce, and so on;
Rheumatic answered 28/10, 2015 at 13:23 Comment(0)
L
2

Ironically (since list comprehensions are something that Python borrowed from languages like Haskell), I'd probably write the code similarly in both languages:

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

and

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

or more briefly in each:

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

Functions in Python are more difficult to compose than in Haskell. A Haskell function takes one argument and returns one value. It's easy for the compiler to check that f . g makes sense given the defined type signatures for f and g. Python, however, has no such type signatures (even in 3.5, the type hinting is optional and only used during static analysis, not during runtime).

Further, Python functions can take an arbitrary number of arguments (no currying), and tuples are variable length, not fixed length. Suppose g returns a tuple. Should f ∘ g (my personal choice for a composition operator should such a thing ever be adopted, and Unicode operators be permitted) be equivalent to f(g(...)) or f(*g(...))? Both make sense, and point to the "need" for two different types of composition. What if g's return value has too many or too few values for f? What about keyword arguments to f? Should they be taken from a dictionary returned by g? What seems like a simple operation becomes quite complex to define in Python.


One other thing I may be completely wrong about. I get the impression that whereas each function in Python is compiled as a distinct piece of code, Haskell can compile optimized code for each composition, so that f . g isn't just naively converted to \x -> f (g x). At least in Python, for

def f(x):
    return x + 5

def g(x):
    return 3 * x

this is what the compiler could generate for f∘g

def fg(x):
    return f(g(x))

which would be far less efficient than the equivalent of what I understand the Haskell compiler could generate:

def fg(x):
    return 3*x + 5
Lodged answered 28/10, 2015 at 13:51 Comment(1)
I like this one better in Haskell: for_ [2,4..20] (print . (^2)). Or, pointy style, for_ [2,4..20] $ \x -> print (x^2). This emphasizes that the list is being used as a Traversable and not really as a Monad.Eating
R
1

For this case you should better use a list comprehension like @CoryKramer said.

To apply partial application in Python you should use functools.partial, would be something like this

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))
Resent answered 28/10, 2015 at 13:18 Comment(0)
H
0

Since map function returns list which is iterable and filter also you can nest them -

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

For more information I would recommend you read the map function documentation also filter function documentation

Hughett answered 28/10, 2015 at 13:16 Comment(1)
Yes, just showing for the people that might want to see this method. Plus I assume this is more pythonic :)Hughett

© 2022 - 2025 — McMap. All rights reserved.