Search code examples
haskelltypesapplicativehaskell-lens

How do I add an Applicative context to a type expected by Lens' MyDataType?


I have a function generalized over a function func to be applied on MyDataType1 and another MyDataType1 like this

setThis :: (Functor f0, Applicative f0) => Lens' MyDataType1 (f0 Float) -> Float -> MyDataType1 -> MyDataType1 -> MyDataType1         
setThis func toThis dataType1 dataType2 = dataType2 & func %~ (\a -> (+) <$> a <*> delta
    where baseStat = dataType1 ^. func -- baseStat has type Maybe Float
          delta = (\a -> toThis * a) <$> baseStat -- also have type Maybe Float

where MyDataType1 is (when used with print. Every numeric value is a Float)

data MyDataType1 = MyDataType1 { _name = Just "First"
                               , _length = Just 5.5 
                               , _dmsTypes = Just
                                     ( DMS { _i = Just 1.9
                                           , _j = Nothing
                                           , _k = Just 95.9
                                           }
                                      )
                               }

The function setThis given a default record function like length, a constant Float, a data set to get base value from and a data set to modify, sets _length to a number that's a sum of the original value and the value from the other set multiplied by some constant.

It works just as I expect when given function length.

What I want to do is have the exact same behavior when given a function like (dmsTypes . _j) as in

setThis (dmsTypes . _Just . _j) 0.3 (someY) (someY) -- someY :: MyDataType1 

Although GHC throws this error if I do just that

Could not deduce (Applicative f) arising from a use of ‘_Just’
  from the context: Functor f
    bound by a type expected by the context:
               Lens' MyDataType1 (Maybe Float)
Possible fix:
    add (Applicative f) to the context of
      a type expected by the context:
        Lens' MyDataType1 (Maybe Float)

And while it seems like GHC knows exactly what I should do, I don't know how to do it.


Solution

  • Since the thing func points at might not exist, it should be a traversal (any number of focused values) rather than a lens (exactly one focused value).

    The part of setThis that uses a setter (i.e., (%~)) remains the same, but the getter part (i.e., (^.)) should instead use a fold, with (^?). In that case, dataType1 ^? func will have two layers of Maybe, one from (^?) and one from func (in what is currently f0), that you'll probably want to flatten with join.

    baseState = join (dataType1 ^? func)
    

    Now f0 must be Maybe.

    setThis :: Traversal' MyDataType1 (Maybe Float) -> ...