2
votes

I have several data structures like

data Data1 = Data1
    { _data1Field :: Int
    -- More fields
    } deriving (Eq, Show)
makeLenses ''Data1

data Data2 = Data2
    { _data2Field :: Int
    -- More fields
    } deriving (Eq, Show)
makeLenses ''Data2

-- More similar data types

So I decided to write a simple type class to make it easier to compose

class HasField a where
    field :: Lens' a Int

instance HasField Data1 where
    field = data1Field

instance HasField Data2 where
    field = data2Field

But then I ran into the problem that some of these structures have the corresponding field as optional

data Data3 = Data3
    { _data3Field :: Maybe Int
    -- More fields
    } deriving (Eq, Show)
makeLenses ''Data3

And now I can no longer use the type class. Since there are about the same number of data types that have that field optional as not, I decided that it'd be better to change the typeclass:

class HasField a where
    field :: Lens' a (Maybe Int)

instance HasField Data3 where
    field = data3Field

But since I'm not very experienced with the lens library, I'm stuck figuring out how to make this new lens work with the types for Data1 and Data2. Ideally, I'd like to be able to view it and get a Maybe Int value for any type, and when setting I'd like Just x to set the field to x for Data1 and Data2 and be a no-op for those two types when passed Nothing.

Is this possible using existing combinators or am I going to have to write the lens myself? I'm fine doing so, but the majority of existing tutorials use TH and gloss over the details of writing one by hand.

I'm using GHC 7.6.3 and lens 3.10.

1
You can't really do it at all because it's an illegal lens. Lens law: view l (set l x s) = x, so if you set it to Nothing you must get Nothing when you view it.shachaf
It might be that you actually want a Traversal, though (which is like a lens onto 0 or more values, rather than just one value). When you have a Traversal you can use preview to view a Maybe and set to set.shachaf
@shachaf How would I write the Traversal for this? Also, would it be a problem if there are multiple fields like this for these records?bheklilr
@shachaf Alternatively, what would be some good reading material to help me understand how to write these kinds of Traversals?bheklilr

1 Answers

2
votes

As a follow up to shachaf

class HasFieldA d where
  field :: Traversal' d Int

instance HasFieldA Data1 where
  field = data1Field -- Lens's are Traversals

instance HasFieldA Data3 where
  field = data3Field . _Just

And then the ^? operator or the ^.. operator

getField :: HasFieldA d => d -> Maybe Int
getField = d ^? field -- or preview field d

to get it.

To set optional fields, you'd need another function

class SetFieldA d where
  setField :: Setter' d Int
instance SetFieldA Data3 where
  setField = set data3Field . Just