Created
October 23, 2014 23:50
-
-
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!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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