I’m trying to construct a function of type:
liftSumthing :: ((a -> m b) -> m b) -> (a -> t m b) -> t m b
where t is a monad transformer. Specifically, I’m interested in doing this:
liftSumthingIO :: MonadIO m => ((a -> IO b) -> IO b) -> (a -> m b) -> m b
I fiddled with some Haskell wizardry libs and but to no avail. How do I get it
right, or maybe there is a ready solution somewhere which I did not find?
This can’t be done generically over all
MonadIOinstances because of theIOtype in a negative position. There are some libraries on hackage that do this for specific instances (monad-control, monad-peel), but there’s been some debate over whether they are semantically sound, especially with regards to how they handle exceptions and similar weirdIOy things.Edit: Some people seem interested in the positive/negative position distinction. Actually, there’s not much to say (and you’ve probably already heard it, but by a different name). The terminology comes from the world of subtyping.
The intuition behind subtyping is that “
ais a subtype ofb(which I’ll writea <= b) when anacan be used anywhere abwas expected instead”. Deciding subtyping is straightforward in a lot of cases; for products,(a1, a2) <= (b1, b2)whenevera1 <= b1anda2 <= b2, for example, which is a very straightforward rule. But there are a few tricky cases; for example, when should we decide thata1 -> a2 <= b1 -> b2?Well, we have a function
f :: a1 -> a2and a context expecting a function of typeb1 -> b2. So the context is going to usef‘s return value as if it were ab2, hence we must require thata2 <= b2. The tricky thing is that the context is going to be supplyingfwith ab1, even thoughfis going to use it as if it were ana1. Hence, we must require thatb1 <= a1— which looks backwards from what you might guess! We say thata2andb2are “covariant”, or occur in a “positive position”, anda1andb1are “contravariant”, or occur in a “negative position”.(Quick aside: why “positive” and “negative”? It’s motivated by multiplication. Consider these two types:
When should
f1‘s type be a subtype off2‘s type? I state these facts (exercise: check this using the rule above):e1 <= e2.d2 <= d1.c2 <= c1.b1 <= b2.a2 <= a1.e1is in a positive position ind1 -> e1, which is in turn in a positive position in the type off1; moreover,e1is in a positive position in the type off1overall (since it is covariant, per the fact above). Its position in the whole term is the product of its position in each subterm: positive * positive = positive. Similarly,d1is in a negative position ind1 -> e1, which is in a positive position in the whole type. negative * positive = negative, and thedvariables are indeed contravariant.b1is in a positive position in the typea1 -> b1, which is in a negative position in(a1 -> b1) -> c1, which is in a negative position in the whole type. positive * negative * negative = positive, and it’s covariant. You get the idea.)Now, let’s take a look at the
MonadIOclass:We can view this as an explicit declaration of subtyping: we are giving a way to make
IO abe a subtype ofm afor some concretem. Right away we know we can take any value withIOconstructors in positive positions and turn them intoms. But that’s all: we have no way to turn negativeIOconstructors intoms — we need a more interesting class for that.