1
votes

I'd like to write a type that I can parameterize by its monad transformer. I've tried several things, and ended up with something along these lines:

newtype Foo t a = Foo { unFoo :: t (State St) a }

Then, I'd like to a bunch of instances:

deriving instance (MonadTrans t, Monad (t (State St))) => Monad (Foo t)

Finally, I instantiate the type with an appropriate monad transformer. My code typechecks, but I get warnings for all of my derived instances: "no explicit implementation for >>=". If I try running it, I get undefined errors when it hits type class functions.

Is there a way to automatically derive all the instances I need? I am trying to derive a lot more classes than just Monad.

I'm also interested in a more ergonomic way to accomplish this same thing -- I have to keep manually wrapping and unwrapping the Foo constructor, particularly when I try to lift a computation into it, since I can't figure out how to implement a MonadTrans instance for it.

1
should it be Foo {unFoo :: t (State St) a}?leftaroundabout
oh yep, definitely, fixed.trpnd
Do you have DeriveAnyClass enabled, or GeneralizedNewtypeDeriving or both?Fyodor Soikin

1 Answers

6
votes

Monad is not a stock derivable class, like Eq or Show. Neither is Functor, incidentally, but that one you can actually enable via DeriveFunctor. But no such luck for Monad.

But wait, your derivation actually works somehow, right? No errors?

That's probably because you have DeriveAnyClass enabled, which is a handy little extension, but not a particularly clever one. It can't do the heavy lifting for you, all it does is produce an empty instance declaration. No method implementations at all.

Wait, what's the use of that then? Because default methods! Many classes provide default method implementations for any type, or at least with some constraints. The ones I've met most frequently are FromJSON and ToJSON, which have default implementations of everything, provided your type has Generic.

And DeriveAnyClass is just a slight shortening of the syntax, that's all. A handy thing so you can write data Foo = ... deriving Bar, relying on all Bar's default methods.

But Monad doesn't have default implementation for >>=, and that's why you get a warning. And, of course, a crash once you try to call that method.


What you really want is GeneralizedNewtypeDeriving. This extension also allows you to automatically derive any class, but only for newtypes, and it works by just delegating each method to the wrapped type's implementation. Very handy.

This is exactly what you need: all method implementations for Foo will be delegated to those for t.


In your code, I'm assuming from your problem description, you either don't have GeneralizedNewtypeDeriving enabled at all, or else you have both DerivedAnyClass and GeneralizedNewtypeDeriving enabled, in which case DeriveAnyClass takes precedence (surprise!)

In order to fix the problem, you can either disable DeriveAnyClass, or, better yet, use DerivingStrategies. With that extension enabled, you can explicitly tell the compiler which strategy to use for each derivation - DeriveAnyClass or GeneralizedNewtypeDeriving:

{-# LANGUAGE DerivingStrategies #-}

deriving newtype instance (MonadTrans t, Monad (t (State St))) => Monad (Foo t)
         ^^^^^^^
            |
    this is the important bit

P.S. if both extensions are enabled, you should also get a warning about that.