Search code examples
haskelllenseshaskell-lens

Convert Lens' a b into Lens' a (Maybe b)


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.


Solution

  • 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