Skip to content

Instantly share code, notes, and snippets.

@seanparsons
Created October 23, 2014 23:50
Show Gist options
  • Save seanparsons/c8a44e5fa9d70193f378 to your computer and use it in GitHub Desktop.
Save seanparsons/c8a44e5fa9d70193f378 to your computer and use it in GitHub Desktop.
Monads: The Why, the What For, and especially the It's Like X, But!
-- To run this file you just need the ghc application from the Haskell Platform bundle
-- With that on a command line run the following (assuming the code is in Monads.hs):
-- ghc Monads.hs && ./Monads
-- Imports needed for later:
import System.IO
-- So pretty much every language has this concept:
-- doThingA();
-- doThingB();
-- If that wasn't hugely clear, it was the concept of calling one bit of code
-- after another bit of code. Which in most languages is done by having them on
-- separate lines or separating them with semicolons etc. Haskell doesn't do
-- this at all, it does, but not in that way which pretty much everything else does:
doThings1 = do
putStrLn "Doing thing A."
putStrLn "Doing thing B."
-- The "do" is the indication that something slightly different is going on here.
-- We should actually write it slightly longer hand.
doThings2 = do
_ <- putStrLn "Doing thing A."
_ <- putStrLn "Doing thing B."
return ()
-- doThings1 and doThings2 are the same bit of code, but the first employs a little
-- syntactic sugar to make things look a bit nicer.
-- If we look at the definition of putStrLn(http://hackage.haskell.org/package/base-4.7.0.1/docs/Prelude.html#v:putStrLn)
-- it has a definition that looks like this:
-- putStrLn :: String -> IO ()
-- So given a String it returns an IO of Unit.
-- In this situation the IO is the monad we're operating on, IO being a way of dealing
-- with things that may go bang or that would otherwise breach
-- referential transparency(http://en.wikipedia.org/wiki/Referential_transparency_%28computer_science%29).
-- The typeclass Monad has a method >>=, otherwise known as bind, which is what's invoked
-- "between the lines" of the do notation.
-- The signature of this method (slightly simplified) is this:
-- (>>=) :: m a -> (a -> m b) -> m b
-- So given a m of a and a function that takes a and returns a m of b, return a m of b.
-- Since do notation is syntactic sugar itself we can remove it:
doThings3 = (putStrLn "Doing thing A.") >>= (\_ -> putStrLn "Doing thing B.") >>= (\_ -> return ())
-- (Note the arrows and underscores between doThings2 and doThings3)
-- In this case we can see that the concept of running one thing after another isn't
-- some special case thing the compiler does, but instead is available for
-- a programmer to employ. With IO this means that instances of IO can be
-- sequenced together before eventually being executed and the failure handling
-- held in one place.
-- This is all well and good, but what does this give us over just good old
-- imperative code?
-- If for example we had a list of user IDs and we wanted to query some database
-- for the usernames of those users we might have some code like this:
listOfUserIds :: [Int]
listOfUserIds = [1, 2, 3]
lookupUser :: Int -> IO String
lookupUser = (\userId -> return ("User" ++ (show userId)))
usernames :: [IO String]
usernames = fmap lookupUser listOfUserIds
-- So lookupUser takes a Int and produces the username in a managed way.
-- But usernames is really unwieldy and we couldn't print out all the names without
-- doing some potentially nasty stuff.
-- Enter the "sequence" function!
-- sequence :: Monad m => [m a] -> m [a]
-- This allows us to transform a list of m of a into a m of list of a.
-- So in our case we can print out those usernames like so:
printOutUsernames = do
names <- sequence usernames
print names
-- Outputs: ["User1","User2","User3"]
-- If any of the calls to lookup a user fails then the whole result fails.
-- For those so inclined there's are versions of sequence that have much
-- wider type constraints allowing this to be done on types other than
-- List for example.
-- Also since List itself is a Monad we can do interesting things like this:
sequenceAListOfLists = print (sequence [[1,2],[3,4]])
-- Outputs: [[1,3],[1,4],[2,3],[2,4]]
-- One useful aspect of this as well is that code might not need to be expressed
-- in terms of a particular Monad, but like sequence can be expressed
-- in terms of all Monads.
-- Everything runs here:
main = do
doThings1
doThings2
doThings3
printOutUsernames
sequenceAListOfLists
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment