I/O without monads, using an event loop

I have been thinking about to what extent you could cleanly do I/O without explicit use of the I/O monad, and without uniqueness types (which are the main alternative to monads in pure functional programming, and are used in the Concurrent Clean programming language). Suppose you have a main event handler function, like this: eventMain :: (Event, SystemState AppState) -> (Command, SystemState AppState) This function could be called over and over in an event loop, until an EndProgram command was received, and the event loop would itself do all the actual I/O (the SystemStates are only in-memory representations of some part of the system state, plus the application's own state). Things like disk I/O could be done with commands which generate events when complete. Interprocess communication could be done in the same way. Then eventMain, and everything called by it, would be referentially-transparent, and yet non-monadic. You could of course build higher-level stuff on top of that. On the other hand, it's quite stateful, because anything you need to remember between events need to be recorded, either in the SystemState or externally (e.g. in a file). I suppose this is the most important disadvantage? Is there any published work or code using this approach, or something like it, in a pure functional language? I'm primarily interested in embedded system and desktop UIs, rather than say web-based systems, although both would be interesting. -- Robin

On Fri, May 30, 2008 at 03:09:37PM +0100, Robin Green wrote:
I have been thinking about to what extent you could cleanly do I/O without explicit use of the I/O monad, and without uniqueness types (which are the main alternative to monads in pure functional programming, and are used in the Concurrent Clean programming language).
Suppose you have a main event handler function, like this:
eventMain :: (Event, SystemState AppState) -> (Command, SystemState AppState)
This function could be called over and over in an event loop, until an EndProgram command was received, and the event loop would itself do all the actual I/O (the SystemStates are only in-memory representations of some part of the system state, plus the application's own state). Things like disk I/O could be done with commands which generate events when complete. Interprocess communication could be done in the same way.
Then eventMain, and everything called by it, would be referentially-transparent, and yet non-monadic. You could of course build higher-level stuff on top of that.
On the other hand, it's quite stateful, because anything you need to remember between events need to be recorded, either in the SystemState or externally (e.g. in a file). I suppose this is the most important disadvantage?
Is there any published work or code using this approach, or something like it, in a pure functional language? I'm primarily interested in embedded system and desktop UIs, rather than say web-based systems, although both would be interesting.
Yeah, check the History of Haskell paper, in particular Section 7. Edsko

Robin Green wrote:
I have been thinking about to what extent you could cleanly do I/O without explicit use of the I/O monad, and without uniqueness types (which are the main alternative to monads in pure functional programming, and are used in the Concurrent Clean programming language).
Suppose you have a main event handler function, like this:
eventMain :: (Event, SystemState AppState) -> (Command, SystemState AppState)
This function could be called over and over in an event loop, until an EndProgram command was received, and the event loop would itself do all the actual I/O (the SystemStates are only in-memory representations of some part of the system state, plus the application's own state). Things like disk I/O could be done with commands which generate events when complete. Interprocess communication could be done in the same way.
Then eventMain, and everything called by it, would be referentially-transparent, and yet non-monadic. You could of course build higher-level stuff on top of that.
Given the above, without uniqueness typing, and because there is clearly no monad, I could write breakMain :: (Event,Event,SystemState AppState) -> ((Command,SystemState AppState),(Command,SystemState AppState)) breakMain (e1,e2,sys) = ( eventMain (e1,sys) , eventMain (e2,sys) ) Now what happens? Do we get two copies of SystemState ? Simpler still, I can write (sys,eventMain e sys) -- what happens here? I have references to both before- and after- state. It is this probelm with copying the un-copiable (external I/O state), that requires pure functional languages to outlaw such programs, either via uniqueness typing, or via a monad interface that completely hides any direct reference to the underlying state and which then sequences the application of state transformers.
-- -------------------------------------------------------------------- Andrew Butterfield Tel: +353-1-896-2517 Fax: +353-1-677-2204 Foundations and Methods Research Group Director. School of Computer Science and Statistics, Room F.13, O'Reilly Institute, Trinity College, University of Dublin http://www.cs.tcd.ie/Andrew.Butterfield/ --------------------------------------------------------------------

On Fri, 30 May 2008 15:23:46 +0100
Andrew Butterfield
Robin Green wrote:
I have been thinking about to what extent you could cleanly do I/O without explicit use of the I/O monad, and without uniqueness types (which are the main alternative to monads in pure functional programming, and are used in the Concurrent Clean programming language).
Suppose you have a main event handler function, like this:
eventMain :: (Event, SystemState AppState) -> (Command, SystemState AppState)
This function could be called over and over in an event loop, until an EndProgram command was received, and the event loop would itself do all the actual I/O (the SystemStates are only in-memory representations of some part of the system state, plus the application's own state). Things like disk I/O could be done with commands which generate events when complete. Interprocess communication could be done in the same way.
Then eventMain, and everything called by it, would be referentially-transparent, and yet non-monadic. You could of course build higher-level stuff on top of that.
Given the above, without uniqueness typing, and because there is clearly no monad, I could write
breakMain :: (Event,Event,SystemState AppState) -> ((Command,SystemState AppState),(Command,SystemState AppState)) breakMain (e1,e2,sys) = ( eventMain (e1,sys) , eventMain (e2,sys) )
Now what happens? Do we get two copies of SystemState ?
Simpler still, I can write (sys,eventMain e sys) -- what happens here? I have references to both before- and after- state.
Yes, you do - but they're only in-memory representations. Sorry, I didn't fully explain what I meant. Two points: 1. The SystemState record only contains in-memory representations of *some* parts of the system state - e.g. on an embedded system they could be the on/off status of the LEDs, motor speeds, the state of the toggle switches, which buttons are currently being pressed, etc. It would be infeasible to record the entire state of, say, an attached 120GB hard drive - and even less feasible to record the state of the external environment - so only some parts of the system would be covered by this data structure. 2. It's the event loop's job to do any necessary I/O to update the *actual* system state to match the SystemState returned by eventMain (ignoring any changes which are impossible, e.g. if the program tries to say a toggle switch is on when it isn't). As I said, only in the event loop is any I/O actually performed. So when you evaluate breakMain or whatever, nothing happens - it's just manipulating representations. You can only return one SystemState from eventMain, and that is used to update the real system state - so there's no paradox. -- Robin

Am Freitag, 30. Mai 2008 16:09 schrieb Robin Green:
[…]
I'm primarily interested in embedded system and desktop UIs,
Than you should take a look at http://haskell.org/haskellwiki/FRP.
[…]
Best wishes, Wolfgang

Yeah, this sounds really similar to functionally reactive programming.
I recommend you start by reading this paper:
http://haskell.cs.yale.edu/frp/genuinely-functional-guis.pdf
On Fri, May 30, 2008 at 11:34 AM, Wolfgang Jeltsch
Am Freitag, 30. Mai 2008 16:09 schrieb Robin Green:
[…]
I'm primarily interested in embedded system and desktop UIs,
Than you should take a look at http://haskell.org/haskellwiki/FRP.
[…]
Best wishes, Wolfgang _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

On May 30, 2008, at 9:09 AM, Robin Green wrote:
eventMain :: (Event, SystemState AppState) -> (Command, SystemState AppState)
The first thing I would do with this type is probably wrap it up in a State monad so I don't have to keep track of the SystemState AppState stuff myself, which completely defeats the purpose. I don't think this really makes anything simpler at all. Also see FRP for a perhaps more practical way of approaching IO as event streams. - Jake

Robin Green wrote:
I have been thinking about to what extent you could cleanly do I/O without explicit use of the I/O monad, and without uniqueness types
Here's a way to see I/O as a pure functional data structure. To keep things simple, we model only Char I/O: data Program = Quit | Output Char Program | Input (Char -> Program) -- ... add here other I/O primitives if you want -- Example: cat :: Program cat = Input (\c -> Output c cat) -- Trivial mapping into the IO monad runProgram :: Program -> IO () runProgram Quit = return () runProgram (Output c p) = putChar c >> runProgram p runProgram (Input k) = getChar >>= runProgram . k Another approach could be to use lazy I/O, à la interact. However, I am uncomfortable with lazy I/O. See also IOSpec, a nice functional model of the IO monad: http://www.cs.nott.ac.uk/~wss/repos/IOSpec/ Regards, Zun.
participants (7)
-
Andrew Butterfield
-
Bit Connor
-
Edsko de Vries
-
Jake Mcarthur
-
Roberto Zunino
-
Robin Green
-
Wolfgang Jeltsch