Here's what I'm trying but it doesn't compile:
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE FlexibleInstances #-}
import Data.Text as T
import Data.Int (Int64)
type family Incoming validationResult baseType
type instance Incoming Validated baseType = baseType
type instance Incoming ValidationErrors baseType = Either [T.Text] baseType
data Validated
data ValidationErrors
data Tag = Tag {unTag :: T.Text} deriving (Eq, Show)
data NewTag f = NewTag
{
ntClientId :: Incoming f Int64
, ntTag :: Incoming f Tag
}
deriving instance (Show baseType) => Show (Incoming Validated baseType)
deriving instance (Show baseType) => Show (Incoming ValidationErrors baseType)
Compilation errors:
23 38 error error:
• Illegal type synonym family application in instance:
Incoming Validated baseType
• In the stand-alone deriving instance for
‘(Show baseType) => Show (Incoming Validated baseType)’ (intero)
24 38 error error:
• Illegal type synonym family application in instance:
Incoming ValidationErrors baseType
• In the stand-alone deriving instance for
‘(Show baseType) => Show (Incoming ValidationErrors baseType)’ (intero)
This can't be made to work. Here's the problem:
Now, if you want a
Show (Either [T.Text] Int)
, you have three options:Any of these would be a valid instance, and GHC requires global uniqueness of instances. Indeed, the problem is that type families aren't injective, and so just because you know that you need an
instance TyCls A
, GHC can't generate the applicationTyFam B1 B2 B3
that would produce anA
– such an application might not even be unique!There are a couple ways you could fix this.
Do you really need the
Show
instance? Maybe all that you need is aShow
constraint on the functions that want to use it. So for example:GHC will propagate those constraints everywhere, but they'll always be satisfiable by the end user. And if
f
is ever a concrete type, they'll vanish entirely!Do you really want
Incoming
things to be indistinguishable from base types? If not, you could use a GADT here:The downside here is twofold: first, you have to pattern-match everywhere you use these; second, you can't (on GHC 7.10, at least) use
StandaloneDeriving
for the GADTShow
instances, you need to write them by hand:Either of these could be a good solution; option (1) is the smallest change from what you're already doing, and so would likely be where I would step first.
One other note: in modern (7.10+) GHCs, we can clean up something in your code. Right now, you have two places your code allows too much flexibility.
NewTag Bool
, orNewTag ()
, or ….Incoming
type family is open – anybody could add atype instance Incoming Bool baseType = Maybe baseType
, orIncoming () () = Int
, or ….You only want to consider
Validated
orValidationErrors
there, and you've already written all the possible type family instances! GHC provides two features for improving this:DataKinds
and closed type families. With closed type families, you can writeNow, this is closed – nobody else can ever add a new case. This solves #2.
As for #1, if we turn on
DataKinds
, GHC automatically promotes our value constructors to the type level! So just as we have thatInt :: *
, we have that'False :: Bool
– the'
indicates to GHC that we're on the type level. Adding this feature looks as follows:We can also add kind signatures if we want –
type family Incoming (validationResult :: ValidationResult) (baseType :: *) :: * where …
ordata NewTag (f :: ValidationResult) = …
, but those will be inferred, and are consequently optional.If the tick really irritates you, you can use the following trick, which I picked up from the GHC source code:
OK, one more type-level fun thing, because I can't resist :-) Let's consider option (1) again, with the type family. We have to provide this annoying
(Show (Incoming f Int64), Show (Incoming f Tag))
constraint everywhere, which is kinda bulky, especially if we want to abstract over it – to produce anEq
instance, it's the same, but withEq
instead ofShow
. And what if there are more fields?If we turn on
ConstraintKinds
, we can abstract over constraints. That works like so:(We need the kind signature so GHC doesn't think this produces an ordinary tuple.) Then we can specify
And everything is much shorter!
Putting this all together, here's what option (1) looks like, with the type family. The only thing that's different about this is that I consolidated the changes I made, reformatted things slightly, and made a few other taste-based changes.
And for completeness, the GADT option:
That need to hand-derive the instances is really dragging it down!
You have two problems here. The first one is what GHC is telling you. Basically, you can't have an instance that depends on a type family (the type family can be there, but only if all the arguments it gets are concrete types). All sorts of bad things can start happening once you allow this, not the least of which is that the right hand side of your type family could have calls to other type families.
Generally, one can solve this sort of problem by moving the type family application to a constraint:
Doing this actually makes the second problem obvious: your instance heads are too general.
That said, I'm not sure there is even anything to fix - just get rid of the deriving lines. You would like the first one to boil down to saying: derive an instance of
Show basetype
given theShow basetype
constraint (which is completely pointless). The second one is equally pointless -Either
already has an instance ofShow
.