runXXXTpattern and how it’s a common gateway for us to use certain monads from the rest of our code. But sometimes it also helps to go back to the basics. I actually went a long time without really grasping how to use a couple basic monads. Or at the very least, I didn’t understand how to use them as monads.
dosyntax is one of the keys to understanding how to actually use monads. The bind operator makes it hard to track where your arguments are. Do syntax keeps the structure clean and allows you to pass results with ease. Let’s see how this works with
IO, the first monad a lot of Haskellers learn. Here’s an example where we read the second line from a file:
readLineFromFile :: IO StringreadLineFromFile = do handle <- openFile “myFile.txt” ReadMode nextLine <- hGetLine handle secondLine <- hGetLine handle _ <- hClose handle return secondLine
IOfunctions, we can start to see the general pattern of do syntax. Let’s replace each expression with its type:
openFile :: FilePath -> IOMode -> IO HandlehGetLine :: Handle -> IO StringhClose :: Handle -> IO ()return :: a -> IO a readLineFromFile :: IO StringreadLineFromFile = do (Handle) <- (IO Handle) (String) <- (IO String) (String) <- (IO String) () <- (IO ()) IO String
<-. Then it has an expression of
IO aon the right side, which it assigns to a value of
aon the left side. The last line’s type then matches the final return value of this function. What’s important now is to recognize that we can generalize this structure to ANY monad:
monadicFunction :: m cmonadicFunction = do (_ :: a) <- (_ :: m a) (_ :: b) <- (_ :: m b) (_ :: m c)
Maybemonad, we can use it and plug that in for
myMaybeFunction :: a -> Maybe a monadicMaybe :: a -> Maybe amonadicMaybe x = do (y :: a) <- myMaybeFunction x (z :: a) <- myMaybeFunction y (Just z :: Maybe a)
IO, this context is that the computation might interact with the terminal or network. For
Maybe, the context is that the computation might fail.
x. On each turn, we can either subtract one, add one, or keep the number the same. We want to know all the possible results after 5 turns, and the distribution of the possibilities. So we start by writing our non-deterministic function. It takes a single input and returns the possible game outputs:
runTurn :: Int -> [Int]runTurn x = [x - 1, x, x + 1]
runGame :: Int -> [Int]runGame x = do (m1 :: Int) <- (runTurn x :: [Int]) (m2 :: Int) <- (runTurn m1 :: [Int]) (m3 :: Int) <- (runTurn m2 :: [Int]) (m4 :: Int) <- (runTurn m3 :: [Int]) (m5 :: Int) <- (runTurn m4 :: [Int]) return m5
[Int]. Then on the left side, we get our
Intout. So each of the
mexpressions represents one of the many solutions we'll get from
runTurn. Then we run the rest of the function imagining we’re only using one of them. In reality though, we’ll run them all, because of how the list monad defines its bind operator. This mental jump is a little tricky. And it’s often more intuitive to just stick to using
whereexpressions when we do list computations. But it's cool to see patterns like this pop up in unexpected places.
Readermonad. It encapsulates the context of having a single argument we can pass to different functions. But it’s not defined in the same way as
Reader. When I tried to grok the definition, it didn’t make much sense to me:
instance Monad ((->) r) where return x = \_ -> x h >>= f = \w -> f (h w) w
returndefinition makes sense. We’ll have a function that takes some argument, ignore that argument, and give the value as an output. The bind operator is a little more complicated. When we bind two functions together, we’ll get a new function that takes some argument
w. We’ll apply that argument against our first function (
(h w)). Then we’ll take the result of that, apply it to
f, and THEN also apply the argument
wagain. It’s a little hard to follow.
myFunctionMonad :: a -> (x, y, z)myFunctionMonad = do x <- :: a -> b y <- :: a -> c z <- :: a -> d return (x, y, z)
Intand use a few different functions that can take an
Int. Here’s what we’ll get:
myFunctionMonad :: Int -> (Int, Int, String)myFunctionMonad = do x <- (1 +) y <- (2 *) z <- show return (x, y, z)
>> myFunctionMonad 3(4, 6, "3")>> myFunctionMonad (-1)(0, -2, "-1")
showit on the third line. And we do this all without explicitly stating the argument! The tricky part is that all your functions have to take the input argument as their last argument. So you might have to do a little bit of argument flipping.