Functional programming : Where does the side effect actually happen?
After beginning to learn Haskell, there something I don't understand in Haskell even after reading a lot of documentation.
I understand that to perform IO operation you have to use a "IO monad" that wrapped a value in a kind of "black box" so the any function that uses the IO monad is still purely functional. OK, fine, but then where does the IO operation actually happen?
Does it mean that a Monad itself is not purely functional? Or is the IO operation implemented, let's say in C, and "embedded" inside the Haskell compiler?
Can I write, in pure Haskell, with or without a Monad, something that will do an IO operation? And if not, where does this ability come from if it's not possible inside the language itself? If it's embedded/linked inside the Haskell compiler to chunks of C code, will that eventually be called by the IO Monad to do the "dirty job"?
As a preface, it's not "the IO
Monad
", despite what many poorly-written introductions say. It's just "the IO
type". There's nothing magical about monads. Haskell's Monad
class is a pretty boring thing - it's just unfamiliar and more abstract than what most languages can support. You don't ever see anyone call IO
"the IO
Alternative
," even though IO
implements Alternative
. Focusing too much on Monad
only gets in the way of learning.
The conceptual magic for principled handling of effects (not side-effects!) in pure languages is the existence of an IO
type at all. It's a real type. It's not some flag saying "This is impure!". It's a full Haskell type of kind * -> *
, just like Maybe
or []
. Type signatures accepting IO values as arguments, like IO a -> IO (Maybe a)
, make sense. Type signatures with nested IO make sense, like IO (IO a)
.
So if it's a real type, it must have a concrete meaning. Maybe a
as a type represents a possibly-missing value of type a
. [a]
means 0 or more values of type a
. IO a
means a sequence of effects that produce a value of type a
.
Note that the entire purpose of IO
is to represent a sequence of effects. As I said above, they're not side effects. They can't be hidden away in an innocuous-looking leaf of the program and mysteriously change things behind the back of other code. Instead, effects are rather explicitly called out by the fact that they're an IO
value. This is why people attempt to minimize the part of their program using IO
types. The less that you do there, the fewer ways there are for spooky action at a distance to interfere with your program.
As to the main thrust of your question, then - a complete Haskell program is an IO
value called main
, and the collection of definitions it uses. The compiler, when it generates code, inserts a decidedly non-Haskell block of code that actually runs the sequence of effects in an IO
value. In some sense, this is what Simon Peyton Jones (one of the long-time authors of GHC) was getting at in his talk Haskell is useless.
It's true that whatever actually executes the IO action cannot remain conceptually pure. (And there is that very impure function that runs IO
actions exposed within the Haskell language. I won't say more about it than it was added to support the foreign function interface, and using it improperly will break your program very badly.) But the point of Haskell is to provide a principled interface to the effect system, and hide away the unprincipled bits. And it does that in a way that's actually quite useful in practice.
上一篇: 米尔纳型推理算法的实现
下一篇: 功能性程序设计:副作用实际发生在哪里?