|
|
Line 1: |
Line 1: |
| <blockquote>
| | #REDIRECT [[IO_inside]] |
| The <code>IO</code> type serves as a tag for operations (actions) that interact with the outside world. The <code>IO</code> type is abstract: no constructors are visible to the user. [...]
| |
| | |
| <small>[https://www.haskell.org/definition/haskell2010.pdf The Haskell 2010 Report] (page 95 of 329).</small>
| |
| </blockquote>
| |
| | |
| Instead of that conventional approach:
| |
| | |
| <haskell>
| |
| data IO -- abstract
| |
| | |
| getChar :: IO Char
| |
| putChar :: Char -> IO ()
| |
| ⋮
| |
| </haskell>
| |
| | |
| describe <code>IO</code> using other types, ones with no visible constructors:
| |
| | |
| <haskell>
| |
| data (->) a b -- abstract
| |
| data OI -- also abstract
| |
| | |
| type IO a = OI -> a
| |
| getChar :: OI -> Char -- an I/O action
| |
| putChar :: Char -> (OI -> ()) -- a function with one parameter, whose result is an I/O action
| |
| ⋮
| |
| </haskell>
| |
| <sub> </sub>
| |
| | |
| == Starting up ==
| |
| | |
| <code>Main.main</code> is also an I/O action:
| |
| | |
| <haskell>
| |
| main :: OI -> ()
| |
| </haskell>
| |
| | |
| Therefore an internal subroutine in the Haskell implementation provides each running program with an initial <code>OI</code> value. However, most programs will require more than just one:
| |
| | |
| <haskell>
| |
| getLine :: OI -> [Char]
| |
| getLine u = let !(u1, u2) = partOI u in
| |
| let !c = getChar u1 in
| |
| if c == '\n' then
| |
| []
| |
| else
| |
| let !cs = getLine u2
| |
| in c:cs
| |
| | |
| putLine :: [Char] -> OI -> ()
| |
| putLine (c:cs) u = let !(u1, u2) = partOI u in
| |
| let !_ = putChar c u1 in
| |
| putLine cs u2
| |
| putLine [] u = putChar '\n' u
| |
| </haskell>
| |
| | |
| So another I/O action is needed in order to access that same internal subroutine from Haskell:
| |
| | |
| <haskell>
| |
| partOI :: OI -> (OI, OI)
| |
| </haskell>
| |
| | |
| If more than two new <code>OI</code> values are needed:
| |
| | |
| <haskell>
| |
| partsOI :: OI -> [OI]
| |
| partsOI u = let !(u1, u2) = partOI u in u1 : partsOI u2
| |
| </haskell>
| |
| | |
| But why are these abstract <code>OI</code> values needed at all - what purpose do they serve?
| |
| | |
| == Actions and functions ==
| |
| | |
| Looking more closely at <code>getLine</code>, <code>putLine</code> and <code>partsOI</code> reveals an interesting fact:
| |
| | |
| * each <code>OI</code> value is only used once (if at all).
| |
| | |
| Why is this important? Because in Haskell, functions have their basis in mathematics. That imposes certain requirements on function, including this one:
| |
| | |
| * if a function's result changes, it is <b>only</b> because one or more of it's arguments has changed.
| |
| | |
| If they're always used with different <code>OI</code> values, then I/O actions can be used like functions:
| |
| | |
| <haskell>
| |
| partsOI :: OI -> [OI]
| |
| partsOI = unfoldr (Just . partOI)
| |
| </haskell>
| |
| | |
| even if they're defined using subroutines:
| |
| | |
| <haskell>
| |
| foreign import "oi_part" partOI :: OI -> (OI, OI)
| |
| foreign import "oi_getchar" getChar :: OI -> Char
| |
| foreign import "oi_putchar" putChar :: Char -> OI -> ()
| |
| </haskell>
| |
| | |
| The need for an <code>OI</code> value also helps to prevent I/O actions from being used as subroutines:
| |
| | |
| <haskell>
| |
| trace :: [Char] -> a -> a
| |
| trace msg x = case putLine msg of !_ -> x -- how is this supposed to work?
| |
| </haskell>
| |
| | |
| == Monadic actions ==
| |
| | |
| The monadic interface:
| |
| | |
| <haskell>
| |
| instance Monad ((->) OI)
| |
| return x = \ u -> let !_ = partOI u in x
| |
| m >>= k = \ u -> let !(u1, u2) = partOI u in
| |
| let !x = m u1 in
| |
| let !y = k x u2 in
| |
| y
| |
| </haskell>
| |
| | |
| allows <code>getLine</code> and <code>getLine</code> to be defined more compactly:
| |
| | |
| <haskell>
| |
| getLine :: OI -> [Char]
| |
| getLine = do c <- getChar
| |
| if c == '\n' then
| |
| return []
| |
| else
| |
| do cs <- getLine
| |
| return (c:cs)
| |
| | |
| putLine :: [Char] -> OI -> ()
| |
| putLine [] = putChar '\n'
| |
| putLine (c:cs) = putChar c >> putLine cs
| |
| </haskell>
| |
| | |
| and conceals the use of all those <code>OI</code> values. But not all definitions will benefit from being monadic:
| |
| | |
| <haskell>
| |
| partsOI :: OI -> [OI]
| |
| partsOI = do (u1, u2) <- partOI; return (u1 : partsOI u2)
| |
| </haskell>
| |
| | |
| == Further reading ==
| |
| | |
| * [[Merely monadic]] provides more information about Haskell's implementation of the monadic interface.
| |
| | |
| * For those who prefer it, John Launchbury and Simon Peyton Jones's [https://launchbury.blog/wp-content/uploads/2019/01/state-in-haskell.pdf State in Haskell] explains the [[Evaluation order and state tokens|state-passing]] approach currently in widespread use.
| |
| | |
| [[Category:Tutorials]]
| |