Domanda

I can see how you'd use a Monad is Haskell for IO - to create a container around the computation for this operation. It makes sense that you can use Monads to 'plug computations together' - as you would compose functions for a dataflow operation.

What I'm only just grokking is that you can use Monads for Control Flow. Now I understand Control flow to be about Sequence, Selection and Iteration. Now I'm comfortable with higher order functions like map, foldl, filter and zipwith/mapcat to perform operations on lists.

My question is - can I do sequence, selection and iteration with monads in order to achieve control flow? (Happy for an answer in Haskell, Scala or Clojure)

È stato utile?

Soluzione

For sequencing in Haskell, you have the functions >>= and sequence:

(>>=) :: Monad m => m a -> (a -> m b) -> m b
sequence :: Monad m => [m a] -> m [a]

The >>= or bind function takes a monadic action, extracts the value from it and feeds it into a function that returns a new monadic action. The sequence function takes a list of monadic actions of the same type and executes all of them, aggregating their results and wrapping it as a single action.

For iteration you have mapM and forM (forM = flip mapM)

mapM :: Monad m => (a -> m b) -> [a] -> m [b]

The mapM and forM functions are for applying a function that returns an action to each element in a list, aggregating the results as a single action.

For selection, I assume you mean conditionals, which are implemented in Haskell as just if-the-else expressions. They can be used directly in monadic expressions the same as they can be used in pure expressions. However, you can also use certain monads for performing choices or at least handling errors. The easiest to grok is the Maybe monad:

data Maybe a = Nothing | Just a

instance Monad Maybe where
    return a = Just a
    (Just a) >>= f = f a
    Nothing  >>= f = Nothing

It has a very simple implementation. Essentially, if you try to sequence a Nothing into anything else, it will return Nothing every time. This gives you the notion of short-circuited failure:

lookup :: Eq a => a -> [(a, b)] -> Maybe b
-- Looks up a value in a key-value association list

myFunc :: Int -> [(String, Int)] -> Maybe Int
myFunc mult assocList = do
    i <- lookup "foo" assocList
    j <- lookup "bar" assocList
    return $ i * mult + j

Here, if the lookup for "foo" fails, the myFunc immediately returns Nothing. Similarly if the lookup for "bar" fails, myFunc immediately returns Nothing. It's only when both lookups succeed does myFunc do any computation. This provides a sort of "error handling". There's a similar monad Either a

data Either a b = Left a | Right b

instance Monad (Either a) where
    return a = Right a
    (Right a) >>= f = f a
    (Left a)  >>= f = Left a

that works very much the same, except the "failure" value can carry some context, such as a string error message or the state of the computation at the point of failure.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top