How to map a function over a part of a nested data structure?

Viewed 114

I want to map a function of type Float -> Float over a part of a data structure which looks like this.

VDstruct { _onBuild = Just True                                                                                          
         , _imageName = Just "arc.png"                                                                                        
         , _category = Just "All"                                                                                         
         , _mmo = Just 210                                                                                                     
         , _structTypes = Just                                                                                                  
               ( Mage                                                                                                           
                   { _ict = Nothing                                                                                            
                   , _create = Just 1.24                                                                                          
                   , _sh = Nothing                                                                                                                                                                                     
                   }    
         }

I want to apply that function to _ict, _create and to _sh.

I know how to do that for each one of those. I'm using Lenses to help me out with that. The result has _create = Just 5.4 which is exactly what I expect from plusXPercent function. This is what I'm using right now.

setSer x = x & (structTypes . _Just . create) %~ fmap plusXPercent

What I want to do is instead of naming every _ict, _sh, etc. I want a way to 'map' that function over the entirety of Mage structure.

How should I go about doing that?

Edit: _ict, _create and _sh have type Maybe Float

and Mage is defined like this

data Mage = Mage { _ict :: Maybe Float
                 , _create :: Maybe Float
                 , _sh :: Maybe Float
                 } deriving Show
2 Answers

If you want to use the mono-traversable package, you can define a MonoFunctor instance for Mage.

type instance Element Mage = Maybe Float

instance MonoFunctor Mage where
    omap f (Mage x y z) = Mage (f x) (f y) (f z)

Then you can use omap to apply (e.g.) fmap (+1) to every field of Mage.

 omap (fmap (+1)) (Mage { _ict = Nothing                                   
                        , _create = Just 1.24                                                                                          
                        , _sh = Nothing
                        })
   == Mage { _ict = Nothing, _create = Just 2.24, _sh = Nothing }

Then, I think, you would write (sorry, just guessing, lenses aren't my strong suit):

--setSer x = x & (structTypes . _Just . create) %~ fmap plusXPercent

setSer x = x & (structTypes . _Just) %~ (omap (fmap plusXPercent))

However, MonoFunctor may be overkill; you can accomplish the same thing with the Applicative instance for functions.

foo :: (Maybe Float -> Maybe Float) -> Mage -> Mage
foo f = Mage <$> f . _ict <*> f . _create <*> f . _sh

You can always write Traversals by hand, and that's what this calls for. Lens has a class that would apply here, Each.

instance (a ~ Float, b ~ Float) => Each Mage Mage a b where
    each f (Mage i c s) = Mage <$> traverse f i <*> traverse f c <*> traverse f s

You could also write that Traversal without the class, but you might as well re-use the name when it's appropriate.

Related