ndmitchell / record-dot-preprocessor

A preprocessor for a Haskell record syntax using dot
Other
129 stars 19 forks source link

Can’t handle records with type families in field types #29

Closed neongreen closed 4 years ago

neongreen commented 4 years ago

Let’s say we have, HKD-style:

data Foo f = Foo { x :: C f Int }

C is a type family here.

Derived instances won’t compile:

Illegal type synonym family application in instance: C f Int
 • In the instance declaration for
    'Z.HasField "x" (Foo f) (C f Int)'

Are there any workarounds in the preprocessor for this? Maybe the ability to derive instances only for specific instantiations of f?

ndmitchell commented 4 years ago

No workarounds, and its unlikely to compile. Do you use C at many instantiations of f? Is the purpose of C to be some record-like framework? This isn't a case I thought of, and don't have any great suggestions how to handle it.

neongreen commented 4 years ago

This is a common (ish) higher-kinded data pattern. It's used e.g. in DB frameworks like beam.

For my usecase, in 90% of cases f ~ Identity. In fact, C for me is defined as:

type family C f where
  C Identity a = a
  C f a = f a

This is why, for instance, I'd be happy with a pragma that lets me say

{-# RECORD_DOT (Foo Identity) #-}

data Foo f = Foo { x :: C f Int }

As a side-note — do you know if I'm going to hit the same problem with -XRecordDots (or whatever it's called) when it becomes a part of GHC?

ndmitchell commented 4 years ago

The HasField defines the necessary instances. I'm not sure what will happen with higher-kinds there. I suspect it might not work, and you'd have to define your own instances by hand - which in this case doesn't look too troublesome.

A pragma wouldn't be unreasonable - or alternatively a pragma that just says ignore Foo and then you can define your own instances for it. I'd happily take a patch, although I'm not actively using this code, so might not get around to actually implementing it myself.

neongreen commented 4 years ago

Let's say we add a pragma. Something like:

{- RECORD_DOT FooT Identity -}
data FooT f = FooT ...

I assume this will be hard to add to the preprocessor reliably? So perhaps it should only be in the GHC plugin.

Any bikeshedding re/ how the pragma should look?

Any helpful tips to whoever will take up implementing this?

ndmitchell commented 4 years ago

Not sure how hard it is to add to the preprocessor - not infeasible. Not too fussed about the pragma design itself. A good question would be what will this look like for the version that gets implemented in GHC? Can we be compatible with that? I'm not really sure.

neongreen commented 4 years ago

I think that GHC should cope with this out of the box, at least when the concrete type is known?

I feel that if GHC generates instances on the fly, it should be able to generate a HasField "x" (Foo Identity) (C Identity Int) instance if it knows that f ~ Identity. But I'm not sure.

Do you know who's in charge of implementing the proposal in GHC?

ndmitchell commented 4 years ago

That bit will be Adam Gundry - but looking at the existing HasField instances in GHC will probably give you a clue what will happen.

neongreen commented 4 years ago

but looking at the existing HasField instances in GHC

Oh, good idea! Thanks

neongreen commented 4 years ago

Good news: HasField in GHC 8.10 works just fine.

> getField @"x" (Foo 1 :: Foo Identity)
1
neongreen commented 4 years ago

The pragma will not help, because type families are not allowed in an HasField instance head at all. I'm closing this.

ndmitchell commented 4 years ago

Thanks for tracking this down and figuring it out.