In what sense is IO monad special (if at all)?
After diving into monads I understand that they are a general concept to allow chaining computations inside some context (failing, non-determinism, state, etc) and there is no magic behind them.
Still IO monad feels even if not magic, but special.
main
function What are the reasons for the points above? What makes IO so special?
Update : in pure code evaluation order doesn't matter. But it does matter when doing IO (we want to save customer before we read it). From what I understand IO monad gives us such ordering guarantees. Is it a property of a monad in general or it is something specific to IO monad?
you cannot escape IO monad like you can with other monads
I'm not sure what you mean by “escape”. If you mean, somehow unwrap the internal representation of the monadic values (eg list -> sequence of cons-cells) – that is an implementation detail. In fact, you can define an IO
emulation in pure Haskell – basically just a State
over a lot of globally-available data. That would have all the semantics of IO
, but without actually interacting with the real world, only a simulation thereof.
If you mean, you can “extract values” from within the monad – nope, that's not in general possible, even for most pure-haskell monads. For instance, you can't extract a value from Maybe a
(could be Nothing
) or Reader ba
(what if b
is uninhabited?)
IO
action can only be run by the main
function
Well, in a sense, everything can only be run by the main
function. Code that's not in some way invoked from main
will only sit there, you could replace it with undefined
without changing anything.
IO is always at the bottom of a monad transformers chain
True, but that's also the case for eg ST
.
Implementation of IO
monad is unclear and source code shows some Haskell internals
Again: implementation is just, well, an implementation detail. The complexity of IO
implementations actually has a lot to do with it being highly optimised; the same is also true for specialised pure monads (eg attoparsec).
As I already said), much simpler implementations are possible, they just wouldn't be as useful as the full-fledged optimised real-world IO
type.
Fortunately, implementation needn't really bother you; the inside of IO
may be unclear but the outside, the actual monadic interface, is pretty simple.
in pure code evaluation order doesn't matter
First of all – evaluation order can matter in pure code!
Prelude> take 10 $ foldr (h t -> h `seq` (h:t)) [] [0..]
[0,1,2,3,4,5,6,7,8,9]
Prelude> take 10 $ foldr (h t -> t `seq` (h:t)) [] [0..]
^CInterrupted.
But indeed you can never get a wrong, non-⊥ result due to misordered pure-code evaluation. That actually doesn't apply to reordering monadic actions ( IO
or otherwise) though, because changing the sequence order changes the actual structure of the resultant action, not just the evaluation order that the runtime will use to construct this structure.
For example (list monad):
Prelude> [1,2,3] >>= e -> [10,20,30] >>= z -> [e+z]
[11,21,31,12,22,32,13,23,33]
Prelude> [10,20,30] >>= z -> [1,2,3] >>= e -> [e+z]
[11,12,13,21,22,23,31,32,33]
All that said, certainly IO
is quite special, indeed I think some people hesitate to call it a monad (it's a bit unclear what it's actually supposed to mean for IO
to fulfill the monad laws). In particular, lazy IO is one massive troublemaker (and best just avoided, at all times).
Similar statements could be made about the ST
monad, or arguably the STM
monad (although you can actually implement that one on top of IO
).
Basically things like the Reader monad, Error monad, Writer monad, etc., are all just pure code. The ST
and IO
monads are the only ones that really do impure things (state mutation, etc), so they aren't definable in pure Haskell. They have to be "hard-wired" into the compiler somewhere.
上一篇: 功能性程序设计:副作用实际发生在哪里?