I'm not sure if you want universal or existential quantification. Either way, best to wrap it in a fresh type.
Strong recommendation: don't use constraint heads on ordinary data types. They will make your life harder, not easier. They accomplish nothing useful.
Existential
{-# LANGUAGE GADTs #-}
data Container' params model = Container'
{ baseparams :: params
, basemodel :: model
}
data Container p m where
Container :: Model params model => Container' params model -> Container params model
Universal
{-# LANGUAGE Rank2Types #-}
data Container' params model = Container'
{ baseparams :: params
, basemodel :: model
}
newtype Container model = Container (forall params . Model params model => Container' params model)
You can not have a universal or qualified type in a type class instance. So
instance Model (ContainerParams params) (Container model)
is not permitted since the type synonym expands to
instance Model (ContainerParams params) (forall ...)
In my GADT solution, I treated both param
and model
as parameters. This is because of something important to know: functional dependencies are not confluent! And, the compiler does not assume them to be confluent for the purposes of type checking. Functional dependencies are only useful in guiding the constraint solver (in a way they resemble extra logical constructs like "cuts" in prolog). If you want confluence, use TypeFamilies
.
class Model model where
type Param model
...
Or the awesome way of doing it
class (model ~ (TheModel param),param ~ (TheParam model)) => Model model param where
type TheModel param
type TheParam model
which has the same bidirectionally as the fundep. And, which would then let you write your existential instance as
data Container model where
Container :: Model model param => Container' model param -> Container model
and then you can do things like combine two containers with the same model
type, knowing that the existentially quantified params
will match. Using this, you can define
data HasParam model where
HasParam :: Model model param => HasParam model
data GADTContainer model where
GADTContainer :: Model model param => Container' model param -> GADTContainer model
newtype NewContainer model
= NewContainer (forall param. Model model param => Container' model param)
and then the tuple (HasParam model, NewContainer model)
is provably isomorphic to GADTContainer model
which also explains the relationship between these types
Anyway, once you have that taken care of, you can define your instance just using the appropriate wrapped type.