I was following Conor McBride's "Kleisli arrows of outrageous fortune" paper and I've posted my implementation of his code here. Briefly, he defines the following types and classes:
type a :-> b = forall i . a i -> b i class IFunctor f where imap :: (a :-> b) -> (f a :-> f b) class (IFunctor m) => IMonad m where skip :: a :-> m a bind :: (a :-> m b) -> (m a :-> m b) data (a := i) j where V :: a -> (a := i) i
Then he defines two types of binds, the latter of which uses (:=)
to restrict the initial index:
-- Conor McBride's "demonic bind" (?>=) :: (IMonad m) => m a i -> (a :-> m b) -> m b i (?>=) = flip bind -- Conor McBride's "angelic bind" (>>=) :: (IMonad m) => m (a := j) i -> (a -> m b j) -> m b i m >>= f = bind (\(V a) -> f a) m
The latter bind works perfectly fine for rebinding do
notation to use indexed monads with the RebindableSyntax
extension, using the following corresponding definitions for return
and fail
:
return :: (IMonad m) => a -> m (a := i) i return = skip . V fail :: String -> m a i fail = error
... but the problem is that I cannot get the former bind (i.e. (?>=)
) to work. I tried instead defining (>>=)
and return
to be:
(>>=) :: (IMonad m) => m a i -> (a :-> m b) -> m b i (>>=) = (?>=) return :: (IMonad m) => a :-> m a return = skip
Then I created a data type guaranteed to inhabit a specific index:
data Unit a where Unit :: Unit ()
But when I try to rebind do
notation using the new definitions for (>>=)
and return
, it does not work, as demonstrated in the following example:
-- Without do notation test1 = skip Unit >>= \Unit -> skip Unit -- With do notation test2 = do Unit <- skip Unit skip Unit
test1
type-checks, but test2
does not, which is weird, since I thought all that RebindableSyntax
did was let do
notation desugar test2
to test1
, so if test1
type-checks, then why does not test2
? The error I get is:
Couldn't match expected type `t0 -> t1' with actual type `a0 :-> m0 b0' Expected type: m0 a0 i0 -> (t0 -> t1) -> m Unit () Actual type: m0 a0 i0 -> (a0 :-> m0 b0) -> m0 b0 i0 In a stmt of a 'do' block: Unit <- skip Unit In the expression: do { Unit <- skip Unit; skip Unit }
The error remains even when I use the explicit forall
syntax instead of the :->
type operator.
Now, I know there is another problem with using the "demonic bind", which is that you can't define (>>)
, but I still wanted to see how far I could go with it. Can anybody explain why I cannot get GHC to desugar the "demonic bind", even when it would normally type-check?
IIUC, the GHC desugarer actually runs after the typechecker (source). That explains why the situation you observe is theoretically possible. The typechecker probably has some special typing rules for the do-notation, and those may be inconsistent with what the typechecker would do with the desugarred code.
Of course, it's reasonable to expect them to be consistent, so I would recommend filing a GHC bug.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With