Partial Lens Composition

I am trying to figure out the cleanest way to change values โ€‹โ€‹nested inside Maybe (or other types to model partiality).

Here is an installation example:

 {-# LANGUAGE TemplateHaskell #-} import Control.Lens data Outer = Outer { _inner :: Maybe Inner } deriving (Show) data Inner = Inner { _foo :: Int } deriving (Show) makeLenses ''Outer makeLenses ''Inner 

It's pretty easy to do this in a somewhat dirty way with lens :

 wibble :: Outer -> Maybe Outer wibble o = do i <- view inner o let i' = over foo succ i return $ set inner (Just i') o 

To indicate why this is unpleasant, here is what I would like to write:

 wibble' :: Outer -> Maybe Outer wibble' = overish inner.foo succ overish = ??? 

The inability to search for a field should simply lead to the failure of the whole operation, instead of explicitly checking for a failure at every point where this could happen.

Any suggestions? I tried navigating the various lens modules, but nothing turned out to be quite suitable for the account.

+7
source share
1 answer

You can use over (inner.traverse.foo) to write to a nested field. This will not report a failure the way you want, because it managed to match all 0 targets.

traverse (from Data.Traversable , re-exported using Control.Lens ), gives you Traversal to navigate Maybe .

You can read with (^?) To find out if the objective of the lens exists.

We can solve this in several ways, using existing lens combinators for reading and writing separately, but we could just build such a combinator directly:

 import Data.Monoid (Any(..)) import Control.Monad (guard) overish :: LensLike ((,) Any) stab -> (a -> b) -> s -> Maybe t overish lfs = case l (\a -> (Any True, fa)) s of (Any r, t) -> t <$ guard r 

You can write this with l %%~ \a -> (Any True, fa) .

You can easily check if there are Traversal objects without using nullOf , but this will require two passes and a higher type of rank:

 overish :: Traversal stab -> (a -> b) -> s -> Maybe t overish lfs = over lfs <$ guard (not (nullOf ls)) 

This is just a check to see what the goal is, and then apply the installer to it, if there are goals.

Then you can just use

 overish (inner.traverse.foo) succ 
+8
source

All Articles