Re: [Haskell-cafe] [ghc-proposals/cafe] Partially applied type families

On Wed May 24 02:33:46 UTC 2017, Richard Eisenberg wrote:
... Thanks for getting me to think about all this in a new way!
Thanks Richard, yes I think we have large areas of agreement. So I'll cut to where we differ.
On May 15, 2017, at 9:26 PM, Anthony Clayden wrote: "Multidirectional type-level reasoning" is what all functional languages do: from Robinson's unification to Hindley-Milner to OutsideIn(X) to System F-subscript-superscript.
I disagree here: the dependently typed languages don't quite do this.
Then I guess I remain to be convinced of the value of dependent typing within Haskell.
...These languages still do multi-directional type inference,
I guess you mean as needed for superclass constraints and instance constraints, so perhaps there's no practical difference.
The backwards-compatibility I particularly want to preserve is "distributed instances". That is, putting the instance code close to type decls and other functions that build or consume that type.
I think one other key difference between our viewpoints is that I view the whole idea of "type instance" to be a bit backward: I view type families as type-level functions. Like other functions, I want the defining equations to be all in one place.
I'd like term-level functions to be more like instances. That is, with the equations distributed, see your example below. (Yes the set of equations must be coherent overall. That's something the compiler can check for me.)
This isn't to argue that my viewpoint is right or that other viewpoints are wrong. It's just a way to understand why we both see things the way we do.
Yes.
I'd argue it's simpler to understand each instance on its own grounds.
Permit me to rewrite your sentence to something that resonates better with me:
I'd argue it's simpler to understand each *equation* on its own grounds.
I agree here, but it's a bit subtler. We aren't confused when we see
f [] = False f _ = True
Oh, yes I think a lot of people are confused. I think they expect the second equation is equivalent to:
f (x:xs) = True
Maybe even
f ~(x:xs) = True
But in the presence of bottom, that's a dangerous delusion. I'd say: if you want the True result to mean there's a non-empty list, then put that explicitly. And then we have two equations that are 'apart'. And they don't need to be sequential. Furthermore that delusion is doubly dangerous for type equations, because GHC knows nothing of closed Data Kinds.
There was a huge and rather repetitive (unfortunately) discussion in several rounds 2009~2012,
These dates might help to explain why I may be repeating old arguments. I became active in the community somewhere
in 2012.
No, you're not repeating old arguments. Type Families, and esp. CTFs really came later. IIRC, at the time, there was a lot of suggestions, but not much push-back from anybody disagreeing. There was some doubt whether there could be a type-level disequality test. Chiefly GHC HQ just ignored the whole thing. Somebody at the time did try to write it up (viz. moi) https://ghc.haskell.org/trac/ghc/wiki/NewAxioms/DiscussionPage And someone from GHC HQ (viz. you) did acknowledge it rather later.
I'm a little unsure what you mean at the top of the post about the "idea" of FunDeps vs their specific syntax.
By "idea", I mean traits like multidirectionality and attachment to classes. I maintain that the function-application syntax of type families is better than the relational syntax of fundeps.
OK I think you're saying that a class decl with superclass (~) constraints to type functions is preferable/more aesthetic. I agree. Furthermore overlap interferes with fundeps (or perhaps I mean v.v.) in horrible ways.
...I'm not sure we need to allow ~ guards. (I think allowing them would cause confusion, with many people asking about the difference between an equality constraint and an equality guard.
Yes I can see the dangers of confusion. We could of course use a different operator.
... They're different, but I think the equality guard is useless
I see two main uses/benefits: 1. avoids the need for non-linear patterns in the head 2. visually points up the 'non-apartness' vs an, um. "apartness guard" (When I was figuring out Andy A-M's 2013 example, I rewrote the non-linear patterns with ~ guards, so I could figure out the apartness guards esp for the semi-apart instances. I posted the later workings to your blog https://typesandkinds.wordpress.com/2013/04/29/coincident-overlap-in-type-fa... )
[except as a type-level "let", I suppose].)
No, they're not doing that: deliberately I don't allow guards to introduce new type vars.
... partial type families ... Of course, when you protect a partial type family by a class constraint, then it's not troublesome: that's the point of the Constrained Type Families paper.
Yes I got that loud and clear, and I agree. So the remaining issue is _how_ to protect them.
Sometimes I deliberately want there to be no instance for some specific pattern.
Indeed yes. And now you can, with TypeError (https://ghc.haskell.org/trac/ghc/wiki/Proposal/CustomType Errors, which is now available in GHC 8).
Ok thanks, yes that helps. Nevertheless it seems disergonomic to write an instance for a type-equal case, even with a helpful error message; when what you want is: no such instance.
Bottom line: You win. I'm convinced. Apartness guards (feel free to fight me on the name here -- I don't feel strongly, but somehow I like "apartness guards" more than "disequality guards") subsume OverlappingInstances (when the author of the instances has a particular desired way to resolve overlap) and closed type families. I think that the syntax of closed type families is often what the user wants, but I admit that CTFs don't cover all scenarios, especially in the context of Constrained Type Families. So I think we should keep CTFs (for now -- until Dependent Haskell is here) but add this new syntax as well, which would subsume the newly-proposed closed type classes.
I've been (mostly in my head) calling them "Instance Guards", because I want both an eq guard and an apartness guard.
Even better, I think apartness guards would be very easy to implement. All the plumbing is already installed: we basically just have to add a switch.
Yes that's what I guessed from your write-up of CTFs and "coincident overlap". What I can see will be awkward is backwards compatibility: can a class/type family with guards coexist with CTFs and with classes using overlap?
Next step: write a ghc-proposal. I'll support it. If I had the time, I'd even implement it.
I'll have a crack at the proposal. But implementing it is entirely beyond me. So thanks for saying "you win". But I'm not celebrating 'til something actually happens. (Been bitten too many times.) AntC

On Wed, May 24, 2017 at 3:13 AM, Anthony Clayden
On Wed May 24 02:33:46 UTC 2017, Richard Eisenberg wrote:
I agree here, but it's a bit subtler. We aren't confused when we see
f [] = False f _ = True
Oh, yes I think a lot of people are confused. I think they expect the second equation is equivalent to:
f (x:xs) = True
Maybe even
f ~(x:xs) = True
But in the presence of bottom, that's a dangerous delusion.
I would argue those are equivalent. To get to the second equation, you
would have had to reject the first equation, so at that point you
already know the argument isn’t bottom.
--
Dave Menendez

On May 24, 2017, at 3:13 AM, Anthony Clayden
wrote: Then I guess I remain to be convinced of the value of dependent typing within Haskell.
The existing dependently typed languages (I'm thinking of Coq, Adga, and Idris -- just not as familiar with F* and perhaps others) do type inference on a best-effort basis, with very little that I have found in the literature describing the type inference process. One of the major challenges of dependent types in Haskell is to continue Haskell's tradition of predictable, describable type inference. In other words, adding dependent types needn't take anything away from the fragment of Haskell you wish to work in. Indeed, the lack of injectivity for type families is the same kind of problem that exists in those other languages, so Haskell has already paid this price.
I'd like term-level functions to be more like instances. That is, with the equations distributed, see your example below. (Yes the set of equations must be coherent overall. That's something the compiler can check for me.)
I think it depends on the function. Some functions are defined by an ordered set of equations. Some (that is, class methods) are defined by an unordered set of equations, perhaps distributed across modules. Both models are useful.
There was some doubt whether there could be a type-level disequality test.
And indeed this was hard! See "apartness".
... [equality constraints and equality guards are] different, but I think the equality guard is useless
I see two main uses/benefits: 1. avoids the need for non-linear patterns in the head
Agreed that it would do this. But why is that important?
2. visually points up the 'non-apartness' vs an, um. "apartness guard"
(When I was figuring out Andy A-M's 2013 example, I rewrote the non-linear patterns with ~ guards, so I could figure out the apartness guards esp for the semi-apart instances. I posted the later workings to your blog
Somehow, this is more convincing. But I'm still not convinced on this point. Happily, you don't need to convince me -- put in the proposal what you like and then we can debate a concrete design.
[except as a type-level "let", I suppose].)
No, they're not doing that: deliberately I don't allow guards to introduce new type vars.
They would work as "let", I think:
instance Either a a | a ~ SomethingBigAndUgly where ...
Note that the `a` is not introduced in the guard.
Ok thanks, yes [TypeError] helps. Nevertheless it seems disergonomic to write an instance for a type-equal case, even with a helpful error message; when what you want is: no such instance.
You could always view
instance TypeError ... => C Int Bool
as sugar for
instance C Int Bool fails
The first declaration isn't so bad, I think, that it warrants introducing new concrete syntax.
What I can see will be awkward is backwards compatibility: can a class/type family with guards coexist with CTFs and with classes using overlap?
Sure. There would be no problem with this. The new idea is just that: new. I don't see it as getting rid of anything we already have. Perhaps some day we could think about removing OverlappingInstances, but we certainly don't have to do that at the same time as introducing instance guards. (I do like that name, too.)
Next step: write a ghc-proposal. I'll support it. If I had the time, I'd even implement it.
I'll have a crack at the proposal.
Great. Thanks! Richard
participants (3)
-
Anthony Clayden
-
David Menendez
-
Richard Eisenberg