58

Every other monad comes with a transformer version, and from what I know the idea of a transformer is a generic extension of monads. Following how the other transformers are build, IOT would be something like

newtype IOT m a = IOT { runIOT :: m (IO a) }

for which I could make up useful applications on the spot: IOT Maybe can either do an IO action or nothing, IOT [] can build a list that can later be sequenced.

So why is there no IO transformer in Haskell?

(Notes: I've seen this post on Haskell Cafe, but can't make much sense of it. Also, the Hackage page for the ST transformer mentions a possibly related issue in its description, but doesn't offer any details.)

David
  • 8,275
  • 5
  • 26
  • 36
  • 5
    For the same reason there is no `runIO` function (discounting unsafePerformIO of course)... – stephen tetley Oct 24 '12 at 19:57
  • 8
    1. that doesn't explain anything, 2. there is no function `m a -> a` in the monad interface so I don't see how it is related in the first place. (The internals of bind can be as unsafe as they want as long as the interface is pure.) – David Oct 24 '12 at 19:59
  • Yes - it was a bit gnomic. There is no `runIO` because you cannot run side-effecting code to get a pure answer. Similarly there is no justification for IOT because there is no monad which you can reasonably add IO effects to. In a monad stack IO has to be "innermost" monad - you can add the other monadic effects to it, but not the other way around. – stephen tetley Oct 24 '12 at 20:08
  • 1
    Do you mean that there is no `IOT` without (sensibly) unwrapping internally every time you use bind, which leads to unpredictable behavior? (If yes, maybe make it into a full answer) – David Oct 24 '12 at 20:14
  • 6
    What should `runIOT (launchMissiles >> lift [])` evaluate to? – is7s Oct 24 '12 at 20:15
  • 5
    If I could upvote this question more than once, I would. Consider how one might give an alternative semantics to IO, how one might interpret it purely in terms of a datatype. What makes anyone think that IO code is "side-effecting"? Only one particular runtime interpretation. – pigworker Oct 25 '12 at 00:14
  • @pigworker - I think most people assume IO is side-effecting because the Haskell Report specifies that side-effecting functions happen in the IO monad. Aside from that, you might want to look at http://comonad.com/reader/2011/free-monads-for-less-3/ – John L Oct 25 '12 at 07:18
  • 1
    @JohnL Yes, that's the sort of treatment I had in mind. It may be the case that side-effecting functions happen in the IO monad, but that doesn't imply that every conceivable semantics for IO is side-effecting. Now, the decision to keep IO abstract has the consequence that only the runtime system is able to provide a semantics for IO: programmers are stuck with it. But indeed, we might imagine a knock-off IO monad, offering the same interface interpretably, with a transformer variant. We might also imagine alternative runtimes which "do" IO by generating values in the knock-off IO type. – pigworker Oct 25 '12 at 09:29
  • My answer to the question at http://stackoverflow.com/a/11794441/946226 also explains why a `IOT` is not possible. – Joachim Breitner Oct 25 '12 at 12:54
  • You say: "`IOT Maybe` can either do an IO action or nothing". However, in such a monad the values would be either `IOT Just IO a` i.e. an IO action wrapped inside `Just` and `IOT` or it could be `IOT Nothing`. In the latter case you don't have `IO` anymore and your can't really do anything anymore. Basically the behaviour would be that you do computations normally until some error producing `Nothing` occurs and the program can exit because there's no further work to do. This feels somewhat reasonable but probably not super useful since you can already use `exitWith` to similarly exit early. – QuantumWiz Jun 08 '22 at 12:45
  • You said: "`IOT []` can build a list that can later be `sequence`d". But you don't need transformer for `IO` in order to `sequence` a list of IO actions. The list monad in Haskell is for non-determinism where you have a computation that branches to multiple results. With `IOT []` you'd have to be able to execute a list of IO actions based on the results of previous IO action lists. What would that mean? The most reasonable interpretation seems that the program would split into multiple threads or processes that run parallel. They'd still share "`RealWorld`" so I'm not sure if it's reasonable. – QuantumWiz Jun 08 '22 at 13:16

1 Answers1

39

Consider the specific example of IOT Maybe. How would you write a Monad instance for that? You could start with something like this:

instance Monad (IOT Maybe) where
    return x = IOT (Just (return x))
    IOT Nothing >>= _ = IOT Nothing
    IOT (Just m) >>= k = IOT $ error "what now?"
      where m' = liftM (runIOT . k) m

Now you have m' :: IO (Maybe (IO b)), but you need something of type Maybe (IO b), where--most importantly--the choice between Just and Nothing should be determined by m'. How would that be implemented?

The answer, of course, is that it wouldn't, because it can't. Nor can you justify an unsafePerformIO in there, hidden behind a pure interface, because fundamentally you're asking for a pure value--the choice of Maybe constructor--to depend on the result of something in IO. Nnnnnope, not gonna happen.

The situation is even worse in the general case, because an arbitrary (universally quantified) Monad is even more impossible to unwrap than IO is.


Incidentally, the ST transformer you mention is implemented differently from your suggested IOT. It uses the internal implementation of ST as a State-like monad using magic pixie dust special primitives provided by the compiler, and defines a StateT-like transformer based on that. IO is implemented internally as an even more magical ST, and so a hypothetical IOT could be defined in a similar way.

Not that this really changes anything, other than possibly giving you better control over the relative ordering of impure side effects caused by IOT.

C. A. McCann
  • 76,893
  • 19
  • 209
  • 302
  • 1
    Good answer! Concerning the last sentence: does that mean transformers are *not* universal even if we leave away the quirky `RealWorld`? Is it a nice coincidence that there are (as in exist) transformers of all the other monads we commonly use? – David Oct 24 '12 at 20:28
  • 25
    @David: Depends on how you look at it. If `IO` was really truly a `State` monad whose state value was the entire outside universe, then an `IOT` defined as such would work correctly, where "correctly" means that a `Nothing` in `IOT Maybe` would discard the universe and thus end all existence. Personally, I'd stick with the current situation instead... – C. A. McCann Oct 24 '12 at 20:33
  • 3
    This is to mistake what it is to *be* an IO computation for one particular implementation of how to *run* one. – pigworker Oct 25 '12 at 00:17
  • 2
    What about `IOT (Just m) >>= k = IOT $ Just $ m >>= maybe (fail "...") id . runIOT . k`? It is bad because `(>>=)` can `fail` (when it shouldn't)? – JJJ Oct 26 '12 at 09:46
  • 3
    Soo... The actual root of the problem is that we can't conjure an `a` out of a `Nothing :: Maybe a` which we could `return` and then `join`, right? Obviously, this would work then for `Identity`, but are there even other monads for which it could possibly work? – Sebastian Graf Sep 01 '16 at 14:09