Closed Type Families: separate instance groups?

Currently (GHC 7.8.3) the only form for Closed Type Families is: type family F a where ... -- list your instances here (This was considered a common use case -- for example in HList to put the type-matching instance with the non-matching, and that would be total coverage; rather than needing a type family decl and an instance decl with the instance head same as family. That was an optimisation over ...) Way back the design was more like this: type family F a type instance F (Foo b c) where F (Foo Int c) = ... F (Foo b Char) = ... type instance F (Bar e f g) where F (Bar Int f g) = ... The idea was that the separate instance groups must have non-overlapping heads. This is handy if Foo, Bar, etc are declared in separate places/modules. You can put the instances with the data decl. And quite possibly the family decl is in an imported/library module you don't want to touch. Is this separate instance group idea still a gleam in someone's eye? If not, is there some deep theoretical reason against? AntC

On Jun 3, 2015, at 7:09 PM, AntC
Is this separate instance group idea still a gleam in someone's eye? If not, is there some deep theoretical reason against?
Not to my knowledge (to both questions). But I don't believe we've lost any expressiveness over the earlier version. You can always define a helper closed type family and have an open type family instance just call a closed type family. Of course, it would be nice to have *local* type families (as if, say, there were a `where` clause allowed), but this should work for you. Or does this not work in your use case? Having closed type families, as opposed to branched instances, just seemed like a cleaner way to package the new functionality. There really wasn't much to it other than aesthetics, if I recall the conversations correctly. Richard

Richard Eisenberg
writes: You can always define a helper closed type family and have an open type family instance just call a closed type family.
Thank you Richard, you mean like: type family OpenF a ... type instance OpenF (Foo b c) = FFoo (Foo b c) type family FFoo a where FFoo (Foo Int c) = ... ... OK. (Seems rather verbose.)
Having closed type families, as opposed to branched instances, just seemed like a cleaner way to package the new functionality. There really wasn't much to it other than aesthetics, if I recall the conversations correctly.
I recall the conversation quite well. (In fact I think it was me who suggested type family ... where ... ) I think it was less to do with aesthetics, and more to do with reducing verbosity in a common use case. It somehow doesn't seem as clean as old-fashioned overlapping instances. (I agree it does seem cleaner than overlaps with FunDeps.) It also BTW cuts us off from using Closed Families as Associated types separated into their Class instances. I think there's two use cases going on: - one where we want to see all the instances together that fits well to type family ... where ... - t'other where we want everything to do with a type constructor together that fits better with the separate instances AntC

I think it's pretty good as-is.
* Use an open family (with non-overlapping instances) to get yourself
into part of the match space:
type instance OpenF (Foo b c) = FFoo (Foo b c)
* Use a closed family (with overlap and top-to-bottom matching) to
deal with that part of the space:
type family FFoo a where
FFoo (Foo Int c) = ...
Doing this was a HUGE improvement, allowing us to cleanly split the
issues of top-to-bottom matching from those of non-overlapping open
families.
| It also BTW cuts us off from using Closed Families as Associated types
| separated into their Class instances.
I don't understand this comment. Can you give an example that the current setup does not handle?
Simon
| -----Original Message-----
| From: Glasgow-haskell-users [mailto:glasgow-haskell-users-
| bounces@haskell.org] On Behalf Of AntC
| Sent: 04 June 2015 01:54
| To: glasgow-haskell-users@haskell.org
| Subject: Re: Closed Type Families: separate instance groups?
|
| > Richard Eisenberg

Simon Peyton Jones
writes: I think it's pretty good as-is.
Thank you Simon, I'm agreeing with "pretty good", though possibly not with "pretty" ;-)
... * Use a closed family (with overlap and top-to-bottom matching) to deal with that part of the space:
Doing this was a HUGE improvement, ...
(I'm not quite getting improvement over what? This was and is the only way to do overlaps with Type Families?) I'm not ever-so sure I'm seeing an improvement over overlapping class instances with FunDeps. I really really want type families to be an improvement because type manipulation in a functional language should be -- errm -- functional.
| It also BTW cuts us off from using Closed Families as Associated types | separated into their Class instances.
I don't understand this comment. ...
I'll answer Richard's strapline at https://typesandkinds.wordpress.com/ "Who needs terms, anyway?": I need both types and terms. Yes the compiler needs first a type-solving phase before dealing with the terms. Type Families cleanly separate that off. And in a significant proportion of use cases, the type-handling is the same across many class instances. So it's more succinct to collapse the type instances into a grouped type family ... where ... There's other use cases for overlapping where you can't collapse the type-handling. So then I'm finding that my class instances have heads that repeat the type instance heads. And I would use Assoc types but the type family instances have to appear in the family, to sequence the top-to-bottom matching.
Can you give an example that the current setup does not handle?
(This is about dealing with many instances, so difficult to give a succinct example. And the current setup does handle it OK. It's just that it seems verbose, with hard to read code, compared to FunDeps. I appreciate that's in the eye of the beholder.) Take the standard example for partial overlaps. Suppose I have a class: class C a where f :: a -> F a instance C (Foo Int c) where -- I'd like to put type F (Foo Int b) = Int -- but it overlaps f (Foo x _) = x instance C (Foo b Char) where type F (Foo b Char) = Char -- non-confluent f (Foo _ y) = y Imagine there's dozens of overlapping instances. (And BTW there's no actual ambiguous usages. By construction (Foo Int b) means b /~ Char. But I have no way to declare that.) I'm also getting (in more complex examples) GHC complaining it can't infer the types for the result of f. So now I'm having to put type equality constraints on the class instances, to assure it that F comes up with the right type. This just seems easier if I have the result type as an extra param to the class, with a FunDep in the classic style: class C a b | a -> b where f :: a -> b (I can supply those more complex examples if need be, but this post is already too long.) AntC
participants (3)
-
AntC
-
Richard Eisenberg
-
Simon Peyton Jones