I’m new to lens
and trying to use it to compose many small modifications to a nested structure, which may fail and possibly return additional results:
element -> Maybe element
element -> Maybe (result, element)
How do I modify an inner structure by index, also returning Nothing
if the index isn’t present? If I use traverseOf
+ix
:
type Thing = (String, [Int])
exampleThing :: Thing
exampleThing = ("example", [0, 1])
predMaybe :: Int -> Maybe Int
predMaybe x
| x == 0 = Nothing
| otherwise = Just (pred x)
decrementThingAt :: Int -> Thing -> Maybe Thing
decrementThingAt i = traverseOf (_2 . ix i) predMaybe
> decrementThingAt 1 exampleThing
Just ("example",[0,0])
> decrementThingAt 0 exampleThing
Nothing
Then if the index isn’t present, this silently returns the structure unmodified:
> decrementThingAt 2 exampleThing
Just ("example",[0,1])
Whereas I want to return Nothing
here as well. I’d like to do it “inside” the lens composition if possible. I know I can use preview
/ ^?
“outside” to get a Maybe
according to whether an optic matched any targets:
> preview (_2 . ix 1) exampleThing
Just 1
> preview (_2 . ix 2) exampleThing
Nothing
But I expected to be able to write something like traverseOf (_2 . ix i . previewed) predMaybe
. I see some awkward ways to do it “outside”, like with foldMapOf
:
decrementThingAt i = getFirst . foldMapOf (_2 . ix i) (First . predMaybe)
But is there a way to keep everything in the same pipeline, so that I’m not repeatedly/explicitly disassembling & reassembling the structure?
I also don’t quite see how to combine this with returning additional results. At the moment I’m using StateT
and zoom
like this:
import Control.Lens (_1, zoom)
import Control.Monad.Trans.State (StateT, runStateT)
import Data.List (uncons)
-- NB: uncons :: [a] -> Maybe (a, [a])
pop :: Thing -> Maybe (Char, Thing)
pop = runStateT $ zoom _1 $ StateT uncons
> pop exampleThing
Just ('e',("xample",[0,1]))
> pop ("", [0, 1])
Nothing
But I still don’t know how to make that work with missing indices, for example, using type ThingMaybe = (Maybe String, [Int])
and failing if that Maybe
is Nothing
.