Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GetField doesn't work with data families #96

Open
isovector opened this issue Aug 6, 2019 · 7 comments
Open

GetField doesn't work with data families #96

isovector opened this issue Aug 6, 2019 · 7 comments

Comments

@isovector
Copy link

The following program works!

data Foo0
  = FooV0
    { _fooId :: Int
    , _fooName :: String
    }
  deriving (Generic, Show, Eq)

data Foo1
  = FooV1
  { _fooId        :: Int
  , _fooName      :: String
  , _fooHonorific :: String
  }
  deriving (Generic, Show, Eq)

v1 = FooV1 2 "james" "sir"
v0 = FooV0 undefined undefined


v0' = copyField @"_fooId" @Int v1 $ copyField @"_fooName" @String v1 v0

copyField
    :: forall name t from to
     . ( HasField name to   to   t t
       , HasField name from from t t
       )
    => from
    -> to
    -> to
copyField f t =
  t & field @name @to @to @t @t .~ f ^. field @name @from @from @t @t

but if you turn Foo into a data family, it doesn't:

data family Foo (a :: Nat)

data instance Foo 0
  = FooV0
    { _fooId :: Int
    , _fooName :: String
    }
  deriving (Generic, Show, Eq)

data instance Foo 1
  = FooV1
  { _fooId        :: Int
  , _fooName      :: String
  , _fooHonorific :: String
  }
  deriving (Generic, Show, Eq)

with the following error message:

/home/sandy/prj/YoloSwag69/src/Lib.hs:39:7: error:
    • Couldn't match type ‘generic-lens-1.1.0.0:Data.Generics.Internal.Families.Changing.ReplaceArgs
                             (Foo 0)
                             (generic-lens-1.1.0.0:Data.Generics.Internal.Families.Changing.Unify
                                b'1 Int)’
                     with ‘Foo 0’
        arising from a use of ‘copyField’
      The type variable ‘b'1’ is ambiguous
    • In the expression: copyField @"_fooId" @Int v1
      In the expression:
        copyField @"_fooId" @Int v1 $ copyField @"_fooName" @String v1 v0
      In an equation for ‘v0'’:
          v0'
            = copyField @"_fooId" @Int v1 $ copyField @"_fooName" @String v1 v0
   |
39 | v0' = copyField @"_fooId" @Int v1 $ copyField @"_fooName" @String v1 v0
   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^

/home/sandy/prj/YoloSwag69/src/Lib.hs:39:37: error:
    • Couldn't match type ‘generic-lens-1.1.0.0:Data.Generics.Internal.Families.Changing.ReplaceArgs
                             (Foo 0)
                             (generic-lens-1.1.0.0:Data.Generics.Internal.Families.Changing.Unify
                                b'0 [Char])’
                     with ‘Foo 0’
        arising from a use of ‘copyField’
      The type variable ‘b'0’ is ambiguous
    • In the second argument of ‘($)’, namely
        ‘copyField @"_fooName" @String v1 v0’
      In the expression:
        copyField @"_fooId" @Int v1 $ copyField @"_fooName" @String v1 v0
      In an equation for ‘v0'’:
          v0'
            = copyField @"_fooId" @Int v1 $ copyField @"_fooName" @String v1 v0
   |
39 | v0' = copyField @"_fooId" @Int v1 $ copyField @"_fooName" @String v1 v0
   |                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

HELP PLEASE!!!!!

@isovector
Copy link
Author

Using field' instead of field seems to work /shrug

@kcsongor
Copy link
Owner

kcsongor commented Aug 6, 2019

Yes, unfortunately this is known to not work. In general, type parameters that have nominal roles are not supported well. field' is a possible workaround. I'm now thinking it would be good to stick this into the documentation somewhere?

@neongreen
Copy link

neongreen commented Jun 21, 2020

You can solve this on a case by case basis if you don't actually want polymorphic updates. (And.. maybe even if you do?)

Let's say you have:

data RefundT f = Refund { id :: B.C f String, ... }

Now, #id .~ "test" will not work on a Refund Identity. But it will if you add the following instance:

import qualified Data.Generics.Labels as GL
import qualified Data.Generics.Product.Fields as GL

instance
  {-# OVERLAPPING #-}
  (GL.HasField' name (RefundT f) a, f ~ g, a ~ b) =>
  GL.HasField name (RefundT f) (RefundT g) a b
  where
  field = GL.field' @name

This is only if you want to use the #field syntax.

polux added a commit to smelc/miso-darkcraw that referenced this issue Jul 2, 2020
Unfortunately symbols didn't work because of
kcsongor/generic-lens#96. Had to revert to field' has
someome suggests on the issue.
@michaelpj
Copy link
Collaborator

Just wanted to add a use case where I hit this. I was thinking about using generic-lens for lsp-types in order to avoid a lens dependency on the types library. However, the lsp library features a bunch of types like this:

data Method = ...
type family MessageParams (m :: Method) where ...
data Message (m :: Method) = Message {
  params :: MessageParams m
  ...
}

Trying to use #params to access the field gives a very similar message to the OP.

Using field' works, and oddly it works if I split it out into separate let-bindings, i.e.

let t = msg ^. #params . #textDocument

doesn't work but

let p = msg ^. #params
      t = p ^. #textDocument

does.

It would be nice if the label syntax worked, but 🤷

@arybczak
Copy link
Contributor

For the record, built-in generic lenses (also usable via OverloadedLabels) in optics have full support for data families.

@michaelpj
Copy link
Collaborator

@arybczak I don't suppose you have any idea why it works in optics but not here? This is annoying enough I want to fix it 😂

@arybczak
Copy link
Contributor

@michaelpj no idea, implementation in here is quite different to what's in optics.

michaelpj added a commit to haskell/lsp that referenced this issue Mar 28, 2024
This adopts the approach discussed here:
#465 (comment)

That is:
- We export normal, non-prefixed record selectors (still using
  `DuplicateRecordFields`, of course).
- Users who want lenses can use `generic-lens`; `lsp` and `lsp-test` do
  this.
- It's sensible for `lsp-types` to define some useful lenses that aren't
  derived from fields; these go in a `lsp-types-lens` component.

I think the result is... fine?
kcsongor/generic-lens#96 is a pain in some
cases, but by and large using the generic lenses is quite nice.

I also tried to just use `OverloadedRecordDot` instead of lenses where I
could, since we now support 9.2 as our earliest version. I couldn't
quite get rid of `lens` in `lsp`, it's too useful. I did get rid of it
entirely in `lsp-types`, which was quite painful in at least one place.

This would obviously be a huge breaking change, but I think it's the
right direction.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants