I know that they are both different languages, but then aren't they both essentially the same? fmap applies a function over a Functor, whereas map of Python applies a function over an iterable. So if there is any difference between the two, can you provide an example of something we can do with map of Python but not fmap of Haskell (or vice versa)?
You can think of fmap as either a function that takes a function and a functor and then maps that function over the functor, or you can think of it as a function that takes a function and lifts that function so that it operates on functors. Both views are correct and in Haskell, equivalent.
map takes a function and a list and applies that function to every element in the list, producing a new list.
They’re pretty different.
The two functions do very similar things on list-like things, but that’s really where the similarities end. It would probably be more useful to compare Haskell’s map
with Python’s map
, but fmap
is both far more general and rather different in meaning.
In Haskell, fmap
is a sort of “lifting” or “piercing” operation. It takes a function from a -> b
and “lifts” it to a function from f a -> f b
. A list-like container is a sort of thing that can be “pierced”, but so many more things than lists are functors!
Mapping over a Maybe a
is extremely useful and common, though one might argue that Maybe
is just like a list of zero or one elements. However, consider the fact that functions are functors in Haskell. You can fmap
over a function and receive a new function, but functions are obviously not iterables. Similarly, parsers in parser combinator libraries like parsec are functors, and you can produce new parsers by fmap
ping over them to apply a function to their result.
All sorts of things are functors, many of which have little to do with containers that hold values. All monads are functors, including things like Writer
, State
, and Cont
, which are effectful computations, not list-like things. Other things are functors, too, like Const
, which can be combined with Lift
to form an applicative functor that monoidally collects errors.
The specifics of what each of these things are and do is outside the scope of this question, but the point is that the Haskell notion of a functor is far more general than an “iterable thing”, and it’s generally used in a different way from Python’s map
.
From another perspective, however, it’s fair to say that Python’s map
is more powerful than Haskell’s fmap
. There is a cost to generality, and that’s a need to be simple. Being specific gives you the option to do more things! This, plus Python’s other differences from Haskell (like dynamic typing and variadic functions), makes it possible for Python’s map
to do things that Haskell’s fmap
cannot.
As Daniel Sanchez points out, Python’s map
is variadic, so it acts like zipWith
in Haskell when provided multiple iterables:
>>> map(lambda x,y: x+y, range(10), range(10))
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
There’s a bit more, though. Python’s map
can also operate over different sorts of things. In Haskell, the generality of the Functor
class is useful mostly because it makes it possible to write functions that operate on any Functor
, and that means all Functor
s need to act in more or less the same way. The element(s) “inside” a f a
, if you want to use the container analogy, must be of type a
.
Consider what this means for something like a hash map or dictionary. In Haskell, Functor
s must have kind * -> *
, so Map
can only be a Functor
if partially applied. Therefore, Map k
has a Functor
instance, so fmap
only operates over the values of a Map
.
In Python, this is not nearly so rigidly enforced by the consequences of the type system, so dictionaries actually function as sequences of their keys when iterated over:
>>> map(None, {'foo': 1, 'bar': 2})
['foo', 'bar']
Oh, and that example also demonstrates another Python oddity—mapping None
over something acts like mapping the identity function, but mapping it over multiple things acts like Haskell’s zip
and produces tuples:
>>> map(None, [1, 2, 3], [4, 5, 6])
[(1, 4), (2, 5), (3, 6)]
Finally, note that Haskell’s fmap
cannot map over sets! Why is this? Well, the rigid type system requires that fmap
work for any values, but Set
s must be composed of values of a type with an Ord
instance. Python does not need to jump through any such hoops, so it can map over sets just fine:
>>> map(None, {1, 2, 3})
[1, 2, 3]
Python and Haskell are very different languages. A lot of these differences are simply artifacts of the language differences, not necessarily differences in what the functions are designed to do.
Still, the core difference is simple to summarize: Python’s map
is for mapping over list-like things, but Haskell’s fmap
is for lifting a function to operate on an arbitrary data structure. Those two things are quite distinct even once you look past language differences, so don’t assume they’re the same just because that have the same sort of shape on the surface.
Python map
has one behaviour that Haskell does not:
map(lambda x,y: x+y, range(10), range(10))
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
What it does its iterate over the pairs of n iterables unpacking the values from them and passing them as arguments to the function.
Actually Haskell map
can't do that due to its type signature
fmap :: Functor f => (a -> b) -> f a -> f b
That python map
usage is similar to Haskell zipWith
:
Prelude> zipWith (\x y -> x+y) [1..10] [1..10]
[2,4,6,8,10,12,14,16,18,20]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With