With injective type families we can now declare that type family result determines the arguments. But ITFs are not yet as expressive as functional dependencies. For example with FDs I can say:
Here we declare that the result and the first argument taken together uniquely determine the second argument. This is not currently possible with ITFs:
I've hit needing this feature for what I was hoping would be pretty elementary code :(
type family ReverseTC (a :: [ k ]) (res :: [ k ] ) = result where ReverseTC '[] res = res ReverseTC (a ': bs ) res = ReverseTC bs (a ': res)type family Reverse (a :: [k]) = (result :: [k]) where Reverse a = ReverseTC a '[]
I would like to explain to GHC that if i know a and result , or res and result, i know the remaining variable, so that my "stack safe" Reverse computation can also be treated as Injective (which it is! )
Carter, can you upload a self-contained piece of code? When we were working on injective type families we had a really hard time finding real-world examples where injectivity was necessary. Many people in the past claimed that they needed it but when we finally had the feature implemented few people could remember their use cases from the past.
I might have a use case for this, but I’m not smart enough to understand if my problem would be solved by this feature or not. Basically, I have a type family for type-level append:
This family is not injective, but if you know the result and one of the two inputs, you can deduce the other one. Therefore, I would like to be able to write the dependency annotation r xs -> ys, r ys -> xs.
I have written a function that uses :++: in its type signature, which currently looks like this:
Currently, this signature is ambiguous. However, notably, we know r, and in the location this function is used, we also know g :++: r. Therefore, my hope is that with this feature, I could use this function, and g could be inferred. Currently, however, I have to use TypeApplications to pick g explicitly, even though the type seems “obvious” to the programmer at its use site (since it very explicitly duplicates type information in an adjacent top-level annotation).
I don’t know enough to know if such an annotation would actually allow such a thing or not, or if this is even applicable in my use case, but I thought I would offer the example anyway, since it sounds like this feature is in need of motivating examples. (I can also try and come up with a smaller, self-contained example if I’m not totally off base and that would, in fact, be relevant/helpful.)
Notice the O.P. for AddTF only considers result & first argument -> second arg.
I see a problem. In instances
type family xs :++: ys = r | r xs -> ys, r ys -> xs where -- both injectivities '[] :++: ys = ys (x ': xs) :++: ys = x ': (xs :++: ys)
Under the injection r ys -> xs, I think GHC will not be able to see the 'apartness' in the two equations. That is, it won't be able to disprove ys ~ (x ': (xs :++: ys)). Yes we know they can't unify, but GHC sees :++: as a Type Function, not a (proto-)constructor.
Speculative remedy proposed here, for the AddTF example.