Search code examples
haskelldictionaryhaskell-lens

How to set value in a nested Map using Lens


I have the following program:

{-# LANGUAGE TemplateHaskell #-}
import qualified Data.Map.Strict as Map
import Control.Lens

data MyLabel = MyLabel { _label :: String } deriving (Show, Eq, Ord)
data MyMap = MyMap { _vals :: Map.Map String MyLabel } deriving (Show, Eq, Ord)
makeLenses ''MyLabel
makeLenses ''MyMap

sample :: MyMap
sample = MyMap { _vals = Map.fromList [("foo", MyLabel "bar")] }

Now I'd like to know how to do a transformation f using lenses such that:

f sample "quux" == MyMap { _vals = Map.fromList [("foo", MyLabel "quux")] }

I learned that the function at from Lens library should be used to modify Maps, so I was trying to do things like this:

sample ^. vals & at "foo" . label .~ Just "quux"

But that produces an error message which is not very understandable for me. What is the right way to do this?


Solution

  • Try this on for size:

    {-# LANGUAGE TemplateHaskell #-}
    
    module Main where
    
    import qualified Data.Map.Strict as Map
    import Control.Lens
    
    data MyLabel =
      MyLabel { _label :: String } deriving (Show, Eq, Ord)
    
    data MyMap =
      MyMap { _vals :: Map.Map String MyLabel } deriving (Show, Eq, Ord)
    
    makeLenses ''MyLabel
    makeLenses ''MyMap
    
    sample :: MyMap
    sample =
      MyMap (Map.fromList [("foo", MyLabel "bar")])
    
    main :: IO ()
    main =
      print (sample & (vals . at "foo" . _Just . label .~ "quux"))
    

    Remember that, when setting, you're trying to build a function of type MyMap -> MyMap. The way you do that is by chaining a bunch of optics together (vals . at "foo" . _Just . label) and then choosing a setter operation (.~). You can't mix and match a getter operation like ^. with a setter operation like .~! So every setter more or less looks like this:

    foo' = (optic1 . optic2 . optic3 . optic4) .~ value $ foo
    --     _________this has type Foo -> Foo___________
    

    And to improve readability we use &, the flipped version of $:

    foo' = foo & (optic1 . optic2 . optic3 . optic4) .~ value