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
MonadIO
instances because of theIO
type 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 weirdIO
y 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 "
a
is a subtype ofb
(which I'll writea <= b
) when ana
can be used anywhere ab
was expected instead". Deciding subtyping is straightforward in a lot of cases; for products,(a1, a2) <= (b1, b2)
whenevera1 <= b1
anda2 <= 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 -> a2
and 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 supplyingf
with ab1
, even thoughf
is 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 thata2
andb2
are "covariant", or occur in a "positive position", anda1
andb1
are "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
.e1
is in a positive position ind1 -> e1
, which is in turn in a positive position in the type off1
; moreover,e1
is in a positive position in the type off1
overall (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,d1
is in a negative position ind1 -> e1
, which is in a positive position in the whole type. negative * positive = negative, and thed
variables are indeed contravariant.b1
is 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
MonadIO
class:We can view this as an explicit declaration of subtyping: we are giving a way to make
IO a
be a subtype ofm a
for some concretem
. Right away we know we can take any value withIO
constructors in positive positions and turn them intom
s. But that's all: we have no way to turn negativeIO
constructors intom
s -- we need a more interesting class for that.