Why does Haskell not have an I Monad (for input only, unlike the IO monad)?
Asked Answered
F

5

22

Conceptually, it seems that a computation that performs output is very different from one that performs input only. The latter is, in one sense, much purer.

I, for one, would like to have a way to separate the input only parts of my programme from the ones that might actually write something out.

So, why is there no input only Monad?

Any reason why it wouldn't work to have an I monad (and an O Monad, which could be combined into the IO Monad)?

Edit: I mostly meant input as reading files, not interacting with the user. This is also my use case, where I can assume that input files do not change during the execution of the programme (otherwise, it's fine to get undefined behaviour).

Fulfil answered 17/2, 2011 at 17:38 Comment(2)
There is no such thing as more or less pure. Either it's pure or not. Output is not pure. What would you expect a print function to return? print :: String -> () would not work: if you evaluate something like runAll [print "foo", print "foo"], Haskell being lazy, you could end up having only one "foo" printed out, because once you evaluate a pure function with a certain argument, you no longer need to evaluate it again. (other kinds of "crazy" things could happen: out of order printing, nothing being printed at all...)Officiant
I meant that, conceptually at least, you can rewrite all functions that read input files as having as implicit argument a huge Map String ByteStream which maps filenames to file contents. This is pure.Fulfil
R
21

I disagree with bdonlan's answer. It's true that neither input nor output are more "pure" but they are quite different. It's quite valid to critique IO as the single "sin bin" where all effects get crammed together, and it does make ensuring certain properties harder. For example, if you have many functions that you know only read from certain memory locations, and which could never cause those locations to be altered, it would be nice to know that you can reorder their execution. Or if you have a program that uses forkIO and MVars, it would be nice to know, based on its type, that it isn't also reading /etc/passwd.

Furthermore, one can compose monadic effects in a fashion besides just stacked transformers. You can't do this with all monads (just free monads), but for a case like this that's all you really need. The iospec package, for example, provides a pure specification of IO -- it doesn't seperate reading and writing, but it does seperate them from, e.g., STM, MVars, forkIO, soforth.

http://hackage.haskell.org/package/IOSpec

The key ideas for how you can combine the different monads cleanly are described in the Data Types a la Carte paper (great reading, very influential, can't recommend enough, etc.etc.).

Renata answered 17/2, 2011 at 18:3 Comment(1)
We have created a Haskell library for Capabilities (also based on Data Types a la Carte) which allows a user to create a restricted type by composing different capabilities: An action of type Restr (Stdin :+: W) () may only read from standard input and write to a file, nothing else. You can then create arbitrary new capabilities to suit your needs.Whitacre
S
12

The 'Input' side of the IO monad is just as much output as it is input. If you consume a line of input, the fact that you consumed that input is communicated to the outside, and also serves to be recorded as impure state (ie, you don't consume the same line again later); it's just as much an output operation as a putStrLn. Additionally, input operations must be ordered with respect to output operations; this again limits how much you can separate the two.

If you want a pure read-only monad, you should probably use the reader monad instead.

That said, you seem to be a bit confused about what combining monads can do. While you can indeed combine two monads (assuming one is a monad transformer) and get some kind of hybrid semantics, you have to be able to run the result. That is, even if you could define an IT (OT Identity) r, how do you run it? You have no root IO monad in this case, so main must be a pure function. Which would mean you'd have main = runIdentity . runOT . runIT $ .... Which is nonsense, since you're getting impure effects from a pure context.

In other words, the type of the IO monad has to be fixed. It can't be a user-selectable transformed type, because its type is nailed down into main. Sure, you could call it I (O Identity), but you don't gain anything; O (I Identity) would be a useless type, as would be I [] or O Maybe, because you'd never be able to run any of these.

Of course, if IO is left as the fundamental IO monad type, you could define routines like:

runI :: I Identity r -> IO r

This works, but again, you can't have anything underneath this I monad very easily, and you're not gaining much from this complexity. What would it even mean to have an Output monad transformed over a List base monad, anyway?

Semipermeable answered 17/2, 2011 at 17:43 Comment(5)
The computation of a pure function can take time and consume memory, which are "communicated to the outside"; does that make the function less pure?Gardenia
@Reid, this is an implementation detail. The consumed time and memory are accidentally communicated to the outside, yes, but with pure code you can't necessarily predict or control what will be communicated in any reliable way (nor should you, except to limit maximum memory/CPU use I suppose). With explicit input primitives, you have the guarantee of a very specific ordering of effects, which requires specific guarantees from this I monad that are explicitly not present in pure code.Semipermeable
My point is that the notion of what is "externally observable" is largely arbitrary and tailored to the situation. If for example you are reading only static data files which do not change during the program's execution, then it is perfectly reasonable to consider their input as pure--as much so as the consumption of memory and processor time.Gardenia
@Reid, "externally observable" in this case is referring to things that the program can deterministically control in providing output to the outside. This deterministic control is not present in pure code; it is, however, present in both input and output code in the IO monad.Semipermeable
"If you consume a line of input, the fact that you consumed that input is communicated to the outside" - yes, that is how it currently works, but it doesn't have to work that way.Showalter
P
3

When you obtain input, you cause side-effects that changes both the state of the outside world (the input is consumed) and your program (the input is used). When you output, you cause side-effects that only change the state of the outside world (output is produced); the act of outputting itself does not change the state of your program. So you might actually say that O is more "pure" than I.

Except that output does actually change the execution state of your program (It won't repeat the same output operation over and over; it has to have some sort of state change in order to move on). It all depends on how you look at it. But it's so much easier to lump the dirtiness of input and output into the same monad. Any useful program will both input and output. You can categorize the operations you use into one or the other, but I'm not seeing a convincing reason to employ the type system for the task.

Either you're messing with the outside world or you're not.

Presbyopia answered 17/2, 2011 at 18:55 Comment(1)
Obtaining input doesn't have to change the world state. Input can be consumed more than once.Showalter
M
2

Short answer: IO is not I/O. Other folks have longer answers if you like.

Misstate answered 1/5, 2011 at 1:32 Comment(0)
C
0

I think the division between pure and impure code is somewhat arbitrary. It depends on where you put the barrier. Haskell's designers decided to clearly separate pure functional part of the language from the rest.

So we have IO monad which incorporates all the possible effects (as different, as disk reads/writes, networking, memory access). And language enforces a clear division by means of return type. And this induces a kind of thinking which divides everything in pure and impure.

If the information security is concerned, it would be quite naturally to separate reading and writing. But for haskell's initial goal, to be a standard lazy pure functional language, it was an overkill.

Cicisbeo answered 27/2, 2011 at 15:15 Comment(1)
Since all of those effects are conflated in Plan 9/Inferno, I suspect that Haskell's "corral" isn't as arbitrary as it looks. Although, it would be an awesome feat of engineering that could use an RNG to faithfully simulate file output. The converse seems simple.Misstate

© 2022 - 2025 — McMap. All rights reserved.