Search code examples
haskellhaskell-lens

Haskell Lens - Prism Composition


I'm trying to compose a Lens and a Prism in Haskell, and not getting the answer I expected. Here is my test code:

{-# LANGUAGE OverloadedStrings #-}
import Data.Function  ( ($), (&) )

import Control.Lens.Getter  ( (^.) )
import Control.Lens.Lens    ( Lens', lens )
import Control.Lens.Prism   ( Prism', prism )
import Control.Lens.Setter  ( (.~) )
import Control.Lens.Review  ( AReview, re )
import Control.Lens.TH      ( makeClassy )

(##) :: AReview t s -> s -> t
x ## y = y ^. re x

data Test = T1 Int | T2 String
  deriving Show

_T1 :: Prism' Test Int
_T1 = prism T1 (\x -> case x of T1 i -> Right i; _ -> Left x)

_T2 :: Prism' Test String
_T2 = prism T2 (\x -> case x of T2 t -> Right t; _ -> Left x)

data Combi = Combi { _t :: Test }
  deriving Show

defCombi :: Combi
defCombi = Combi (T1 7)

t :: Lens' Combi Test
t = lens (\(Combi x) -> x) (\( Combi _ ) t' -> Combi t')

test = (defCombi & t .~ (_T2 ## "foo"), defCombi & (t . _T2) .~ "bar")

Now my surprise is that when I run this, the second of the pair shows Combi {_t = T1 7}; that is, the "assignment" via t . _T2 has no effect.

Looking at the types, the apparently relevant detail is that composing t with _T2 promotes the "Functor" requirement to an "Applicative" requirement.

*Main
λ> :t t
t :: Functor f => (Test -> f Test) -> Combi -> f Combi
*Main
λ> :t t . _T2
t . _T2
  :: Applicative f => (String -> f String) -> Combi -> f Combi

But quite frankly, I can't get my head around what this means, and particularly why this means that the composition doesn't "work" (or, more likely, that it does work, but I'm misunderstanding what it means in this context).

Any enlightenment gratefully received.


Solution

  • When you use a Prism, or indeed any Traversal as a setter, it only works if the argument is already on the correct variant.

    Prelude> :module + Control.Lens
    
    Prelude Control.Lens> (Left 4) & (_Left .~ "foo")
    Left "foo"
    
    Prelude Control.Lens> (Left 4) & (_Right .~ "foo")
    Left 4
    

    Thus when you apply _T2 .~ "bar" to T1 7 you get T1 7 instead of T2 "bar"