CRIP: the Curiously Reoccuring Instance Pattern
2012-07-31 01:38:31 GMT
With apologies to Jim Coplien :)
I've been seeing this pattern in a surprising number of instance definitions lately:
instance (a ~ ar, b ~ br) => Mcomp a ar b br [1]
instance (b ~ c, CanFilterFunc b a) => CanFilter (b -> c) a [2]
The trick is that since instance selection is done entirely on the instance head, these instances are strictly more general than the ones they replace:
instance Mcomp a a b b
instance CanFilterFunc b => CanFilter (b -> b) a
The compiler has to do a lot more work to select these instances; it has to prove that the matching types actually match before it can select the instance; if it can't, it won't select an instance, and instead will complain about no instance "CLASS Int a". But with the CRIP, you help the compiler--it chooses the general instance, and then gets a constraint to solve. The constraint forces the two types to unify, or else there is a type error.
What I'm wondering is--are there many cases where you really want the non-constraint-generating behavior? It seems like (aside from contrived, ahem, instances) whenever you have instance CLASS A B where A and B share some type variables, that there aren't any valid instances of the same class where they don't share the types in that way. For example, I've never really seen a class in practice with instances like
class Foo a b
instance Foo a a
instance Foo ConcreteTypeA ConcreteTypeB
Note that it's very difficult to use polymorphic types in the second instance without risking overlap.
TL;DR: I, for one, welcome our new type equality constraint overlords.
-- ryan
[1] http://permalink.gmane.org/gmane.comp.lang.haskell.cafe/99611
[2] http://www.yesodweb.com/blog/2012/07/classy-prelude
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe <at> haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
Yes you did try it: the HList paper gives it as evidence for "We gave up on
persuading Hugs." and "Overlapping Banned" [Section 6, p7]. I think you were
premature, which is why I'm using it as an example.
I've also 'repaired' the example in "Ended up in murky water", and got that
working in Hugs.
Let me say straight away that I'm not trying to 'knock' the work you and Ralf
put into HList. It's a formidable, and formidably impressive piece of work (as
can be seen from the number of papers that reference it). I've built record-
like systems using it.
But the HList paper goes on to abandon overlapping, and bring in (essentially)
the TTypeable approach for type equality. I'm saying (in the discussion a
couple of months ago) that TTypeable is unnecessary, and that overlapping
instances are robust -- except when they get mixed up with FunDeps.
I think instead you should have:
- abandoned FunDeps
- embraced Overlapping more!
> To be sure some HList code did work on Hugs. We even had
> a special subset of HList specifically for Hugs (which I removed from
> the distribution some time ago). The problem was that Hugs
> inexplicably would fail in some other HList code. Perhaps 2006 version
> is better in that respect, and more or all of HList would work on it.
>
Yeah, I'm not trying to 'rehabilitate' Hugs. No point in doing 'compiler
archeology' on whether/when anything got fixed.
To come back to the CRIP Ryan has spotted:
- It got 'blessed' by SPJ in amongst the 'Records in Haskell' proposals [1]
- Certainly equality constraints make the technique easier;
- but TypeCast (in the HList paper) achieved the same thing back in 2004
(and the HList paper discusses earlier approaches for typecast).
- So HList used the technique to get round some of the issues with overlapping.
- I'm saying you could have got round more of the issues.
- And perhaps have got the whole of HList working in Hugs.
So here's my conjecture:
1. We don't need FunDeps, except to define TypeCast.
(And GHC now has equality constraints, which are prettier.)
2. Without FunDeps, overlapping works fine.
3. (Also we don't get into trouble with transitive chains of FunDeps.)
4. Instead of FunDeps, put TypeCasts on all of the instances.
(Or other Class constraints to 'chain' typevars.)
5. And all of the instances must be framed with a bare typevar in the 'result'.
(That is, a typevar distinct from any others in the head.)
6. (As you said to Ryan) we still sometimes need repeated typevars in the head,
for instances that are (in effect) testing for equality.
But these should only be in the 'argument' position.
AntC
[1]
RSS Feed