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

Anthony Clayden anthony_clayden at clear.net.nz
Sun Jun 4 08:06:32 UTC 2017


> On Fri Jun 2 13:20:13 UTC 2017, Richard Eisenberg wrote:

> > On Jun 2, 2017, at 3:22 AM, Anthony Clayden wrote: 
> >> I hereby pronounce 
> >>> instance TypeError ... => C Int Bool
> >> 
> >> as "not an instance". ...
> > 
> > Hmm. I'd be interested how you're going to stop
> > the compiler matching some usage site to it.
> 
> That's what TypeError does. When the compiler matches a
> use site, it issues an error. I would imagine that's the
> behavior you want.

No. I want to leave the class open,
so I can add other instances (possibly in a different
module).
If I protect instances in this module with guards,
the compiler can satisfy itself that 
the other modules' instances do not overlap.

> Can you write a concrete example of something you'd like
> to do that the current machinery doesn't? 
> 

OK let's recap the Expression Problem first. There are two
legs:

* We want to freely add methods over a set of datatypes.
  (Problematic in OOP, because
   the methods are embedded in the objects.)
  In Haskell we can declare a new class with new method(s),
  and give instances over those datatypes.
* We want to freely add constructors,
  and expand existing classes/methods to cover.
  That's not so easy in Haskell.
  The usual work-round is to make each constructor a new
datatype.
  Then we can use the instance mechanism to extend
classes/methods.
  ("Usual" for example with DSL's to expand the AST.)

If you have closed type families/classes (esp in an
imported/library module), 
you're blocked from extending it for new datatypes.
Now apparently Instance Chains don't suffer this,
the `fails` triggers a search for other instances in other
chains.
Garrett in an earlier message quoted section 3.1.1 of the
paper
"a class can be populated by multiple, non-overlapping
 instance chains, where each chain may contain multiple
clauses".
Unfortunately I can find no examples of using this feature,
so I'm a bit unsure how it goes.

We'd know the chains are non-overlapping
because they'd chain over distinct datatypes, I imagine.

And I thought that's what we were getting with CTFs,
but the implementation fell short of my expectations.
I wanted (when I suggested the `where`-style syntax):

> module A
>   type family F a b          -- full stop!
>   type instance F (T1 a) b where
>     ...     -- overlapping equations within T1
>
> module B
>   type instance F (T2 c) d where
>     ...    -- overlapping equations within T2

Those closed instances in distinct modules
are provably non-overlapping,
because of the different type constructor.
(By the same rules as currently,
 for apartness of instance heads.
 All equations within the where-group
 must unify with the instance head.)

So then this (current) syntax would be a shorthand:
>   type family G  e f where
>     ...  -- total equations for G

for the author of `G` who deliberately wanted
to prevent anybody expanding the family.
i.e shorthand for

>   type family G e f
>   type instance G e f where
>     ...

Fair enough, that's a common requirement.
But all we got in the implementation
was that blitz-all style. (I'm grumpy.)
And according to Garrett's survey,
15% of Hackage would be grumpy too.

OK, concrete example: within the HList framework

> data HNil = HNil
> data HList e l = HList e l
> -- deliberately not using DataKinds

the Hlist paper discusses various approaches
for indexing the payloads `e` within the list.

Suppose (eliding a lot of detail) I choose Type-Indexed
Products like

> newtype PersonId = PersonId Int
> newtype PersonName = PersonName String
> ...
> me = HCons (PersonId 41) $ HCons (PersonName "AntC") HNil

And type indexed `Has` predicate needing overlaps:

> class Has e l ...
> -- no instance for HNil
> instance Has e (HCons e l) ...
> instance (Has e l') => Has e (HCons e' l') ...

That's standard HList TIP, let's assume it's in a
library/module.

Another style of carrying the payload is type labels.
To mix these styles, I need a way to cons a labelled
element:

> data AstroSign = AstroSign
> ...
> data HConsLab lab e l = HConsLab lab e l
>
> me2 = HConsLab AstroSign 12 me

This deliberately allows the payload to be an ad-hoc type.
Now I need a Has instance:

> instance Has lab (HConsLab lab e l) ...
> instance (Has lab l') => Has lab (HConsLab lab' e' l') ...

Again relies on overlap.
No danger of clashing with the vanilla HCons style,
because labels are a distinct set of types.
IOW this is fine:

> you = HCons (PersonId 73) $ HCons (PersonName "rae")
>           $ HConsLab AstroSIgn 7 HNil
> -- instances for the type of `you`
> --  (Has you PersonId, Has you PersonName, Has you
AstroSign)

I can do all this now with stand-alone instances.
But I have to be careful to keep my overlaps well-behaved.
With closed Type Families/the proposed Classes,
I can't add those HConsLab instances.

I want to continue current support for freely adding
instances,
but provide a mechanism to enforce no-overlaps.
And I think guards achieve that with relatively lightweight
syntax,
avoiding hard-to-follow sequences of `else` ...


AntC


More information about the Haskell-Cafe mailing list