[Haskell-cafe] Instance Chains selection by class membership [was: [ghc-proposals/cafe] Partially applied type families]

Anthony Clayden anthony_clayden at clear.net.nz
Sun Jun 4 13:38:47 UTC 2017


> On Sun Jun 4 12:00:21 UTC 2017, Anthony Clayden wrote:

> I think this shows that using class instances
> to both define methods and drive selection logic
> can fail to separate concerns.

Richard's been (quite correctly) prodding me
to explain why I want a no-instance in places,
rather than an `instance ,,, fails` or
`instance (TypeError ... ) => ...`

I think Figure 7 in Garrett's 2015 paper
provides a case in point.
I find that `type family Ifi` and its call line 5/6 above
to be more like an exercise in obfuscated type-programming.

`type family Into` doesn't observe kind-hygiene
in the way we'd expect a term-level function
to observe type-hygiene.
Type families `Into` or Ifi` might properly return:
* Refl | L lp | R rp    -- that could be a DataKind
   (where `lp` or `rp` is from a recursive call to `Into`)
* or `Nope` -- which is a Boolean DataKind
             (yeuch)

Yes `Into` does need to chase down the data structure
in order to return the correct L/R accessor.
But it doesn't need to also encode failure in that way.

The frustrating thing (for me as reader)
is `type family IsIn` is looking ahead inside the data
structure
to find `f`. Isn't that enough lookahead and error
detection?

I want there to be no equation for `Into` returning `Nope`. 
So I want no Line 7, nor line 10, 13 in `type family Ifi`.

I think I'd tackle it this way.
Instead of `type family IsIn` returning a Boolean,

> type family CountF f g :: Nat where
>   CountF f f           = (S Z)
>   CountF f (g :+: h) = Plus (CountF f g) (CountF f h)
>   CountF f g         = Z

Then for `Into` I'd use a count-preparing helper function,
(instead of `Ifi`) and avoid those `Nope` returns:

> type family Into f g where
>   Into f f       = Refl
>   Into f (g :+: h) = IntoLR f (g :+: h) (CountF f g)
(CountF f h)
>   -- no fail case needed
>
> type family IntoLR f g (cg :: Nat) (ch :: Nat)
> type instance  Into' f (g :+: h) (S Z) Z     = L (Into f
g)
> type instance  Into' f (g :+: h)  Z  (S Z)    = R (Into f
h)
>   -- no fail case needed, no overlap, so not closed

So if the counts return {Z, Z} or {S Z, S Z} or
{S (S (S Z)), S (S (S (S Z)))}, etc,
there's simply no instance for `IntoLR`.

(If you want a helpful error message,
 of course easy enough to add failure instances.)

For completeness, I'll give the Instance Guard
version for `CountF` and `Into`:

> type family {-# INSTANCEGUARD #-} CountF f g :: Nat
> type instance  CountF f f         = (S Z)
> type instance  CountF f (g :+: h) | f /~ (g :+: h)
>                               = Plus (CountF f g) (CountF
f h)
> type instance  CountF f g  | f /~ g, g /~ (_ :+: _)     =
Z

> type family {#- INSTANCEGUARD -#} Into f g
> type instance  Into f f       = Refl
> type instance  Into f (g :+: h) | f /~ (g :+: h)
>                               = IntoLR f (g :+: h) (CountF
f g) (CountF f h)

Those families/instances could be Associated types,
grounded in the class instances (also guarded).

Now I can see my way to translating this into Haskell 2010.

AntC


More information about the Haskell-Cafe mailing list