Compilation Error with Free and Inject


i’m currenly playing around with Free and Inject to define a composable algebra. It works well in simple examples, but it seems break when i try to lift another Monad into my algebra. I know there is purescript-run which solves the same problem with the help of row polymorphism, but i was curious about why my inject approach fails to compile.

This is the code i have so far.

I wonder why myApp does not compile.

thx a lot,

I think the issue is that you don’t define an instance of the Inject type class for LiftF. You’re using the Inject (LiftF m) (LiftF m) constraint, but how would the compiler solve it?

Hey Jordan,

no, i don’t think that’s the problem here. The code from my gist compiles fine, it’s only the 2 commented out lines containing myApp which do not compile.

This is the Inject instance that should be used in both cases (myApp' and myApp):

I got it working: by changing

lift :: forall f m a. (Functor m) => (Inject (LiftF m) f) => m a -> Free f a
lift ma = do
  liftF $ inj $ Lift ma


lift :: forall f m a. (Functor m) => (Inject (LiftF m) (f m)) => m a -> Free (f m) a
lift ma = do
  liftF $ inj $ Lift ma

I understand that the original problem derived from the fact that the type variable m was ambiguous because it was only part of the constraint and not part of the Free type itself.

I actually wonder if the Inject class without allowing overlapping Instances is actually useful?

Consider the type

type AppF m = Coproduct ConsoleF (Coproduct LogF (LiftF m))

And a function like this

app' :: forall m . 
  Inject ConsoleF (AppF m) 
  => Inject LogF (AppF m)
  => Inject (LiftF m) (AppF m) 
  => Free (AppF m) Unit

if we want to abstract this function like this:

app :: forall f m . 
  Inject ConsoleF (f m) 
  => Inject LogF (f m) 
  => Inject (LiftF m) (f m) 
  => Free (f m) Unit

It correctly fails to compile when used like

app::Free (AppF m) Unit

because AppF is just an alias where f unifies with Coproduct ConsoleF and m with (Coproduct LogF (LiftF m)). This is obviously not what we want here.

This is where a newtype + a new inject instance would help to solve this problem. But without Overlapping instances there is no way to create an Inject instance for that newtype. The only solution is to create your own Inject class without instance chains.

But maybe i’m missing something…