Need help doing free monad generalization

I’m using free monad to structure my application. I thought that it would be a good idea to generalize the middle layer to make it more reusable. Long story short I want to make this:

data TeletypeF n
  = DbFindPayin (Pg.Query (Pg.Row1 PayinId) (Pg.Row1 Payin)) PayinId (Maybe Payin -> n)
  | DbFindPayout (Pg.Query (Pg.Row1 PayoutId) (Pg.Row1 Payout)) PayoutId (Maybe Payout -> n)
  | DbUpdatePayin ...

instance functorTeletypeF :: Functor TeletypeF where
  map f (DbFindPayin query params next) = DbFindPayin query params (next >>> f)
  map f (DbFindPayout query params next) = DbFindPayout query params (next >>> f)

type Teletype = Free TeletypeF

into something like this (where I can have just 1 constructor for every type of database operation):

data TeletypeF i o e next
  = DbFindOne (Pg.Query i o) i (Maybe o -> next)
  | DbUpdateOne (Pg.Query i o) i next
  | ...

instance functorTeletypeF :: Functor (TeletypeF i o e) where
  map f (DbFindOne query params next) = DbFindOne query params (next >>> f)
  map f (DbUpdateOne query params next) = DbUpdateOne query params (f next)
type Teletype i o e
  = Free (TeletypeF i o e)

The problem is when I try to use it, I get Could not match type Row1 PayoutId with type i'0 errors.
Can I actually parametrize my functor like that to work with different queries? Thanks for any help.

The issue you’ll run into is that the monad has an arity of Type -> Type, which means that all of the type arguments going to TeletypeF (i o e) are locked-into the type before it becomes a monad. In other words, Teletype foo bar baz will not yield the same functor/monad as Teletype bar baz foo, which means you won’t be able to compose them together in useful ways (no bind, no map, etc).

In general, when working with free monads, I find that the functor is useful for modeling effects. In this case, the effect is reading to or writing from the DB, which in general has one type (ie Array DBValue -> Array (Array DBValue)). For more type safety, I’d recommend using a typeclass and make instances for your i & o that marshal them to and from the “unsafe” representation (DBValue or whatever) that then goes to the DB.

1 Like