Difference between revisions of "IO, partible-style"

From HaskellWiki
Jump to navigation Jump to search
m (Small change of formatting)
(Page superseded by "IO in action")
Tag: New redirect
 
(17 intermediate revisions by the same user not shown)
Line 1: Line 1:
  +
#redirect [[IO in action]]
   
  +
[[Category:Pages to be removed]]
<i><code>IO</code> is the [monadic type] you cannot avoid.</i>
 
<tt>
 
* [https://image.slidesharecdn.com/functionalconf2019-whyishaskellsohard2-191116135003/95/why-is-haskell-so-hard-and-how-to-deal-with-it-53-638.jpg Why Haskell is so HARD? (And how to deal with it)]; Saurabh Nanda.
 
</tt>
 
 
::...but you kept looking anyway, and here you are!
 
 
<i>
 
[...] the input/output story for purely-functional languages was weak and unconvincing, let alone error recovery, concurrency, etc. Over the last few years, a surprising solution has emerged: the monad. I say "surprising" because anything with as exotic a name as "monad" - derived from category theory, one of the most abstract branches of mathematics - is unlikely to be very useful to red-blooded programmers. But one of the joys of functional programming is the way in which apparently-exotic theory can have a direct and practical application, and the monadic story is a good example.
 
</i>
 
<tt>
 
* [https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.13.9123&rep=rep1&type=pdf Tackling the Awkward Squad: monadic input/output, concurrency, exceptions, and foreign-language calls in Haskell], Simon Peyton Jones.
 
</tt>
 
 
::...with that (''ahem'') "joy" leading to more than a few [[Monad tutorials timeline|helpful guides about the topic]] - that monadic interface: it's abstract alright!
 
 
__TOC__
 
<sub> </sub>
 
----
 
 
=== <code>IO</code>, <u>using</u> <code>OI</code> ===
 
 
Our definition of <code>IO</code> is a type synonym:
 
 
<haskell>
 
type IO a = OI -> a
 
</haskell>
 
 
with <code>OI</code> being an abstract [[Plainly partible|partible]] type:
 
 
<haskell>
 
data OI a
 
primitive primPartOI :: OI -> (OI, OI)
 
 
instance Partible OI where
 
part = primPartOI
 
</haskell>
 
 
Like <code>primPartOI</code>, most other primitives for the <code>OI</code> type also accept an <code>OI</code>-value as their last (or only) argument e.g:
 
 
<haskell>
 
primitive primGetChar :: OI -> Char
 
primitive primPutChar :: Char -> OI -> ()
 
 
</haskell>
 
 
Borrowing the running example from Philip Wadler's [https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.91.3579&rep=rep1&type=pdf How to Declare an Imperative]:
 
 
<haskell>
 
echo :: OI -> ()
 
echo u = let !(u1:u2:u3:_) = parts u
 
!c = primGetChar u1 in
 
if c == '\n' then
 
()
 
else
 
let !_ = primPutChar c u2
 
in echo u3
 
</haskell>
 
 
Wadler also provides an [https://www.smlnj.org/sml.html SML] version:
 
 
<pre>
 
val echoML : unit -> unit
 
fun echoML () = let val c = getcML () in
 
if c = #"\n" then
 
()
 
else
 
(putcML c; echoML ())
 
end
 
</pre>
 
 
in which we replace SML's sequencing operator <code>;</code>:
 
 
<pre>
 
val echoML : unit -> unit
 
fun echoML () = let val c = getcML () in
 
if c = #"\n" then
 
()
 
else
 
let val _ = putcML c in
 
echoML ()
 
end
 
end
 
</pre>
 
 
If we compare it to our Haskell version:
 
 
{|
 
|<haskell>
 
echo :: OI -> ()
 
echo u = let !(u1:u2:u3:_) = parts u
 
!c = primGetChar u1 in
 
if c == '\n' then
 
()
 
else
 
let !_ = primPutChar c u2
 
in echo u3
 
 
--
 
</haskell>
 
|<pre>
 
val echoML : unit -> unit
 
fun echoML () =
 
let val c = getcML () in
 
if c = #"\n" then
 
()
 
else
 
let val _ = putcML c in
 
echoML ()
 
end
 
end
 
</pre>
 
|}
 
...we can now see just how similar the two versions of <code>echo</code> really are: apart from the obvious changes of syntax, the Haskell version replaces all use of <code>unit</code>-values with <code>OI</code>-values, and adds an extra call to <code>parts</code> to provide them.
 
 
So there you have it: for the price of some extra calls and bindings, we can have SML-style I/O in Haskell. Furthermore, as SML has been available since 1997, there should be plenty of I/O tutorials to choose from...
 
 
At this point, you may be tempted to try something like:
 
 
<pre>
 
type IO a = () -> a
 
 
primitive might_get_Char :: () -> Char
 
primitive might_put_Char :: Char -> ()
 
 
</pre>
 
 
While this ''might'' work in some situations, it's unreliable in general. Why?
 
 
* ''Short answer'': unlike SML, Haskell's nonstrict evaluation means expressions should be referentially transparent.
 
* ''Long answer'': read section 2.2 (pages 4-5) of Wadler's [https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.91.3579&rep=rep1&type=pdf paper].
 
* ''Longer answer'': read Lennart Augustsson's [https://augustss.blogspot.com/2011/05/more-points-for-lazy-evaluation-in.html More points for lazy evaluation].
 
* ''Extended answer'': read John Hughes's [https://www.cse.chalmers.se/~rjmh/Papers/whyfp.pdf Why Functional Programming Matters].
 
 
But - if after all that - you're still not convinced, then perhaps you'll be happier programming in [https://www.smlnj.org/sml.html SML]...
 
 
 
...you're still here: ''nice!'' &nbsp;Now for a small example - here's <code>interact</code>, using those <code>OI</code>-based definitions:
 
 
<haskell>
 
interact :: (String -> String) -> OI -> ()
 
interact f v = let !(u1, u2) = part v
 
gets = map primGetChar $ parts u1
 
puts s = foldr (\!_ -> id) () $ zipWith primPutChar s $ parts u2
 
in
 
puts $ f $ gets
 
</haskell>
 
<sub> </sub>
 
----
 
=== <u>Some annoyances</u> ===
 
 
* ''Extra parameters and arguments'' - As noted in Paul Hudak and Raman S. Sundaresh's [https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.49.695&rep=rep1&type=pdf On the Expressiveness of Purely Functional I/O Systems], passing around all those <code>OI</code>-values ''correctly'' can be tedious for large definitions.
 
 
* ''Polymorphic references'' - It's been known for a very long time in the SML community that naive declarations for operations using ''mutable references'' breaks type safety:
 
 
:{|
 
|<pre>
 
primitive newPolyRef :: a -> OI -> PolyRef a
 
primitive readPolyRef :: PolyRef a -> OI -> a
 
primitive writePolyRef :: PolyRef a -> a -> OI -> ()
 
 
kah_BOOM u = let …
 
!vehicle = newPolyRef undefined u1
 
!_ = writePolyRef ("0" :: [Char]) u2
 
!crash = readPolyRef vehicle u3
 
burn = 1 :: Int
 
in
 
crash + burn
 
</pre>
 
|}
 
 
:SML's solution is to make all mutable references ''monomorphic'' through the use of dedicated syntax:
 
 
:{|
 
|<pre>
 
let val r = ref (…)
 
 
</pre>
 
|}
 
 
:One alternative for Haskell would be to extend type signatures to support [[Monomorphism by annotation of type variables|monomorphic type-variables]]:
 
 
:<haskell>
 
primitive newIORef :: monomo a . a -> OI -> IORef a
 
primitive readIORef :: monomo a . IORef a -> OI -> a
 
primitive writeIORef :: monomo a . IORef a -> a -> OI -> ()
 
 
{- would be rejected by the extended type system:
 
kah_BOOM u = let !(u1:u2:u3:_) = parts u
 
!vehicle = newIORef undefined u1 -- vehicle :: monomo a . IORef a
 
!_ = writeIORef ("0" :: [Char]) u2 -- vehicle :: IORef [Char]
 
!crash = readIORef vehicle u3 -- vehicle :: IORef [Char] ≠ IORef Int
 
burn = 1 :: Int
 
in
 
crash + burn
 
-}
 
</haskell>
 
 
:In standard Haskell, one of the few places this already occurs (albeit implicitly) is the parameters of a function:
 
 
:<haskell>
 
{- will be rejected by the standard Haskell type system
 
 
ker_plunk f = (f True, f 'b')
 
 
-}
 
</haskell>
 
----
 
=== <u>One solution</u> ===
 
 
* ''Extra parameters and arguments'' - What is needed is a succinct interface to "hide the plumbing" used to pass around <code>OI</code>-values:
 
 
:<haskell>
 
instance Monad ((->) OI) where
 
return x = \u -> case part u of !_ -> x
 
m >>= k = \u -> case part u of
 
(u1, u2) -> case m u1 of
 
!x -> k x u2
 
</haskell>
 
 
* ''Polymorphic references'' - we now make <code>IO</code> into an ''abstract data type'':
 
 
:<haskell>
 
module Abstract.IO
 
(
 
Monad (..),
 
getChar, putChar, …
 
newIORef, readIORef, writeIORef,
 
 
)
 
where
 
 
instance Monad ((->) OI) where
 
return x = \u -> case part u of !_ -> x
 
m >>= k = \u -> case part u of
 
(u1, u2) -> case m u1 of
 
!x -> k x u2
 
 
getChar :: IO Char
 
getChar = primGetChar
 
 
putChar :: Char -> IO ()
 
putChar = primPutChar
 
 
newIORef :: a -> IO (IORef a)
 
newIORef = primNewIORef
 
 
readIORef :: IORef a -> IO a
 
readIORef = primReadIORef
 
 
writeIORef :: IORef a -> a -> IO ()
 
writeIORef = primWriteIORef
 
 
 
-- these are now local, private entities --
 
type IO a = OI -> a
 
 
data OI a
 
primitive primPartOI :: OI -> (OI, OI)
 
 
primitive primGetChar :: OI -> Char
 
primitive primPutChar :: Char -> OI -> ()
 
 
 
data IORef
 
primitive primNewIORef :: a -> OI -> IORef a
 
primitive primReadIORef :: IORef a -> OI -> a
 
primitive primWriteIORef :: IORef a -> a -> OI -> ()
 
 
 
</haskell>
 
 
:With the <code>IO</code> type now made abstract, the only way to use <code>IO</code>-values is by using:
 
 
:* the visible <code>IO</code> operations: <code>getChar</code>, <code>putChar</code>, etc.
 
:* the monadic interface - <code>Monad(return, (>>=), …)</code> (or via Haskell's <code>do</code>-notation).
 
 
:The key here is the type of <code>(>>=)</code>, for <code>IO</code>-values:
 
 
:<haskell>
 
(>>=) :: IO a -> (a -> IO b) -> IO b
 
</haskell>
 
 
:in particular, the type of the second argument:
 
 
::{|
 
|<haskell>
 
(a -> IO b)
 
</haskell>
 
|}
 
 
:...it's a function, so the value it receives will be rendered monomorphic in the function's result (of type <code>IO b</code>).
 
 
:As <code>(>>=)</code> is now the only <code>IO</code> operation which can retrieve a result from an <code>IO</code>-value, mutable references (<code>IORef …</code>) simply cannot be used polymorphically.
 
----
 
=== <u>GHC's solution</u> ===
 
<sub> </sub>
 
<haskell>
 
newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))
 
</haskell>
 
 
...you may have noticed that we've already made liberal use of one Haskell extension - [https://downloads.haskell.org/~ghc/7.8.4/docs/html/users_guide/bang-patterns.html bang-patterns] - and it would be useful to stay as close as possible to standard Haskell, so we'll simplify matters:
 
 
<haskell>
 
newtype IO a = IO (IOState -> (IOState, a)) -- unboxed-tuple replaced by standard one
 
 
type IOState = State# RealWorld
 
</haskell>
 
 
Now to make the changes:
 
 
* ''to the type'' - <code>IOState</code> uses an <code>OI</code>-value:
 
 
:<haskell>
 
newtype IOState = IOS OI
 
</haskell>
 
 
* ''to the I/O-specific operations'' - each one will use the <code>OI</code>-value in the initial state to provide two new <code>OI</code>-values: one to make up the final state; the other being used by the <code>OI</code>-primitive:
 
 
:<haskell>
 
getChar :: IO Char
 
getChar = IO $ \(IOS u) -> let !(u1, u2) = parts u
 
!c = primGetChar u1
 
in (IOS u2, c)
 
 
putChar :: Char -> IO ()
 
putChar c = IO $ \(IOS u) -> let !(u1, u2) = parts u
 
!t = primPutChar c u1
 
in (IOS u2, t)
 
 
-- etc.
 
</haskell>
 
 
* ''to the overloaded operations'' - you've probably seen it all before:
 
 
:<haskell>
 
instance Monad IO where
 
return x = IO $ \!s -> (s, x)
 
IO m >>= k = IO $ \!s -> let !(s', x) = m s
 
!IO w = k x
 
in w s'
 
</haskell>
 
 
:(...if you haven't: it's ye ol' <del>''pass-the-planet''</del> state-passing technique.)
 
 
One aspect which doesn't change is <code>IO</code> and its operations being abstract. In fact, the need is even more pressing: in addition to preventing the misuse of certain <code>OI</code>-operations, being an abstract data type prevents <code>IOState</code>-values from being erroneously reused.
 
----
 
=== <u>Conclusions</u> ===
 
 
* ''Why is Haskell I/O monadic'' - to avoid having to use extra arguments and parameters ''everywhere''.
 
 
* ''Why is Haskell I/O abstract'' - to ensure I/O works as intended, by preventing the misuse of internal data.
 
 
* ''Why is Haskell I/O unusual'' - because of Haskell's ''nonstrict evaluation'' and thus its focus on ''referential transparency'', contrary to most other programming languages.
 
 
----
 
=== <u>Further reading</u> ===
 
 
If you've managed to get all the way to here, [https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.52.3656&rep=rep1&type=pdf State in Haskell] by John Launchbury and Simon Peyton Jones is also worth reading, if you're interested in how GHC eventually arrived at its current definition of <code>IO</code>.
 
 
[[Category:Tutorials]]
 

Latest revision as of 21:27, 14 June 2022

Redirect to: