Difference between revisions of "IO Semantics"

From HaskellWiki
Jump to navigation Jump to search
m (Extra reading references)
m (Various minor changes; extra references)
Line 1: Line 1:
 
[[Category:Theoretical_foundations]]
 
[[Category:Theoretical_foundations]]
== Semantics of <code>IO</code>: A Free Approach ==
 
   
  +
<i>
The following is inspired by [https://lukepalmer.wordpress.com/2008/03/29/io-monad-the-continuation-presentation Luke Palmer's post] on the topic. This only describes one possible semantics of <code>IO a</code>; your actual implementation may vary.
 
  +
Note:
  +
* For simplicity, the examples here only gives semantics for teletype I/O.
  +
* These are only some of the various ways to describe the semantics of </i><code>IO a</code><i>; your actual implementation may vary.
  +
</i>
  +
  +
== A free approach ==
  +
 
(Inspired by [https://lukepalmer.wordpress.com/2008/03/29/io-monad-the-continuation-presentation Luke Palmer's post].)
   
 
The idea is to define <code>IO</code> as
 
The idea is to define <code>IO</code> as
Line 11: Line 18:
 
</haskell>
 
</haskell>
   
For simplicity, this is an example of <code>IO</code> that only gives semantics for teletype I/O.
+
Think of <code>IO a</code> as a tree:
 
Think of <code>IO a</code> as a tree whose leaves are <code>Done a</code> that holds the result of the program:
 
 
* <code>PutChar</code> is a node that has one child tree and the node holds one character of data.
 
* <code>PutChar</code> is a node that has one child tree and the node holds one character of data.
 
* <code>GetChar</code> is a node that has many children; it has one child for every character, but <code>GetChar</code> holds no data itself.
 
* <code>GetChar</code> is a node that has many children; it has one child for every character, but <code>GetChar</code> holds no data itself.
 
* <code>Done a</code> (a leaf) is a node that holds the result of the program.
   
This tree contains all the information needed to execute teletype interactions. One interprets (or executes) an <code>IO a</code> by tracing a route from root of the tree to a leaf:
+
This tree contains all the information needed to execute basic interactions. One interprets (or executes) an <code>IO a</code> by tracing a route from root of the tree to a leaf:
 
* If a <code>PutChar</code> node is encountered, the character data contained at that node is output to the terminal and then its subtree is executed. It is at this point that Haskell code is evaluated in order to determine what character should be displayed before continuing.
 
* If a <code>PutChar</code> node is encountered, the character data contained at that node is output to the terminal and then its subtree is executed. It is at this point that Haskell code is evaluated in order to determine what character should be displayed before continuing.
 
* If a <code>GetChar</code> node is encountered, a character is read from the terminal (blocking if necessary) and the subtree corresponding to the character received is executed.
 
* If a <code>GetChar</code> node is encountered, a character is read from the terminal (blocking if necessary) and the subtree corresponding to the character received is executed.
* If <code>Done</code> is encountered, the program ends.
+
* If a <code>Done</code> node is encountered, the program ends.
   
<code>Done</code> holds the result of the computation, but in the case of <code>Main.main :: IO ()</code> the data is of type <code>()</code> and thus contains no information and is ignored.
+
<code>Done</code> holds the result of the computation, but in the case of <code>Main.main :: IO ()</code> the data is of type <code>()</code> and thus ignored as it contains no information.
   
 
This execution is not done anywhere in a Haskell program, rather it is done by the run-time system.
 
This execution is not done anywhere in a Haskell program, rather it is done by the run-time system.
Line 49: Line 55:
   
 
* The function <code>putChar</code> builds a small <code>IO ()</code> tree that contains one <code>PutChar</code> node holding the character data followed by <code>Done</code>.
 
* The function <code>putChar</code> builds a small <code>IO ()</code> tree that contains one <code>PutChar</code> node holding the character data followed by <code>Done</code>.
 
 
* The function <code>getChar</code> builds a short <code>IO Char</code> tree that begins with a <code>GetChar</code> node that holds one <code>Done</code> node for every character.
 
* The function <code>getChar</code> builds a short <code>IO Char</code> tree that begins with a <code>GetChar</code> node that holds one <code>Done</code> node for every character.
   
Other teletype commands can be defined in terms of these primitives:
+
Other commands can be defined in terms of these primitives:
 
<haskell>
 
<haskell>
 
putStr :: String -> IO ()
 
putStr :: String -> IO ()
Line 62: Line 67:
 
| SysCallName p1 p2 ... pn (r -> IO a)
 
| SysCallName p1 p2 ... pn (r -> IO a)
 
</haskell>
 
</haskell>
  +
where:
where <code>p1</code> ... <code>pn</code> are the parameters for the system call, and <code>r</code> is the result of the system call. (Thus <code>PutChar</code> and <code>GetChar</code> will not occur as constructors for I/O trees if they don't correspond to system calls).
 
  +
* <code>p1</code> ... <code>pn</code> are the parameters for the system call,
  +
* and <code>r</code> is the result of the system call.
  +
 
(Thus <code>PutChar</code> and <code>GetChar</code> will not occur as constructors for I/O trees if they don't correspond to system calls).
   
 
== Further reading ==
 
== Further reading ==
  +
  +
* [https://www.haskell.org/definition/haskell2010.pdf The Haskell 2010 Report]
  +
::ed. Simon Marlow Marlow, 2010.
   
 
* [http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.722.8440&rep=rep1&type=pdf A Functional Specification of Effects]
 
* [http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.722.8440&rep=rep1&type=pdf A Functional Specification of Effects]
Line 78: Line 90:
 
::Levent Erkök, John Launchbury, Andrew Moran. In Fixed Points in Computer Science Workshop, FICS'01 (2001).
 
::Levent Erkök, John Launchbury, Andrew Moran. In Fixed Points in Computer Science Workshop, FICS'01 (2001).
   
* [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]
+
* [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. In "Engineering theories of software construction", ed. Tony Hoare, Manfred Broy, Ralf Steinbruggen, IOS Press, ISBN 1 58603 1724, 2001, pages 47-96.
 
::Simon Peyton Jones. In "Engineering theories of software construction", ed. Tony Hoare, Manfred Broy, Ralf Steinbruggen, IOS Press, ISBN 1 58603 1724, 2001, pages 47-96.
   
 
* [https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.52.6409&rep=rep1&type=pdf Relating operational and denotational semantics for input/output effects]
 
* [https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.52.6409&rep=rep1&type=pdf Relating operational and denotational semantics for input/output effects]
 
::Roy L. Crole, Andrew D. Gordon. Mathematical Structures in Computer Science 9(2): 125-158 (1999).
 
::Roy L. Crole, Andrew D. Gordon. Mathematical Structures in Computer Science 9(2): 125-158 (1999).
  +
  +
* [https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.91.3579&rep=rep1&type=pdf How to Declare an Imperative]
  +
::Philip Wadler. ACM Computing Surveys, 29(3): 240-263, September 1997.
   
 
* [https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.16.5894&rep=rep1&type=pdf A Sound Metalogical Semantics for Input/Output Effects]
 
* [https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.16.5894&rep=rep1&type=pdf A Sound Metalogical Semantics for Input/Output Effects]

Revision as of 20:19, 2 January 2024


Note:

  • For simplicity, the examples here only gives semantics for teletype I/O.
  • These are only some of the various ways to describe the semantics of IO a; your actual implementation may vary.

A free approach

(Inspired by Luke Palmer's post.)

The idea is to define IO as

data IO a = Done a
          | PutChar Char (IO a)
          | GetChar (Char -> IO a)

Think of IO a as a tree:

  • PutChar is a node that has one child tree and the node holds one character of data.
  • GetChar is a node that has many children; it has one child for every character, but GetChar holds no data itself.
  • Done a (a leaf) is a node that holds the result of the program.

This tree contains all the information needed to execute basic interactions. One interprets (or executes) an IO a by tracing a route from root of the tree to a leaf:

  • If a PutChar node is encountered, the character data contained at that node is output to the terminal and then its subtree is executed. It is at this point that Haskell code is evaluated in order to determine what character should be displayed before continuing.
  • If a GetChar node is encountered, a character is read from the terminal (blocking if necessary) and the subtree corresponding to the character received is executed.
  • If a Done node is encountered, the program ends.

Done holds the result of the computation, but in the case of Main.main :: IO () the data is of type () and thus ignored as it contains no information.

This execution is not done anywhere in a Haskell program, rather it is done by the run-time system.

The monadic operations are defined as follows:

return :: a -> IO a
return x = Done x

(>>=)  :: IO a -> (a -> IO b) -> IO b
Done x      >>= f = f x
PutChar c x >>= f = PutChar c (x >>= f)
GetChar g   >>= f = GetChar (\c -> g c >>= f)

As you can see return is just another name for Done. The bind operation (>>=) takes a tree x and a function f and replaces the Done nodes (the leaves) of x by a new tree produced by applying f to the data held in the Done nodes.

The primitive I/O commands are defined using these constructors.

putChar :: Char -> IO ()
putChar x = PutChar x (Done ())

getChar :: IO Char
getChar = GetChar (\c -> Done c)
  • The function putChar builds a small IO () tree that contains one PutChar node holding the character data followed by Done.
  • The function getChar builds a short IO Char tree that begins with a GetChar node that holds one Done node for every character.

Other commands can be defined in terms of these primitives:

putStr :: String -> IO ()
putStr = mapM_ putChar

More generally speaking, IO a will represent the desired interaction with the operating system. For every system call there will be a corresponding I/O-tree constructor of the form:

	| SysCallName p1 p2 ... pn (r -> IO a)

where:

  • p1 ... pn are the parameters for the system call,
  • and r is the result of the system call.

(Thus PutChar and GetChar will not occur as constructors for I/O trees if they don't correspond to system calls).

Further reading

ed. Simon Marlow Marlow, 2010.
Wouter Swierstra. Ph.D. thesis, University of Nottingham. (2009).
Wouter Swierstra, Thorsten Altenkirch. In: Proceedings of the ACM SIGPLAN Workshop on Haskell, Haskell ’07, ACM, New York, NY, USA, pages 25–36 (2007).
Malcolm Dowse. PhD dissertation, University of Dublin, Trinity College (2006).
Levent Erkök, John Launchbury, Andrew Moran. In Fixed Points in Computer Science Workshop, FICS'01 (2001).
Simon Peyton Jones. In "Engineering theories of software construction", ed. Tony Hoare, Manfred Broy, Ralf Steinbruggen, IOS Press, ISBN 1 58603 1724, 2001, pages 47-96.
Roy L. Crole, Andrew D. Gordon. Mathematical Structures in Computer Science 9(2): 125-158 (1999).
Philip Wadler. ACM Computing Surveys, 29(3): 240-263, September 1997.
Andrew Gordon. In International Workshop on Computer Science Logic, January 1995. Springer Berlin Heidelberg.
Andrew Gordon. In FPCA '93: Conference on Functional Programming Languages and Computer Architecture, Copenhagen, June 1993. ACM Press.
Andrew Gordon. Cambridge University Press. Revision of 1992 PhD dissertation.
Andrew Gordon. Computer Laboratory Technical Report Number 160, University of Cambridge (1989).
Simon Thompson. Technical Report 48, Computing Laboratory, University of Kent, Canterbury, UK, November 1987.