4 ms·
One thing that might highlight the value here is to use a reduced set of effects. Here's (in Haskell, sorry) the "teletype monad" -- set of commands da
by tel 12y ago
One thing that might highlight the value here is to use a reduced set of effects. Here's (in Haskell, sorry) the "teletype monad"
-- set of commands
data Teletype = Write String x | Read (String -> x) deriving Functor
-- monadic data structure
data TeletypeM a = Pure a | Effect (Teletype (TeletypeM a))
-- monad instance
instance Monad TeletypeM where
return = Pure
m >>= k = case m of
Pure a -> k a
Effect tt -> Effect (fmap (>>= k) tt)
-- API
write :: String -> TeletypeM ()
write s = Effect (Write s ())
read :: TeletypeM String
read = Effect (Read (\s -> s))
Now using this we can write "effectful" teletype programs. For instance, the "echo doubler"
echoTwice :: TeletypeM ()
echoTwice = forever $ do
line <- read
write line
write line
Finally, we can "interpret this into IO", which is to say give it a side-effecting semantics
runTeletypeMIO :: TeletypeM a -> IO a
runTeletypeMIO m = case m of
Pure a -> return a
Effect e -> case e of
Write s next -> do
putStrLn s
runTeletypeMIO next
Read cont -> do
line <- readLine
let next = cont line
runTeletypeMIO next
This is nice and feels a lot like "pushing the toothpaste around", but we can take advantage of its pure structure for other purposes. For instance, here's a delimited interpreter which only allows it to take a certain number of "steps"
runTTDelimited :: Int -> TeletypeM () -> IO ()
runTTDelimited n m
| n <= 0 = return ()
| otherwise = case m of
Pure a -> return a
Effect e -> case e of
Write s next -> do
putStrLn s
runTTDelimited (n-1) next
Read cont -> do
line <- readLine
let next = cont line
runTTDelimited (n-1) next
And here's a completely pure interpreter version of the above which also takes its input from a pre-arranged "script"
runTTPure :: Int -> TeletypeM () -> ([String] -> [String])
runTTPure n m input = reverse (go n m input []) where
go n m input acc
| n <= 0 = acc
| otherwise = case m of
Pure a -> acc
Effect e -> case e of
Write s next -> go (n-1) next input (s : acc)
Read cont -> case input of
[] -> error "ran out of input"
(s : ss) -> go (n-1) (cont s) ss acc
All of these can work off of the same TeletypeM values.