1

There seems to be some undocumented knowledge about the difference between Monad IO and IO. Remarks here and here) hint that IO a can be used in negative position but may have unintended consequences:

Citing Snoyman 1:

However, we know that some control flows (such as exception handling) are not being used, since they are not compatible with MonadIO. (Reason: MonadIO requires that the IO be in positive, not negative, position.) This lets us know, for example, that foo is safe to use in a continuation-based monad like ContT or Conduit.

And Kmett 2:

I tend to export functions with a MonadIO constraint... whenever it doesn't have to take an IO-like action in negative position (as an argument).

When my code does have to take another monadic action as an argument, then I usually have to stop and think about it.

Is there danger in such functions that programmers should know about?

Does it for example mean that running arbitrary continuation-based action may redefine control flow giving unexpected results in ways that Monad IO based interface are safe from?

sevo
  • 4,559
  • 1
  • 15
  • 31
  • 2
    No danger. Just can't be done. [Possible duplicate.](https://stackoverflow.com/q/9243215/791604) – Daniel Wagner Jan 26 '19 at 14:41
  • What cannot be done? Having `IO a` in negative position? I might be wording the question incorrectly. I upvoted that answer as it helped me a bit but I couldn't understand it. – sevo Jan 26 '19 at 17:26
  • No, generalizing a term which has a type that mentions `IO` in a negative position to a type that works for any `MonadIO` instance is what cannot be done. – Daniel Wagner Jan 26 '19 at 17:37
  • But I don't want to generalize. I want to keep `IO a` in negative position and I'm asking why it seems to be discouraged. – sevo Jan 26 '19 at 17:40
  • Nothing discouraged about that. You've misunderstood the commentary. – Daniel Wagner Jan 26 '19 at 17:47

1 Answers1

3

Is there danger in such functions that programmers should know about?

There is not danger. Quite the opposite, the point Snoyman and Kmett are making is that Monad IO doesn't let you lift through things with IO in a negative positive.

Suppose you want to generalize putStrLn :: String -> IO (). You can, because the IO is in a positive position:

putStrLn' :: MonadIO m => String -> m ()
putStrLn' str = liftIO (putStrLn str)

Now, suppose you want to generalize handle :: Exception e => (e -> IO a) -> IO a -> IO a. You can't (at least not with just MonadIO):

handle' :: (MonadIO m, Exception e) => (e -> m a) -> m a -> m a
handle' handler act = liftIO (handle (handler . unliftIO) (unliftIO act))

unliftIO :: MonadIO m => m a -> IO a
unliftIO = error "MonadIO isn't powerful enough to make this implementable!"

You need something more. If you're curious about how you'd do that, take a look at the implementation of functions in lifted-base. For instance: handle :: (MonadBaseControl IO m, Exception e) => (e -> m a) -> m a -> m a.

Alec
  • 31,829
  • 7
  • 67
  • 114
  • Suppose I don't want to generalize and keep `IO a`. So far, it seems that it can do things that `MonadIO` version cannot. Why is that important? – sevo Jan 26 '19 at 17:38
  • @sevo I'm not sure I understand. "it seems that it can do things that MonadIO version cannot" sounds like you are comparing to something that _is_ generalized. Do you have a more concrete example in mind? – Alec Jan 26 '19 at 17:40
  • 1
    @sevo I think i understand now: using `IO a` in a negative position is _not_ discouraged. Snoyman and Kmett both care about writing code that works over monad transformers, so they care about being able to write the generalized versions of functions, and that isn't really possible without some stronger abstraction than `MonadIO`. – Alec Jan 26 '19 at 17:49
  • That makes sense! But! Are we opting for less general abstraction because of language deficiency or because it's the right thing to do? Snoyman seems to argue for the latter. – sevo Jan 26 '19 at 18:16
  • 1
    @sevo Bit of both. Here's [Snoyman laying it out](https://www.reddit.com/r/haskell/comments/67tc1w/monadio_vs_monadbase_io_in_library_apis/dgt5eiu/). `MonadBaseControl` uses type families and multi parameter type classes. `MonadIO` is plain old Haskell98. – Alec Jan 26 '19 at 18:20