简体   繁体   中英

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

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).

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, eg, 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.).

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 $ ... main = runIdentity . runOT . runIT $ ... 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?

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.

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM