Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use Haskell's lenses library to fmap a lens

In the code below, my question concerns the top-most function someFunc (everything below is just to provide a complete example). I use a record-syntax getter and fmap there. What's the lens way to implement someFunc?

import           Control.Lens
import           Data.IntMap  (IntMap)

someFunc :: Farm -> IntMap Size
someFunc farm =
  _barnSize <$> farm ^. farmBarns

data Farm = Farm
  { _farmBarns :: IntMap Barn
  }

farmBarns :: Lens' Farm (IntMap Barn)
farmBarns = lens _farmBarns (\farm barns -> farm { _farmBarns = barns } )

type Size = (Int, Int)

data Barn = Barn
  { _barnSize :: Size
  }

barnSize :: Lens' Barn Size
barnSize = lens _barnSize (\barn size -> barn { _barnSize = size } )
like image 466
ruben.moor Avatar asked Nov 14 '16 14:11

ruben.moor


2 Answers

Just replace _barnSize by (^. barnSize) or, equivalently, view barnSize:

someFunc :: Farm -> IntMap Size
someFunc farm = view barnSize <$> farm ^. farmBarns

For a "100% lens" solution, you can use the mapped setter. In this case, though, I don't think there is any real advantage in doing that.

someFunc :: Farm -> IntMap Size
someFunc farm = (mapped %~ view barnSize) (farm ^. farmBarns)

Another possible spelling involves using to to combine everything in a single getter. That doesn't buy you much here either, but it might be somewhat convenient if you wanted to keep working with the IntMap in lens style by chaining additional getters/folds/etc.

someFunc :: Farm -> IntMap Size
someFunc farm = farm ^. farmBarns . to (fmap (view barnSize))

There is a special purpose combinator that subsumes the to/(^.) combination above. It is called views:

someFunc :: Farm -> IntMap Size
someFunc farm = views farmBarns (fmap (view barnSize)) farm
like image 152
duplode Avatar answered Nov 17 '22 00:11

duplode


You can use mapped (or traversed) to 'map' over a traversable using lenses, in your case:

someFunc :: Farm -> IntMap Size
someFunc farm =
    farm ^. farmBarns & mapped %~ view barnSize

This may be a bit confusing, but here's what's happening, I'll add brackets to make it a little clearer

someFunc :: Farm -> IntMap Size
someFunc farm =
    (farm ^. farmBarns) & (mapped %~ (view barnSize))

So basically, we use farm ^. farmBarns to get the IntMap Barns from the farm, on the right hand side of & we construct a setter using %~ which is the infix of over, however the function which over passes its target to is actually just focuses a the barnSize using a lens. view barnSize :: Barn -> Size.

Lastly we use & to tie it all together which is equivalent to flip $ and takes the setter from the right hand and uses it on the result of the left hand.

like image 2
Chris Penner Avatar answered Nov 17 '22 00:11

Chris Penner