Skip to content

Instantly share code, notes, and snippets.

@ion1
Last active August 30, 2020 08:04
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ion1/7154691 to your computer and use it in GitHub Desktop.
Save ion1/7154691 to your computer and use it in GitHub Desktop.
What is inside Haskell IO?

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.

#!/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")]
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"
{-# 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