Monads for effects are familiar to the Haskell programmer; they were introduced by Wadler's "Monads for functional programming" and are the accepted way to work with side effects in a lazy language.
Note that corralling side effects is necessary with laziness: sharing gives us efficient evaluation, but if sharing a side-effecting result may change how often its effects are performed.
Wadler's approach was brilliant and timely. Nearly all I/O in a typical Haskell program is done via the monadic interface. This is why monads are famously associated with Haskell: it is hard to imagine doing I/O in a lazy language without reinventing the monad.
Nevertheless, there remain some practical limitations to Haskell's IO
type.
Laziness & Streaming FFI
There are several unsatisfying tidbits w.r.t. C FFI:
The
unsafePerformIO
function is justified, for instance, to compute \( {n \choose k } \). We need to free memory allocated during computation; however, the result is constant and can be shared. There is no need to perform its effects each time as there would be withputStrLn
, for instance.The requirements here are idiosyncratic: one must perform all clumped effects together or not at all, and one can reuse the result without performing the effects again.
As I have pointed out before, one would like to wrap a streaming C FFI and provide a streaming API based on laziness. In such situations, one would use
unsafeIOToST
orunsafeInterleaveIO
.The requirements here are different: one would like to stream
ByteString
s, with allocations (a side effect) occuring only as-needed, based on forcing lazy values. Again, effects need to be clumped together.However,
unsafeInterleaveIO
is pathological. However, it is now the only way to wrap a C API with a Haskell API that would be the same as something written in pure Haskell.
Philosophical Foundations
There are some philosophical objections to Haskell's
IO type:
As pointed out by Wadler in Linear types can change the world!,
one can duplicate or discard the RealWorld
:
kill : RealWorld -> ()
dupl : RealWorld -> (RealWorld, RealWorld)
Moreover, it is not clear that all effecting functions need take place in the
IO
monad: perhaps one could use a CFFI
token for interacting with an
appropriate C API (say, for streaming decompression) and another for console
output.
Confusion
mtl-Replacements
Some Haskell libraries (and even academic papers) claim to offer "effects" - but are really more like mtl replacements.
None address the C FFI cases above, and in any case GHC still only accepts FFI
bindings through IO
.
Algebraic Effects
Some languages (ATS, Kitten, Mirth, Idris, PureScript) track effects at the type level, but they do not inform the above problems around laziness. ATS does not require a token to be passed around for effects and does not use monads at all.