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

Generic Product Field Traversals #145

Open
dwincort opened this issue Apr 18, 2022 · 4 comments
Open

Generic Product Field Traversals #145

dwincort opened this issue Apr 18, 2022 · 4 comments

Comments

@dwincort
Copy link

dwincort commented Apr 18, 2022

I find it a little frustrating that when I have a sum-of-products data type, I can't easily access the fields by their name. I do understand the technical limitations here, but I've still been thinking about workarounds. One that would suit my purposes pretty well is to have a traversal to a field name.

Motivation and Explanation

Ideally, if I have a data type such as

data Foo
  = Bar { a :: Int }
  | Baz { a :: Int, b :: Bool }
  | Qux { a :: Int, b :: Bool, c :: Char }
  deriving (Generic)

and I have a value Baz 3 True, then I could do a prism-constructor access of Baz followed by a lens-field access on b. Unfortunately, the prism for Baz has type Prism' Foo (Int, Bool) -- Haskell's lack of anonymous records strikes!

At a user level, I can make data types for all of these, as in:

data Foo = Bar Bar' | Baz Baz' | Qux Qux' deriving Generic
data Bar' = Bar' { a :: Int } deriving Generic
data Baz' = Baz' { a :: Int, b :: Bool } deriving Generic
data Qux' = Qux' { a :: Int, b :: Bool, c :: Char } deriving Generic

Not only is this some annoying type bloat and makes data construction more irritating, but this creates the problem that I no longer have the free lens field @"a" :: Lens' Foo Int.

I could also define, for instance,

baz_b :: Prism' Foo Bool
qux_b :: Prism' Foo Bool
qux_c :: Prism' Foo Char

which is a valid, if verbose, option. Template Haskell could help, especially if there are a lot of fields.

As it turns out, there are many cases where one doesn't need the full power of a prism but still wants access to fields that are not defined in every constructor. Obviously, the library maintainer understands this given the traversals in Data.Generics.Product.Types and Data.Generics.Product.Param. I propose this be extended so that one can get a traversal from just a field name. I imagine something like:

class HasFields (field :: Symbol) s t a b where
  fields :: Traversal s t a b

This would be very similar to HasField except that it would construct a traversal rather than a lens, and it would not check to make sure that the field is present in every constructor. I think, as good form, it should probably make sure that the field is present in at least one constructor, but technically, this wouldn't be necessary.

For Your Consideration

  • Is this a good idea?
  • Is there an alternative I should consider?
  • How much work do you think this is?
  • Would you welcome a PR if I made one?
@kcsongor
Copy link
Owner

I think this is a good idea! There's no nice alternative that I'm aware of that achieves this goal in a satisfactory way.

A lot of the lens derivation code can be piggybacked off of, so implementation-wise I don't expect this to require too much work. I would most certainly welcome a PR!

@adamgundry
Copy link

I agree that this makes a lot of sense to offer. FWIW optics has a version of this as gafield.

@googleson78
Copy link
Sponsor

googleson78 commented Aug 8, 2023

An alternative that I would like to see, but am entirely incapable of judging whether it's (easily) possible:

Can we not leverage some machinery to make it so that the type returned by using a ctor prism actually does have the fields that it "should"?

The ones that come to mind are:

  • Use some extensible records implementation and return an extensible record type from the ctor prism instead of returning a vanilla Haskell tuple. This seems very heavyweight, but at the same time, it sounds like it ought to be doable.
  • Write custom GenericLensTupleN types and then write instances for them which (somehow??) have the Fields instances which are implied by the fields of the used ctor prism, returning a GenericLensTupleN instead of a vanilla Haskell tuple. This one is entirely cloudy to me, to the point where I question whether it's even possible without incidentally implementing an extensible records library.

@Elvecent
Copy link

Elvecent commented Aug 9, 2023

This would be really nice to have. lens generates these traversals through Template Haskell, and having used it in practice, I'd say it's indeed reasonable and convenient. Don't think I have time to implement, though 😿

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