Last active

Embed URL

HTTPS clone URL

SSH clone URL

You can clone with HTTPS or SSH.

Download Gist

What is inside Haskell IO?

View 0README.md

What is inside Haskell IO?

<lambdabot> shachaf says: getLine :: IO String contains a String in the same
            way that /bin/ls contains a list of files

There are multiple ways IO could be implemented internally. Here’s a demonstration of one.

MiniIO (below) implements an ADT of arbitrarily chosen primitives that represent I/O operations and a way to combine them. One can create, evaluate and manipulate MiniIO values without causing any side effects to occur. That is what example in Main.hs does. The same applies to real IO values in Haskell: you can create a big list of print "hello"s and later pick which ones to actually execute.

You can have an IO action executed by assigning it to main. If the actual IO is implemented like MiniIO, the runtime system will run an interpreter against the value main and execute the appropriate impure effects. This is simulated by running runMiniIO against example. runMiniIO doesn’t actually contain impurity but instead simulates standard input and output as pure strings and a trivial filesystem as a pure map from filename to contents.

Another hypothetical implementation of Haskell IO might be based on:

data IO a = IO MachineCode

This one highlights how an IO String does not contain a String but a description of how to generate one. GHC uses yet another implementation (which doesn’t involve interpreting an ADT in runtime). The semantics and the API from the point of view of you the programmer are the same for all of them.

View 0README.md
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
#!/usr/bin/env runhaskell
 
module Main (main, example) where
 
import Prelude hiding (getLine, putStr, putStrLn, print, readFile, writeFile)
import qualified Prelude
 
import Data.Map (Map)
import qualified Data.Map as Map
 
import MiniIO
 
main :: IO ()
main = Prelude.putStr stdout
where ((), stdout) = runMiniIO (MiniIOState stdin files) example
 
example :: MiniIO ()
example = do
putStr =<< readFile "hello"
 
putStrLn "What does your mother call you?"
name <- noisyGetLine
putStrLn ("O hai, " ++ name ++ " (if that is your real name).")
putStrLn "To what file shall I write?"
filename <- noisyGetLine
writeFile filename ("Name (supposedly): " ++ name ++ "\n")
 
putStrLn ("Now the file " ++ show filename ++ " contains:")
print =<< readFile filename
 
where noisyGetLine = do s <- getLine; putStrLn ("> " ++ s); return s
 
stdin :: String
stdin = unlines ["John User", "hello"]
 
files :: Map String String
files = Map.fromList [("hello", "Hello world!\n")]
View 0README.md
1 2 3 4 5 6 7 8
Hello world!
What does your mother call you?
> John User
O hai, John User (if that is your real name).
To what file shall I write?
> hello
Now the file "hello" contains:
"Name (supposedly): John User\n"
View 0README.md
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TemplateHaskell #-}
 
module MiniIO
( MiniIO, MiniIOState (..)
, getLine, putStr, putStrLn, print, readFile, writeFile
, runMiniIO
) where
 
import Prelude hiding (getLine, putStr, putStrLn, print, readFile, writeFile)
 
import Control.Lens
import Control.Monad.Operational
import Control.Monad.Trans.RWS
import Data.Map (Map)
 
type MiniIO = Program MiniIOPrim
 
-- | IO primitives
data MiniIOPrim a where
GetLine :: MiniIOPrim String
PutStr :: String -> MiniIOPrim ()
ReadFile :: String -> MiniIOPrim String
WriteFile :: String -> String -> MiniIOPrim ()
 
getLine :: MiniIO String
getLine = singleton GetLine
 
putStr :: String -> MiniIO ()
putStr = singleton . PutStr
 
putStrLn :: String -> MiniIO ()
putStrLn = putStr . (++ "\n")
 
print :: Show a => a -> MiniIO ()
print = putStrLn . show
 
readFile :: String -> MiniIO String
readFile = singleton . ReadFile
 
writeFile :: String -> String -> MiniIO ()
writeFile name contents = singleton (WriteFile name contents)
 
data MiniIOState =
MiniIOState { _mioInput :: String -- ^ Standard input
, _mioFiles :: Map String String -- ^ A “filesystem”
}
deriving (Eq, Ord, Show, Read)
makeLenses ''MiniIOState
 
-- | An interpreter for the MiniIO ADT
runMiniIO :: MiniIOState -> MiniIO a -> (a, String)
runMiniIO st mio =
case runRWS (interpretWithMonad runMiniIO' mio) () st of
~(result, _, output) -> (result, output)
 
runMiniIO' :: MiniIOPrim a -> RWS () String MiniIOState a
runMiniIO' GetLine = mioInput %%= breakLine
runMiniIO' (PutStr s) = tell s
 
runMiniIO' (ReadFile name) = use (mioFiles . ix name)
runMiniIO' (WriteFile name s) = mioFiles . at name ?= s
 
breakLine :: String -> (String, String)
breakLine = (_2 %~ dropNewline) . break (=='\n')
where dropNewline ('\n':xs) = xs
dropNewline xs = xs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.