Purifying Code with Algebra
James Earl Douglas
September 16, 2016
Motivation
Side effects are the worst.
getNum :: IO Int
getNum = do
removeDirectoryRecursive "/"
return 42
Objective
Use a GADT to get IO
out of function types.
Side effects and IO
A function might have side effects if it uses the
IO
type class.
IO
with a side effect
getNum2 :: IO Int
getNum2 = read <$> getLine
Returns whatever value the user feels like providing
IO
without a side effect
getNum3 :: IO Int
getNum3 = return 42
Always returns 42
Side effects and IO
A function can't have side effects if it doesn't use
IO
.
A side effect without IO
getNum4 :: Int
getNum4 = read <$> getLine
This will not compile
Basic console IO
Let's start with a couple of library functions:
getLine :: IO String
putStr :: String -> IO ()
LameCalc
calculator :: IO ()
calculator = do
putStr "Enter a number: "
x <- getLine
putStr "Enter another one: "
y <- getLine
let z = show (read x * read y)
putStr $ x ++ " * " ++ y ++ " = " ++ z ++ "\n"
Abstract the effects
data ConsoleIO a where
PutStr :: String -> ConsoleIO ()
GetLine :: ConsoleIO String
Combinators as data types
data ConsoleIO a where
PutStr :: String -> ConsoleIO ()
GetLine :: ConsoleIO String
Pure :: a -> ConsoleIO a
Ap :: ConsoleIO (a -> b) -> ConsoleIO a -> ConsoleIO b
Bind :: ConsoleIO a -> (a -> ConsoleIO b) -> ConsoleIO b
Combinators as instances
instance Functor ConsoleIO where
fmap f s = Bind s (\a -> Pure $ f a)
instance Applicative ConsoleIO where
pure a = Pure a
(<*>) f a = Ap f a
instance Monad ConsoleIO where
(>>=) a mb = Bind a mb
Interpret the effects
class Effect x where
runEffect :: x a -> IO a
instance Effect ConsoleIO where
runEffect (Pure a) = return a
runEffect (Ap f a) = (runEffect f) <*> (runEffect a)
runEffect (Bind s fs) = (runEffect s) >>= (\a -> runEffect (fs a))
runEffect (PutStr s) = putStr s
runEffect GetLine = getLine
Purify calculator
calculator1 :: IO ()
calculator1 = do
putStr "Enter a number: "
x <- getLine
putStr "Enter another one: "
y <- getLine
let z = show (read x * read y)
putStr $ x ++ " * " ++ y ++ " = " ++ z ++ "\n"
PureCalc
calculator2 :: ConsoleIO ()
calculator2 = do
PutStr "Enter a number: "
x <- GetLine
PutStr "Enter another one: "
y <- GetLine
let z = show (read x * read y)
PutStr $ x ++ " * " ++ y ++ " = " ++ z ++ "\n"
Looks just like calculator1
, but doesn't allow for
side effects
Demo
main :: IO ()
main = do
putStr "\nRunning calculator...\n"
calculator
putStr "\nRunning calculator2...\n"
runEffect calculator2
Usage
These slides are literate Haskell. Try running them with Codedown:
$ curl https://earldouglas.com/talks/gadt-io/slides.md | codedown haskell > gadt-io.hs
$ runhaskell gadt-io.hs
Running calculator...
Enter a number: 6
Enter another one: 7
6 * 7 = 42
Running calculator2...
Enter a number: 6
Enter another one: 7
6 * 7 = 42