user3416536
user3416536

Reputation: 1469

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.

Upvotes: 1

Views: 942

Answers (1)

Paul Johnson
Paul Johnson

Reputation: 17786

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"

Upvotes: 1

Related Questions