Search code examples
purescriptlenses

Combine prisms to focus on a value regardless of a branch


I have a sum type of possible outcomes, and in every outcome there is a certain "Result" that I want to focus on. I know how to get that "Result" from each of the outcomes (I have a bunch of prisms for that), but I don't know how to combine these prisms so that I can grab the "Result" from the whole sumtype, without worrying which case I'm on.

Simplified example:

type OneAnother = Either Int Int

exampleOneAnother :: OneAnother
exampleOneAnother = Left 10

_one :: Prism' OneAnother Int
_one = _Left

_another :: Prism' OneAnother Int
_another = _Right

_result :: Lens' OneAnother Int
_result = ???
-- How can I combine _one and _another to get result regardless whether its left or right ?

Solution

  • Once a prism comes to focus, it loses the context. So I don't see a way to define _result in terms of _one and _another. But you can certainly do better than resorting to unsafePartial:

    import Data.Lens.Lens (Lens', lens)
    import Data.Profunctor.Choice ((|||), (+++))
    
    type OneAnother = Either Int Int
    
    _result :: Lens' OneAnother Int
    _result = lens getter setter
      where
      getter      = identity ||| identity
      setter e x  = (const x +++ const x) e
    

    Stole this from the profunctor-lens repository:

    -- | Converts a lens into the form that `lens'` accepts.
    lensStore :: forall s t a b . ALens s t a b -> s -> Tuple a (b -> t)
    lensStore l = withLens l (lift2 Tuple)
    

    It isn't exported somehow. With that help, the following solution should be generic enough:

    import Prelude
    import Control.Apply (lift2)
    import Data.Lens.Common
    import Data.Lens.Lens
    import Data.Lens.Prism
    import Data.Profunctor.Choice ((|||), (+++))
    import Data.Tuple
    
    _result :: Lens' OneAnother Int
    _result = lens getter setter
      where
      getter      = identity ||| identity
      setter e x  = (const x +++ const x) e
    
    lensStore :: forall s t a b . ALens s t a b -> s -> Tuple a (b -> t)
    lensStore l = withLens l (lift2 Tuple)
    
    data ABC
      = A Int
      | B (Tuple Boolean Int)
      | C OneAnother
    
    lensABCInt :: Lens' ABC Int
    lensABCInt = lens' case _ of
      A i -> map A <$> lensStore identity i
      B i -> map B <$> lensStore _2 i
      C i -> map C <$> lensStore _result i
    

    Here ABC is your target sum type. As long as its each variant has a lens, you have a lens for it as a whole.