5 ms·
I don't understand how people keep failing to understand monads. Maybe it's the line noise operator `>>=' ? Lets define a simple type, `Maybe', which can store
by jmillikin 17y ago
I don't understand how people keep failing to understand monads. Maybe it's the line noise operator `>>=' ?
Lets define a simple type, `Maybe', which can store either a value or an absence of value:
data Maybe a = Just a | Nothing
deriving (Show)
And define a function which compares a number against a limit, and returns `Nothing' if the value is out of range:
clamp :: Ord a => a -> a -> Maybe a
clamp limit x = if x > limit then Nothing else Just x
Easy enough. Now, how should the number be compared against multiple limits?
One way is to just brute-force it:
multiclamp :: Ord a => a -> a -> a -> Maybe a
multiclamp lim1 lim2 x = case clamp lim1 x of
Nothing -> Nothing
Just x' -> clamp lim2 x'
But this is ugly. Lets use monads to simplify it.
First, take a look at the core Monad operations. I'm going to use `build' and `bind' instead of `return' and `>>=', because they're probably more familiar to most readers.
class Monad m where
build :: a -> m a
bind :: m a -> (a -> m b) -> m b
Next, we'll define an instance of the `Monad' typeclass for `Maybe'. It will use the same semantics as the `multiclamp' example -- that is, if the first test returns `Nothing', the entire computation will return `Nothing':
instance Monad Maybe where
build x = Just x
bind Nothing _ = Nothing
bind (Just x) f = f x
Using this definition, `multiclamp' can be written thus:
multiclamp2 :: Ord a => a -> a -> a -> Maybe a
multiclamp2 lim1 lim2 x = bind
(clamp lim1 x)
(\x' -> clamp lim2 x')
Although ugly, this notation is sufficiently generic that the compiler can generate it from an imperative notation. Lets look at two more examples. First, `multiclamp2' written using "real" monads. Notice that this is exactly the same as before, except `bind' is an operator named `>>=':
multiclamp3 :: Ord a => a -> a -> a -> Maybe a
multiclamp3 lim1 lim2 x =
(clamp lim1 x) >>=
(\x' -> clamp lim2 x')
And finally, using the imperative-style `do' notation. Note that although this is imperative, it is not impure -- this is semantically equal to `multiclamp3':
multiclamp4 :: Ord a => a -> a -> a -> Maybe a
multiclamp4 lim1 lim2 x = do
x' <- clamp lim1 x
clamp lim2 x'
This is, obviously, a superficial example -- you'd never see such verbose code in code written by an experienced programmer. But the verbosity helps to visualize what the compiler is doing.
For completeness, here's a "cleaner" version, which takes advantage of currying and the combinator `.':
multiclamp5 :: Ord a => a -> a -> a -> Maybe a
multiclamp5 lim1 lim2 = (clamp lim2 =<<) . clamp lim1
- deleted 17y ago[deleted]
- ericwaller 17y agoSo, to see if I'm understanding: is the purpose of the Maybe monad to abstract the concept of "evaluate to Nothing if the previous expression evaluated to nothing?" Also, is 'build'/'return' being used implicity somewhere?
- jmillikin 17y agois the purpose of the Maybe monad to abstract the concept of "evaluate to Nothing if the previous expression evaluated to nothing? Exactly. It's a data type for short-circuiting. Also, is 'build'/'return' being used implicity somewhere? Not in this example -- I thought about it, but ultimately decided additional changes between examples would just confuse things. It's worth including in the definition because it's one of the two fundamental operations. In real code, it's mostly used when the exact monad being used isn't specified. For example, here's a function which runs a monadic computation until it returns `False'. The result is `()', sort of a Haskell void type. runUntil :: Monad m => m Bool -> m () runUntil comp = loop where loop = do x <- comp if x then loop else return () Not useful for `Maybe', but stateful monads like `State', `Get', `Put', or `IO' could all be used on it reasonably.
- torial 17y agoI think for those of us that have a C language family background, parallel examples in both C (or C# or Java or Python) and Haskell would be very beneficial! We have two hurdles to jump: the concept of monad AND the syntax.
- jmillikin 17y agoC and Java can't represent monads at all, since they don't support closures. C# and Python can, sort of, but their syntax and object-oriented semantics make it somewhat difficult. I don't know C# well, and Python needs a lot of magic[1] to get reasonable-looking code. Here's a Python example. It's not a direct translation, but is close enough that you should be able to figure out the original. I've included the Haskell code in comments. import abc # class Monad m where # build :: a -> m a # bind :: m a -> (a -> m b) -> m b class Monad (metaclass = abc.ABCMeta): @classmethod def build (cls, x): raise NotImplementedError def bind (self, f): raise NotImplementedError # data Maybe a = Just a | Nothing # deriving (Show) class Maybe (Monad): def __init__ (self, x): self.__x = x def __repr__ (self): if self is Nothing: return "Nothing" return ("Just %r" % self.__x) # instance Monad Maybe where # build x = Just x # # bind Nothing _ = Nothing # bind (Just x) f = f x @classmethod def build (cls, x): return Just (x) def bind (self, f): if self is Nothing: return Nothing return f (self.__x) def Just (x): return Maybe (x) Nothing = Maybe (None) # clamp limit x = if x > limit then Nothing else Just x def clamp (limit, x): if x > limit: return Nothing return Just (x) # multiclamp2 lim1 lim2 x = bind # (clamp lim1 x) # (\x' -> clamp lim2 x') def multiclamp2 (lim1, lim2, x): return clamp (lim1, x).bind (lambda x2: clamp (lim2, x2)) [1] http://www.valuedlessons.com/2008/01/monads-in-python-with-nice-syntax.html http://www.valuedlessons.com/2008/01/monads-in-python-with-n...