Difference between revisions of "Output/Input"

From HaskellWiki
Jump to navigation Jump to search
m
(Stale references to articles replaced, selected content simplified, other changes)
Line 1: Line 1:
  +
Regarding <code>IO a</code>, Haskell's monadic I/O type:
<div style="border-left:1px solid lightgray; padding: 1em" alt="blockquote">
 
A purely functional program implements a <i>function</i>; it has no side effect. [...] if the side effect can’t be in the functional program, it will have to be outside it.
 
   
  +
<blockquote>
<small>[https://web.archive.org/web/20210415200634/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 (pages 3-4 of 60). </small>
 
  +
Some operations are primitive actions,
</div>
 
  +
corresponding to conventional I/O operations. Special operations (methods in the class <code>Monad</code>, see Section 6.3.6)
  +
sequentially compose actions, corresponding to sequencing operators (such as the semicolon) in imperative
  +
languages.
   
  +
:<small>[The Haskell 2010 Report], (page 107 of 329).</small>
One technique has been used for similar tasks:
 
  +
</blockquote>
   
  +
So for I/O, the monadic interface merely provides [[Monad tutorials timeline|an abstract way]] to sequence its actions. However there is another, more direct approach to sequencing:
<div style="border-left:1px solid lightgray; padding: 1em" alt="blockquote">
 
This is discussed by Burton[https://academic.oup.com/comjnl/article-pdf/31/3/243/1157325/310243.pdf <span></span>], and is built on by Harrison[https://core.ac.uk/download/9835633.pdf <span></span>]. The effect of this proposal is to place the non-determinism <i>entirely</i> outside the software [...]
 
 
<small>[https://academic.oup.com/comjnl/article-pdf/32/2/162/1445725/320162.pdf Functional Programming and Operating Systems], Simon B. Jones and A. F. Sinclair (page 10 of 13).</small>
 
</div>
 
 
It can also be used to provide access to external resources:
 
 
<div style="border-left:1px solid lightgray; padding: 1em" alt="blockquote">
 
The approach generalizes so that a program can make use of other run-time information such as the current time or current amount of available storage.
 
 
<small>[https://academic.oup.com/comjnl/article-pdf/31/3/243/1157325/310243.pdf Nondeterminism with Referential Transparency in Functional Programming Languages], F. Warren Burton (front page).</small>
 
</div>
 
 
Perhaps it can be used for I/O...
 
<br>
 
 
__TOC__
 
<sup> <sup>
 
 
----
 
=== <u>Details, details</u> ===
 
 
How does it work?
 
 
<div style="border-left:1px solid lightgray; padding: 1em" alt="blockquote">
 
[...] supply each program with an extra argument consisting of an infinite (lazy) binary tree of values. (We choose a tree [...] since any number of subtrees may be extracted from an infinite tree). In practice, these values will be determined at run time (when used as arguments to a special function [...]), but once fixed will never change.
 
</div>
 
 
...<i>“a special function”</i>: only one? More will definitely be needed! To keep matters [https://www.interaction-design.org/literature/article/kiss-keep-it-simple-stupid-a-design-principle simple], each value shall only be used <b>once</b> (if at all) as an argument to any such function.
 
   
 
<haskell>
 
<haskell>
  +
Control.Parallel.pseq :: a -> b -> b
main' :: Tree Exterior -> ...
 
 
-- section 2
 
data Tree a = Node { contents :: a,
 
left :: Tree a,
 
right :: Tree a }
 
 
data Exterior -- the abstract value type
 
getchar :: Exterior -> Char -- the special functions
 
putchar :: Char -> Exterior -> () -- (add more as needed :-)
 
 
</haskell>
 
</haskell>
   
  +
(as opposed to the [[seq|<b>non</b>]]-sequential <code>Prelude.seq</code>.) So a more direct way of preserving [[Referential transparency|referential transparency]] is also needed. For simple teletype I/O:
Avoiding gratuitous repetition:
 
 
<haskell>
 
type OI = Tree Exterior
 
 
getChar' :: OI -> Char
 
getChar' = getchar . contents
 
 
putChar' :: Char -> OI -> ()
 
putChar' c = putchar c . contents
 
</haskell>
 
<sup> </sup>
 
 
==== An alternative abstraction ====
 
 
About those trees: are they really necessary? If <code>OI</code> was an abstract data type, the use of trees could at least be confined to the implementation:
 
   
 
<haskell>
 
<haskell>
 
data OI
 
data OI
getChar' :: OI -> Char
+
partOI :: OI -> (OI, OI)
putChar' :: Char -> OI -> ()
+
getChar :: OI -> Char
  +
putChar :: Char -> OI -> ()
 
</haskell>
 
</haskell>
   
  +
where:
...provided that single-use property applies directly to <code>OI</code> values (thereby deeming <i>“special”</i> any function which uses an <code>OI</code> argument). That includes the initial <code>OI</code> value supplied to each program:
 
   
  +
* <code>OI</code> isn't an ordinary Haskell type - ordinary Haskell types represent values without (externally-visible) side-effects, hence <code>OI</code> being abstract.
<haskell>
 
main' :: OI -> ...
 
</haskell>
 
   
  +
* The action <code>partOI</code> is needed because each <code>OI</code> can only be used once.
But most Haskell programs will need more:
 
   
  +
* The action <code>getChar</code> obtains the the next character of input.
<haskell>
 
part :: OI -> (OI, OI)
 
part t = (left t, right t)
 
</haskell>
 
   
  +
* The function <code>putChar</code> expects a character, and returns an action which will output the given character.
...than two <code>OI</code> values:
 
 
<haskell>
 
parts :: OI -> [OI]
 
parts t = let (t1, t2) = part t in t1 : parts t2
 
</haskell>
 
 
So <code>OI</code> can be a tree-free abstract data type after all:
 
 
<haskell>
 
data OI
 
partOI :: OI -> (OI, OI)
 
getChar :: OI -> Char
 
putChar :: Char -> OI -> ()
 
</haskell>
 
<sup> </sup>
 
   
  +
Now for a few other I/O interfaces - if <code>seq</code> was actually sequential:
----
 
=== <u>Other interfaces</u> ===
 
   
 
* [[Monad|monad]]
 
* [[Monad|monad]]
Line 150: Line 80:
 
putcharC :: C Char -> ()
 
putcharC :: C Char -> ()
 
putcharC (u, c) = putChar c u
 
putcharC (u, c) = putChar c u
 
 
</haskell>
 
</haskell>
   
Line 183: Line 112:
 
</haskell>
 
</haskell>
   
The <code>OI</code> interface can also be used to implement [https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.91.3579&rep=rep1&type=pdf I/O models used in earlier versions] of Haskell:
+
The <code>OI</code> interface can also be used to implement [https://web.archive.org/web/20210414160729/https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.91.3579&rep=rep1&type=pdf I/O models used in earlier versions] of Haskell:
   
 
* dialogues:
 
* dialogues:
Line 225: Line 154:
 
</haskell>
 
</haskell>
   
...and even <i>that</i> <s><i>world</i></s> state-passing style used in GHC, which is also used by [https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.17.935&rep=rep1&type=pdf Clean], [https://staff.science.uva.nl/c.u.grelck/publications/HerhSchoGrelDAMP09.pdf Single-Assignment C] and as part of the I/O model used for the verification of interactive programs in [https://cakeml.org/vstte18.pdf CakeML], remembering that <code>OI</code> values can only be used once:
+
...and even <i>that</i> <s><i>world</i></s> state-passing style used in GHC, and by [https://web.archive.org/web/20130607204300/https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.17.935&rep=rep1&type=pdf Clean], [https://staff.science.uva.nl/c.u.grelck/publications/HerhSchoGrelDAMP09.pdf Single-Assignment C] and as part of the I/O model used for the verification of interactive programs in [https://cakeml.org/vstte18.pdf CakeML], remembering that <code>OI</code> values can only be used once:
   
 
<haskell>
 
<haskell>
Line 240: Line 169:
 
W u2
 
W u2
 
</haskell>
 
</haskell>
<sup> </sup>
 
   
  +
(Rewriting those examples to use <code>pseq</code> is left as an exercise.)
----
 
  +
=== <u>See also</u> ===
 
  +
See also:
   
 
* [[Plainly partible]]
 
* [[Plainly partible]]

Revision as of 17:40, 16 September 2024

Regarding IO a, Haskell's monadic I/O type:

Some operations are primitive actions, corresponding to conventional I/O operations. Special operations (methods in the class Monad, see Section 6.3.6) sequentially compose actions, corresponding to sequencing operators (such as the semicolon) in imperative languages.

[The Haskell 2010 Report], (page 107 of 329).

So for I/O, the monadic interface merely provides an abstract way to sequence its actions. However there is another, more direct approach to sequencing:

Control.Parallel.pseq :: a -> b -> b

(as opposed to the non-sequential Prelude.seq.) So a more direct way of preserving referential transparency is also needed. For simple teletype I/O:

data OI
partOI  :: OI -> (OI, OI)
getChar :: OI -> Char
putChar :: Char -> OI -> ()

where:

  • OI isn't an ordinary Haskell type - ordinary Haskell types represent values without (externally-visible) side-effects, hence OI being abstract.
  • The action partOI is needed because each OI can only be used once.
  • The action getChar obtains the the next character of input.
  • The function putChar expects a character, and returns an action which will output the given character.

Now for a few other I/O interfaces - if seq was actually sequential:

type M a   =  OI -> a

unit       :: a -> M a
unit x     =  \ u -> let !_ = partOI u in x 

bind       :: M a -> (a -> M b) -> M b
bind m k   =  \ u -> let !(u1, u2) = partOI u in
                     let !x = m u1 in
                     let !y = k x u2 in
                     y

getcharM   :: M Char
getcharM   =  getChar

putcharM   :: Char -> M () 
putcharM   =  putChar
type C a         =  (OI, a)

extract          :: C a -> a
extract (u, x)   =  let !_ = partOI u in x

duplicate        :: C a -> C (C a)
duplicate (u, x) =  let !(u1, u2) = partOI u in
                    (u2, (u1, x))

extend           :: (C a -> b) -> C a -> C b
extend h (u, x)  =  let !(u1, u2) = partOI u in
                    let !y        = h (u1, x) in
                    (u2, y)

getcharC         :: C () -> Char
getcharC (u, ()) =  getChar u

putcharC         :: C Char -> ()
putcharC (u, c)  =  putChar c u
type A b c   =  (OI -> b) -> (OI -> c)

arr          :: (b -> c) -> A b c
arr f        =  \ c' u -> let !x = c' u in f x

both         :: A b c -> A b' c' -> A (b, b') (c, c')
f' `both` g' =  \ c' u -> let !(u1:u2:u3:_) = partsOI u in
                          let !(x, x')      = c' u1 in
                          let !y            = f' (unit x) u2 in
                          let !y'           = g' (unit x') u3 in
                          (y, y')
                where
                  unit x u = let !_ = partOI u in x

getcharA     :: A () Char
getcharA     =  \ c' u -> let !(u1, u2) = partOI u in
                          let !_        = c' u1 in
                          let !ch       = getChar u2 in
                          ch     

putcharA     :: A Char ()
putcharA     =  \ c' u -> let !(u1, u2) = partOI u in
                          let !ch       = c' u1 in
                          let !z        = putChar ch u2 in
                          z

The OI interface can also be used to implement I/O models used in earlier versions of Haskell:

  • dialogues:
runD :: ([Response] -> [Request]) -> OI -> ()
runD d u = foldr (\ (!_) -> id) () $ yet $ \ l -> zipWith respond (d l) (partsOI u)

yet :: (a -> a) -> a
yet f = f (yet f)

respond :: Request -> OI -> Response
respond Getq     u = let !c = getChar u in Getp c
respond (Putq c) u = let !_ = putChar c u in Putp

data Request  = Getq | Putq Char
data Response = Getp Char | Putp
  • continuations:
type Answer = OI -> ()

runK :: Answer -> OI -> ()
runK a u = a u

doneK :: Answer
doneK = \ u -> let !_ = partOI u in ()

getcharK :: (Char -> Answer) -> Answer
getcharK k   = \ u -> let !(u1, u2) = partOI u in
                      let !c        = getChar u1 in
                      let !a        = k c in
                      a u2

putcharK :: Char -> Answer -> Answer
putcharK c a = \ u -> let !(u1, u2) = partOI u in
                      let !_        = putChar c u1 in
                      a u2

...and even that world state-passing style used in GHC, and by Clean, Single-Assignment C and as part of the I/O model used for the verification of interactive programs in CakeML, remembering that OI values can only be used once:

newtype World = W OI

getcharL :: World -> (Char, World)
getcharL (W u) = let !(u1, u2) = partOI u in
                 let !c = getChar u1 in
                 (c, W u2)

putcharL :: Char -> World -> World
putcharL c (W u) = let !(u1, u2) = partOI u in
                   let !_ = putChar c u1 in
                   W u2

(Rewriting those examples to use pseq is left as an exercise.)

See also: