This is a follow up to my previous question. I recieved some good answers there, but, because of the way I simplified my actual problem, I think I misled the answerers, and I hope to remedy that here.
TL;DR I have a typeclass Category
from constrained-categories
which constrains the domain/codomain of it's Category
s using a TypeFamily ("constraint family") called Object
. I would like to make a Free Category
, but am struggling to get proofs that expressions satisfy the Object
constraint.
Consider my Free
data type, and its constrained-categories.Category
instance:
data Free p a b where
Id :: Object p a => Free p a a
Comp :: Object p b => Free p b c -> Free p a b -> Free p a c
instance Category (Free p) where
type Object (Free p) a = Object p a -- passes through the constraints from instantiated `p`
id = Id
(.) = Comp
To explain a bit more, let's also consider a non-free Category
I might like to eval
to:
newtype MyArr a b = MyArr (a -> b)
instance Category MyArr where
type Object MyArr a = () -- trivial, no constraints needed
id = MyArr P.id
MyArr g . MyArr f = MyArr (g . f)
I would like to write an expression like this (which is obviously far simpler than expressions I'll be writing in practice):
-- error: Could not deduce: Object p Int arising from a use of ‘Id’
e0 :: Free p Int Int
e0 = Id
I can fix this the obvious way, but this becomes verbose for larger expressions. Imagine composing functions of tuples, and needing to explicitly provide an Object p _
instance for every interim step in the composition:
e1 :: Object p Int => Free p Int Int
e1 = Id
I could choose to not abstract the Category p
, and, that works:
e2 :: Free MyArr Int Int
e2 = Id
But I'd like to abstract it. I should think adding a constraint Category p
should work, and bring into scope its constraint that type Object (Free p) a = Object p a
, and give me any Object p _
instance I need, but alas, it does not work.
-- error: Could not deduce: Object p Int arising from a use of ‘Id’
e3 :: Category p => Free p Int Int
e3 = Id
It seems to me that QuantifiedConstraints
might help, eg forall a. Object (Free p) a => Object p a
, but you can't have a TypeFamily
in the head of the predicate.
I've also considered using a type Object p :: * -> Constraint
like in Conal Elliot's concat library, but, that would require a different library, a different Category
class, and, last time I used categories constrained that way, it seemed a bit more cumbersome and I'm not even sure if that would solve my boilerplate issue. A comment in that library suggests the usefulness of QuantifiedConstraints
, but, I'm pretty twisted up at this point and don't know in which direction to trudge forward.
Runnable
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE TypeFamilies #-}
module ConstrainedCategoryFreeArrow3 where
import Prelude hiding (id, (.))
import qualified Prelude as P
import Control.Category.Constrained
-- * A Free 'Category'
data Free p a b where
Id :: Object p a => Free p a a
Comp :: Object p b => Free p b c -> Free p a b -> Free p a c
instance Category (Free p) where
type Object (Free p) a = Object p a -- passes through the constraints from instantiated `p`
id = Id
(.) = Comp
eval :: (Category p, Object p a, Object p b) => Free p a b -> p a b
eval Id = id
eval (Comp g f) = eval g . eval f
-- * A specific (trivial) 'Category'
newtype MyArr a b = MyArr (a -> b)
instance Category MyArr where
type Object MyArr a = () -- trivial, no constraints needed
id = MyArr P.id
MyArr g . MyArr f = MyArr (g . f)
-- * A generic expression
-- -- error: Could not deduce: Object p Int arising from a use of ‘Id’
-- e0 :: Free p Int Int
-- e0 = Id
-- works, but becomes verbose. Imagine for instance building an expression with
-- functions of tuples; you would need to provide an incredible amount of
-- boilerplate constraints for each permutation of types in tuples used anywhere
-- in the body. The problem is a little worse than this once you account for
-- `Cartesian` which has a Constraint Family called `PairObjects` where you need
-- to prove that each tuple is an allowed product in the current constrained
-- category.
e1 :: Object p Int => Free p Int Int
e1 = Id
-- works, but is no longer abstract in the category `p`
e2 :: Free MyArr Int Int
e2 = Id
-- -- ideal solution, but alas
-- -- error: Could not deduce: Object p Int arising from a use of ‘Id’
-- e3 :: Category p => Free p Int Int
-- e3 = Id