trevor cook
trevor cook

Reputation: 1600

How to use the same record selector two ways within a function? Lenses?

I have some data that have different representations based on a type parameter, a la Sandy Maguire's Higher Kinded Data. Here are two examples:

wholeMyData :: MyData Z
wholeMyData = MyData 1 'w'

deltaMyData :: MyData Delta
deltaMyData = MyData Nothing (Just $ Left 'b')

I give some of the implementation details below, but first the actual question.

I often want to get a field of the data, usually via a local definition like:

let x = either (Just . Left . myDataChar) myDataChar -- myDataChar a record of MyData

It happens so often I would like to make a standard combinator,

getSubDelta :: ( _ -> _ ) -> Either a b -> Maybe (Either c d)
getSubDelta f = either (Just . Left . f) f

but filling in that signature is problematic. The easy solution is to just supply the record selector function twice,

getSubDelta :: (a->c) -> (b->d) -> Either a b -> Maybe (Either c d)
getSubDelta f g = either (Just . Left . f) g

but that is unseemly. So my question. Is there a way I can fill in the signature above? I'm assuming there is probably a lens based solution, what would that look like? Would it help with deeply nested data? I can't rely on the data types always being single constructor, so prisms? Traversals? My lens game is weak, so I was hoping to get some advice before I proceed.

Thanks!


Some background. I defined a generic method of performing "deltas", via a mix of GHC.Generics and type families. The gist is to use a type family in the definition of the data type. Then, depending how the type is parameterized, the records will either represent whole data or a change to existing data.

For instance, I define the business data using DeltaPoints.

MyData f = MyData { myDataInt  :: DeltaPoint f Int
                  , myDataChar :: DeltaPoint f Char} deriving Generic

The DeltaPoints are implemented in the library, and have different forms for Delta and Z states.

data DeltaState = Z | Delta deriving (Show,Eq,Read)
type family DeltaPoint (st :: DeltaState) a where
  DeltaPoint Z      a = a
  DeltaPoint Delta  a = Maybe (Either a (DeltaOf a)) 

So a DeltaPoint Z a is just the original data, a, and a DeltaPoint Delta a, may or may not be present, and if it is present will either be a replacement of the original (Left) or an update (DeltaOf a).

The runtime delta functionality is encapsulated in a type class.

class HasDelta a where
  type DeltaOf a
  delta :: a -> a -> Maybe (Either a (DeltaOf a))
  applyDeltaOf :: a -> DeltaOf a -> Maybe a

And with the use of Generics, I can usually get the delta capabilities with something like:

instance HasDelta (MyData Z) where
  type (DeltaOf (MyData Z)) = MyData Delta

Upvotes: 0

Views: 77

Answers (1)

K. A. Buhr
K. A. Buhr

Reputation: 50864

I think you probably want:

{-# LANGUAGE RankNTypes #-}
getSubDelta :: (forall f . (dat f -> DeltaPoint f fld))
            -> Either (dat Z) (dat Delta)
            -> Maybe (Either (DeltaPoint Z fld) (DeltaOf fld))
getSubDelta sel = either (Just . Left . sel) sel

giving:

x :: Either (MyData Z) (MyData Delta) 
     -> Maybe (Either (DeltaPoint Z Char) (DeltaOf Char))
x = getSubDelta myDataChar
-- same as: x = either (Just . Left . myDataChar) myDataChar

Upvotes: 1

Related Questions