Skip to content

Generic Product Field Traversals #145

@dwincort

Description

@dwincort

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions