The Day Python Embarassed Imperative Programming
the-27th-comrade.appspot.com
the-27th-comrade.appspot.com
Beyond obeying some simple laws, there are strikingly few restrictions on the semantics of >>=. It can mean completely different things to different Monad instances. To me, that's at the heart of why newbies struggle to understand monads: >>= operates at a higher level of abstraction than many programmers are accustomed to. Outside of specific Monad instances, >>='s meaning is less significant than the structure that it imposes via the type system.
But that flexibility is also what makes Monad such a useful class in Haskell. Once your brain begins to recognize the `m a -> (a -> m b) -> m b` pattern in code, you see it everywhere. Monads are just a way of acknowledging that that particular structure exists in your code and abstracting it away to be replaced by a single operator. A chain of function calls with possible failure, like the author points out, is one case in which such a pattern emerges, but it's far from the only one. I think the article sort of misses that larger point.
m a -> (a -> m b) -> m b
Okay, so what does this mean? I thought it was `f x -> x * x` is a function definition, right? Or am I completely mistaken here? Because in the above code, what is the function name, the parameter and the return value? I know "everything is a function" in Haskell but I really can't see through the multiple arrows here. Any help?First of all, this is a function declaration (ie. prototype), not a definition. So this is describing a function type, not a function implementation. But that doesn't help very much because it's still not very obvious what the function type means.
The shortcut way to understanding Haskell function declarations is this; if you see:
a -> b -> c -> d
in your head, think of it as: f(a, b, c) -> d
In other words, it is a function that takes three parameters of types a, b, and c and returns type d. Everything before the final -> is a parameter and the final type is a return type.My "shortcut" isn't literally true, obviously. Here is the gory detail.
In Haskell, every function takes at most one parameter. Functions of multiple parameters do not exist; they are simulated through a technique called "currying." When you think you're calling a function of more than one parameter, you're actually calling a series of functions, each of which takes exactly one parameter. So in Haskell, if you call:
f a b c
This is actually parsed as ((f a) b) c
Or in more C-like notation: f(a)(b)(c)
In other words, you call a function with a single parameter "a", which returns a function that you call with a single parameter "b", which returns a function that you call with a single parameter "c."Likewise, the Haskell type declaration:
a -> b -> c -> d
Is actually right-associative, so it's parsed as: a -> (b -> (c -> d)))
Which is why the whole thing works.So to parse:
m a -> (a -> m b) -> m b
Think of it as a function that would be called like so: f(m a, g) -> m b
Where g is a function that would be called like so: g(a) -> m b
The "m a" and "m b" business you can think of as being a lot like M<a> and M<b> in C++. (>>=) :: Monad m => m a -> (a -> m b) -> m b
So, >>= is a function that takes:- A value a in a monadic type m
- A function (a -> m b): a function that takes a value a and returns a value b wrapped in the monadic type m.
And it returns:
- A value b wrapped in the monadic type m.
It helps to look at the definition of (>>=) for a particular monad. The article mostly discusses computations that can fail (None vs an object in Python). The corresponding Haskell monads are Maybe and Either. The signature for (>>=) in the Maybe monad is:
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
What the grandparent points out is that a monad defines a structure (how to combine expressions that result in wrapped values), but not semantics. The semantics are defined in the definition of a particular monad. E.g. the Maybe monad models failure in computation, while a monad such as MonadRandom does something different altogether (providing random numbers, while threading the state of the random number generator). (>>=) :: Monad m => m a -> (a -> m b) -> m b
It's not the definition of >>=, though. The definition is left up to the specific Monad instance that's defining it (which is basically the larger point that I was trying to make).In English, that type signature basically says that >>= is a function which takes a monad and a function that operates on the monad's "contents" as its arguments, and returns the result of applying the function to the monad's contents. Any data type for which that abstract structure makes sense (and that also complies with the basic "monad laws") can be made an instance of Monad and provide its own definition for how >>= operates. That definition can be wildly different for different data types; the specific definition that the article focuses on is the one for the Maybe type.
In this case, it's a type signature for a curried function. So
a -> m b
is a function taking a value of type `a` and returning a value of type `m b`. In C# you would write: Function<A, M<B>>
so `m a -> (a -> m b) -> m b` is a function taking a value of type `m a` and a function with signature `a -> m b` and returning a function of type `m b`. Translating to C# again, Function< M<A>, Function<A, M<B>>, M<B> >
> Or am I completely mistaken here?Pretty much yeah. In haskell, a function is either a top-level binding:
someFunction arg = doSomething arg
or an anonymous function which is introduced by the character `\` (because `\` looks like `λ`, kind-of): \arg -> doSomething arg
In the expression you quoted, there is no function name, these are all types. The function name in this case is `(>>=)`For example,
data Maybe a = Nothing | Just a
- The m is for "Maybe"
- The a is for "a"
For example: (>>=) (Just 3) (\ x -> Just (show x))
should returns: Just "3". (>>=) Nothing (\x -> Just (show x))
better written Nothing >>= (\x -> Just (show x))
should returns: Nothinghttp://conal.net/blog/posts/everything-is-a-function-in-hask...
I think perhaps another reason why beginners find monads so hard to understand, especially those coming from a math background, is that >>= is a bizarre presentation of monads. I think, for example, defining a monad as an applicative with a join operation would have been a much better idea - most monads, like Maybe or [], make the most sense to me as "containers" that can be joined. The bind operator never really made intuitive sense to me when thinking about a monad as a container with a structure. Plus, to me, m (m a) -> m a is a lot easier to recognize than m a -> (a -> m b) -> m b.
This is especially true since applicatives are now becoming a very popular language idiom. It seems unfair to beginners to make them try to understand applicatives and then make them learn an unrelated typeclass for monads, when mathematically the two are very similar objects.
I tried to explain it here ( http://www.reddit.com/r/programming/comments/erzh1/monads_ar... ) a while ago, which might be helpful for some people. Although, the version of List's >>= function is much easier to understand here ( http://en.wikibooks.org/wiki/Haskell/Advanced_monads#The_Lis... ) than the one I pulled out of GHC's library code.
I'm not being sarcastic or anything, I'm really wondering: are Python programmers using monads without knowing it? Or is it that monads are a good tool at giving a semantic (or "mathematical meaning") to what they are coding?
I think the distinction between the two points of view isn't relevant, but my point is that when you know something and like it, you easily see it everywhere: if you want it, all your code is just lambda-calculus, all your control structure are some kinds of monads, all for loops are just tail-recursive functions, all objects are just closures…
But the thing that makes Haskell unique is not that you have explicit monads. It's that you can (1) use monads other than the IO monad (like Maybe, [], STM, ST), and (2) that you can choose to use no monads at all in a given function.
To put it another way, Haskell is unique because it allows you write functions without using monads, but in other languages you can't turn them off. Like nullable types, which can't be turned off in Java (ever debug NullPointerExceptions?) or Python, but which have to be enabled on a case-by-case basis in Haskell.
def isnotnone(fn):
def decorator(*args, **kwargs):
for arg in args:
if arg is None:
raise TypeError("{0} does not accept None".format(fn.func_name))
for arg in kwargs.values():
if arg is None:
raise TypeError("{0} does not accept None".format(fn.func_name))
return fn(*args,**kwargs)
return decorator
@isnotnone
def f(a, b=dict):
return a(), b()
>>> f(None)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in decorator
TypeError: f does not accept None
>>> f(b=None)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 8, in decorator
TypeError: f does not accept None
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 9, in decorator
TypeError: f() takes at least 1 argument (0 given)
>>> f(str)
('', {})
>>>
[0]http://code.activestate.com/recipes/454322-type-checking-dec...Perhaps it's cleaner, and you can get accustomed to it, but it surely comes with an overhead over imperative thinking, at least for a beginner.
do
b <- f a
c <- g b
h c
Now, suppose that in a particular case f a returns Nothing, then the whole do-expression will evaluate to Nothing and g Nothing is never evaluated.Failure monads do not only add cleanliness, but also safety.
try:
b = f(a)
c = g(b)
return h(c)
If f(a) raises an exception, g and h don't get executed.None should be returned when it's a valid value (say, in search() if it doesn't find anything), and in those cases it makes sense to have explicit handling.
The nice thing about monads is that it provides an abstraction on sequences of computations, involving failure, error, state, effects, etc. Though, it can get ugly at times when you want to use multiple monads simultaneously (via monad transformers).
But I think that's a feature, not a bug. Conflating errors and valid values leads to ambiguity. As PEP 20 says, "Explicit it better than implicit".
If you actually want certain function return values to act as a failure, I think you should wrap it in a new function that adds those semantics.
Edit: Wow, I was downvoted for this?
I come to completely the opposite conclusion:
http://williamedwardscoder.tumblr.com/post/18319031919/progr...
There's nothing stopping someone writing a haskell-alike with readable syntax as long as they stay away from trying to create terms like monads to explain things that don't need explaining ;)
There's nothing stopping someone from doing that while trying to create such terms, either; witness Haskell. (Or maybe your argument is that the syntax isn't readable? Well, is `>>=` really worse than `?:` in terms of immediate apprehensibility?)
However, I think it's important to recognise that the use of 'monad' is not a case of "creat[ing] terms … to explain things that don't need explaining". A pattern was identified, and it was realised that it fit into the existing mathematical formalism of monads (http://en.wikipedia.org/wiki/Monad_%28category_theory%29).
This can hardly be regarded as a willfully abstruse activity; identifying and naming existing patterns, especially if the name is already out there, is part of a (good) computer programmer's toolkit, right? http://en.wikipedia.org/wiki/Design_Patterns
Besides, that whole post just gave me the impression that you haven't done the minimum necessary to understand Haskell programs. You claim that there is no immediate visual grouping of the expressions, yet you seem to gloss over all the pattern-based definitions of functions.
Now, if you find using such lengthy sequences of patterns to define your functions unsavory, you can use case expressions, which... require indentation for their cases.
On most respects, it just comes down to the fact that Haskell is different, and it takes a bit of reading to accustom oneself to it.
edit: It was a tad too defensive before. Sorry 'bout that.
Google please fix this mess, elegantly.
Now those being on the spotlight more than twice please consider upgrading your account or adding a banner at the bottom of your site "indestructibly hosted on app engine" to continue free riding.
In Python, every object is an example of a monad. It has two possible values: None and anything_else.
Is no more true then, "It has two possible values: 'Hello, I am a sexy bear' and anything_else."[1] The caveat is the bottom value, which represents something akin to an exception which wasn't handled, that your program is now in an undefined state and now will promptly crash if you use the 'bad' part.
Well, in Python every reference may be of any type. None is just another object, there's nothing particularly special about it.
In languages like Java, null has special semantics that no other value has. In Python, there's nothing particularly special about None, it's simply used as a convenient sentinel by convention. I could easily write Python code that returns the literal string "This value is empty" as the sentinel instead, something that doesn't apply to languages like Java.
And due to Python's runtime types, it wouldn't matter either way, because even if None only existed within the context of a Maybe type, you could still just apply any operation on it and have crash at the first method call on it.
It is true that None is implemented like any other object (whther the value of 5 or an instance of my own class), but it still maintains special semantics.
Edit: strange downvoting here :/. The parent has a point in the sense that None is just another object in Python. However, it's a singleton with a special meaning (by convention). Consequently, the parent used a false analogy.
WAKE UP SHEEPLE.
Seriously, reading monad explanations is like struggling in quicksand of abstraction. Only by making it concrete in working code did I make any progress.
If I had to pick the most tractable monad, I would try the Maybe monad, use it for map lookups in Haskell's Data.Map, since its something you would commonly do with a Python dictionary anyway.
I read about the first two on the typeclassopedia page, and was somewhat unsatisfied with its explanation of monads. I then went to Learn You a Haskell's page on all three of these and skipped to monads, and I was happy with it. I bet you could just read about all three on the Learn You page, the guy does a great job.
People who are confused should definitely check it out.
Yes, it's a bit long, but it actually works, and, what's more, unlike the vast majority of monad tutorials written by someone who just sort of half figured out the concept half-an-hour ago (but they really didn't), it is also correct. This is one of the major root causes behind people's general inabilities to "understand monads after reading tons of tutorials", the tutorials are not only generally not very good but often wrong and mutually contradictory.
http://blog.sigfpe.com/2006/08/you-could-have-invented-monad...
It starts from concrete examples, lets you write a few exercises, then it shows you the common pattern behind those examples.
def pymon(f, v = None): if v: return f(v)
and I just can't keep reading. Why would you do that? What reason would you have for not just calling f directly?
In Haskell, you don't have to. If your function takes a String as an argument, for instance, the compiler will ensure that it never receives a null instead. It will always receive an actual String, because normal types are non-nullable in Haskell. You have to explicitly wrap a type in a Maybe if you want to add nullability, and `Maybe String` is a completely different type from `String`, so the type checker can and will enforce that they don't improperly intermingle.
> and b) not call f with invalid parameters?
Same story here. If f takes a String argument, you can't (deliberately or by accident) pass it a Maybe String instead, or your code simply won't compile. The strong static type system in Haskell eliminates the mental burden of having to always watch out for nulls and handle them as a special case.
That may not seem like much of a burden if you're accustomed to languages like Python, Java, etc. where nullability is the default. But think about how much time you've spent dealing with NullPointerExceptions (or the equivalent in your language of choice) and imagine how much nicer it would be if the language itself could simply eliminate the possibility of them ever occurring in the first place. Well, thanks to its type system, Haskell can do that.
The example snippet was likely just an illustrative example, using Python merely because it's a more widely understood language than Haskell. If he wrote it in Haskell, then not only would it be redundant (since monads are already defined in that language), but it would be less comprehensible to the intended audience of new and non-Haskell programmers.
def f(v):
if v is None: return
# REST OF f stuff
And say you have other functions g, h, i, ... each one of those would also start with the: if v is None: return
portion of the function. This is just a case of DRY.If the code really cares what it's getting, it should not only be checking for None, but also that the arguments it is called with are valid.
I still fail to see why you would write a function like this at all.
Have you ever written validator code for your inputs from a web form? It really is the same pattern, usually implemented via decorator rather than direct
result = validate(f, inputs)
calls, but decorators are just syntactic sugar for that.Or are you really suggesting that every function which may take data from an input source copy and paste the same validation code to the top of that function?
hamon2 f (Just x) = f x
hamon2 f Nothing = Nothing
? The author's definition doesn't seem to typecheck if `f :: a -> Maybe b` (as in `(>>=)`).EDIT: Ah, I see; looking at the blackboard picture suggests that the author has confused `(>>=) :: Monad m => m a -> (a -> m b) -> m b` with `fmap :: Functor m => (a -> b) -> m a -> m b`.
None is just a singleton value. A particular object may be None, or may be 4, or may be whatever. However, you cannot assign a new value to 4, or None, so it isn't accurate to say that any object could have a value of None. It isn't that an object might have something in it, or not. It's that the object might be None, or something other than None, JUST as it might be 4, or something other than 4. That is just how it works in Python's type system. There really is no notion of "has no value". None IS a value. If (for some reason, hopefully a good one) you mean to exclude it and raise an error, you must deal with that in exactly the same way as you would exclude and raise an error on any other particular value you found important.
In Python, it is not true that anything might not have a value. Python itself doesn't even have predicates for "has something in it" or "doesn't have something in it." That might be a common idiom using None by convention, but it's by no means inherent to Python, or necessary. It's really not the same as NULL.
What's true is that any argument (or namespace binding) could have a value of None. OR 4, or a certain dict, or whatever. That's just because Python isn't doing automatic type checking. None does not take any special role which you do not give to it. Its semantics are up for grabs. NOTHING in Python actually forces you to use None to denote "no value." And it is BY DESIGN that Python does not automatically type check everything. YOU must decide whether and how to type check arguments - not at all, by using your own decorators or asserts or some library, or by not using Python. Python isn't a bondage and discipline language. If you don't like that, don't use it, it's just that simple. There's no need to call it embarrassing, or gloat about how you schooled somebody who showed interest in your favorite language.
So. If you use the return value of a function without a return statement, you are asking for a value which may be None, the same as if you had written 'return None'. You bear responsibility for that decision - the same as you bear responsibility for feeding int(4) or a module object to your functions. If you don't like that, then don't do it.
Under normal circumstances, without doing anything special, you will get something like a TypeError if someone (e.g. you) feeds a None to a function not anticipating it.
You only have to handle that in the case where it is vitally important to have a different exception or other behavior. You don't need "six hundred and forty nine thousand two hundred and eighty-eight if statements" unless you are being needlessly compulsive to begin with, in a vain effort to emulate a bondage-and-discipline language. Python isn't even supposed to be a bondage-and-discipline language, so it's no revelation that it isn't good at that. Trying to use it that way is just doing it wrong.
(n.b. Instead of using imperative if-statements, you might prefer: f(v) if v else None, for those instances where it's even necessary).
Dear Author:
It's cool that you are proud of yourself, but in this case I think your advocacy is being hurt by your ego. I have never gotten the impression that Haskell programmers in general are smug and boastful. If YOU don't want to be thought of as smug and boastful, then don't publish articles like this where you are smugly bragging about who you schooled.