Injective type families

Jan Stolarek jan.stolarek at p.lodz.pl
Thu Jul 10 14:37:36 UTC 2014


Hi all,

I'd like to take a stab at implementing injective type families (#6018). My plan of attack looks 
like this:

1. Implement injective type families that are:
   a) injective in all arguments
   b) only admit RHS that is a concrete type or a type variable introduced by the LHS or a 
recursive call to self.
2. Lift restriction a) ie. allow type families injective only in some arguments
3. Lift restriction b) ie. allow injective type families to call other type families.

I'm not sure if 3) really gets implemented as this seems more in lines of #4259, which I don't 
intend to approach.

Now, let's discuss the most important of all matters - the syntax! :-) Note that syntax must:
1. allow to define injectivity only for some parameters
2. work for both open and closed type families

Here's my proposal (based on Richard's idea to use functional-dependencies-like syntax):

1. Standard injective type family (all parameters uniquely determined by the RHS):

   injective type family F a b c | a b c
   type family instance F Int  Char Bool = Bool
   type family instance F Char Bool Int  = Int
   type family instance F Bool Int  Char = Char

2. Type family injective only in some parameters (ie. only some parameters uniquely determined by 
the RHS):

   injective type family G a b c | a b
   type family instance G Int  Char Bool = Bool
   type family instance G Int  Char Int  = Bool
   type family instance G Bool Int  Int  = Int

   Here knowing the RHS allows us to determine a and b, but not c.

3. Type families where knowing the RHS and some parameters on the LHS makes other parameters 
injective:

   Example 1: knowing the RHS and any single parameter uniquely determines other parameters

   injective type family H a b c | a -> b c, b -> a c, c -> a b
   type family instance H Int  Char   Double = Int
   type family instance H Bool Double Char   = Int

   Example 2: knowing the RHS and either a or b allows to uniquely determine other parameters, but 
knowing the RHS and c gives us no information about a or b

   injective type family J a b c | a -> b c, b -> a c
   type family instance J Int  Char   Double = Int
   type family instance J Bool Double Double = Int

For closed type families this notation would be identical: type family declaration would be 
followed by the "where" keyword.

In comment 34 of #6018 Richard proposed a notation that uses a keyword "Result". This would allow 
for a more uniform notation. Notice that in my proposal some families use ->, while others don't. 
The idea is that on the right side of the arrow we write what we can infer from the things on the 
left of the arrow. If the only thing left of the arrow is the right hand side of the type family 
equation (the result) then the arrow can be elided. In other words these would be equivalent:

   injective type family F a b c | a b c
   injective type family F a b c | result -> a b c

   injective type family G a b c | a b
   injective type family G a b c | result -> a b
   (note: it is also true that "injective type family G a b c | result c -> a b")

   injective type family H a b c | a -> b c, b -> a c, c -> a b
   injective type family H a b c | result a -> b c, result b -> a c, result c -> a b

   injective type family J a b c | a -> b c, b -> a c
   injective type family J a b c | result a -> b c, result b -> a c

I find the notation explicitly using "result" to be easier to understand, but it is also more 
verbose. I also suspect that it might be a bit easier to parse. Which of the notations do you 
find better? What do you think about using the "injective" keyword? Are there any alternative 
proposals?

Janek


More information about the ghc-devs mailing list