From anthony.d.clayden at gmail.com Sun Aug 7 10:14:12 2022 From: anthony.d.clayden at gmail.com (Anthony Clayden) Date: Sun, 7 Aug 2022 22:14:12 +1200 Subject: [Hugs-users] FunDeps: divergent improvement from overlapping instances Message-ID: The HList project (~2004) had to abandon Hugs because (for example) they couldn't express a type-level type equality test. Hugs rejects any attempt 'Instances are not consistent with Functional Dependencies'. Here's the equivalent that works in GHC. > class TypeEq a b e | a b -> e > instance TypeEq a a TTrue > instance {#- OVERLAPPABLE -#} (e ~ TFalse) => TypeEq a b e Hugs rejects the equivalent of these instances because it can unify the instance heads' determining positions (as TypeEq a a e0) but the improvement diverges. Hugs always treats a wildcard e0 as apart from any specific type or from any other wildcard; whereas GHC treats them as unifiable, so accepts the instances. The instances (whole heads) are overlappable, so GHC's overlapping instances mechanism will improve e ~ TFalse only on condition it can't unify types a, b at some usage site. SPJ points out https://gitlab.haskell.org/ghc/ghc/-/wikis/Functional-dependencies-in-GHC/Key-examples/FunDep-improvement-vs-instance-selection#qs-for-spj that using an instance head to improve under a FunDep is a separate step and proceeds first vs selecting an instance. So this form for the second instance won't work in GHC: > instance TypeEq a b TFalse (Also the instance heads are not overlapping.) In case of TypeEq a a e0, instance improvement will find both instances match and improve e0 ~ TTrue, e0 ~ TFalse, then reject as inconsistent, same as Hugs. But notice this FunDep is 'Full' -- that is, mentions all of the classes, parameters. ('Full' FunDep is introduced in the 2008 'via CHRs' paper.) If improvement can use a Full FunDep matching to an instance head, it has in effect selected that instance. This is really using the instance as a _candidate_ for improvement -- it might be the Dependent types are already improved as far as possible before looking at this instance. (That is, if we already have [W] TypeEq Int Int TTrue, looking to the instance heads won't improve further; but Hugs is not smart enough to shortcut that TTrue can't be improved.) Hugs' type routines at this step of compilation are very much focussed on improving types, not selecting instances. And in case of a class with multiple FunDeps, the routines should 'fire' all possible FunDeps for each instance. However it's only a small mod in routine instImprove to say: upon matching to a Full FunDep for an instance, stop looking at other instances for improvement. A precondition is that it scans instances in most-specific-first sequence -- which Hugs is already doing. So I can write these instances > class TypeEq a b e | a b -> e > instance TypeEq a a TTrue > instance TypeEq a b TFalse Without needing to clutter the instances by deferring improvement to constraints. (This also makes type inference error messages less cluttered.) I'm now working on the instance sequencing routines, to ignore those divergent Dependent positions when deciding overlap ordering. AntC -------------- next part -------------- An HTML attachment was scrubbed... URL: From anthony.d.clayden at gmail.com Fri Aug 12 00:57:38 2022 From: anthony.d.clayden at gmail.com (Anthony Clayden) Date: Fri, 12 Aug 2022 12:57:38 +1200 Subject: [Hugs-users] FunDeps: divergent improvement from overlapping instances In-Reply-To: References: Message-ID: > A precondition is that it scans instances in most-specific-first sequence > -- which Hugs is already doing. It galled me that going only by (an adapted notion of) overlap, I still had to write three instances for a three-FunDep AddNat: > class AddNat x y z | x y -> z, x z -> y, y z -> x > > instance AddNat Z Z Z > instance AddNat Z (S y') (S y') > > instance AddNat x' y z' => AddNat (S x') y (S z') > because the 'obvious' way to write, with two instances (see below), put them in no substitution ordering for the argument positions to the third FunDep. So I dug out an idea from a few years ago (rejected proposal) and implemented: > instance AddNat Z y y > > instance AddNat x' y z' => AddNat (S x') y (S z') | y /~ (S z') > That after the `|` in the instance is an 'Instance Disequality Guard': prefer a more specific instance if those two types from the head unify. Then Hugs puts that instance later in the preference ordering than the first instance -- because the instance heads unify going by the argument positions, but the first instance has no guard. This also works for the semi-overlap instances that Hugs currently rejects/GHC accepts but might turn out to be unusable: > class Semi a b > > instance Semi Int b > instance Semi a Bool | a /~ Int > > [W] Semi Int Bool -- GHC currently rejects as ambiguous The guard says: in case of [W] Semi Int Bool, prefer the Semi Int b instance. We can even go: > instance Semi Int Bool > > instance Semi Int b | b /~ Bool > instance Semi a Bool | a /~ Int > > instance Semi a b -- no guard, because strictly more general than any instance -------------- next part -------------- An HTML attachment was scrubbed... URL: From anthony.d.clayden at gmail.com Mon Aug 22 01:54:54 2022 From: anthony.d.clayden at gmail.com (Anthony Clayden) Date: Mon, 22 Aug 2022 13:54:54 +1200 Subject: [Hugs-users] FunDeps: divergent improvement from overlapping instances Message-ID: > I'm now working on the instance sequencing routines, to ignore those > divergent Dependent positions when deciding overlap ordering. Ok. Done. So this works: >* class TypeEq a b e | a b -> e *>* instance TypeEq a a TTrue *>* instance TypeEq a b TFalse* In case of multiple FunDeps for an instance, improvement must use all FunDeps for a given instance, but not go on to improve from a less specific instance -- if one of the FunDeps is Full. Note a Full FunDep needn't actually deliver any improvement (change of types) for this 'don't go on' to apply -- it's sufficient if some other improvement has already got the Wanted to match the instance. (In effect this is selecting the instance -- although Hugs doesn't 'officially' do so until a later step.) Getting the preference sequence of instances was tricky -- especially in case of multiple FunDeps: in comparing two instance heads, they might be apart going by some FunDeps, but overlapping going by others. -------------- next part -------------- An HTML attachment was scrubbed... URL: