This is a simpler version of Using lens for array indexing if both array and index are in State as I have resolved some issues. I'm not sure if I should delete the original or edit it in place.
Given
{-# Language TemplateHaskell #-}
{-# Language Rank2Types #-}
import Control.Lens
import Control.Lens.TH
import Data.Array
data M = M { _arr :: Array Int Int, _idx :: Int } deriving (Show)
$(makeLenses ''M)
I want to write a function
combine :: Lens' M (Array Int Int) -> Lens' M Int -> Lens' M Int
That takes arr
and idx
lenses and constructs a combined lens that can be used to read and write an element pointed to by idx
. The lens I want exists:
comboGet :: M -> Int
comboGet m = _arr m ! _idx m
comboSet :: M -> Int -> M
comboSet m v = m { _arr = _arr m // [(_idx m, v)] }
combo1 :: Simple Lens M Int
combo1 = lens comboGet comboSet
I understand that comboGet
and comboSet
can in principle be rewritten to use solely arr
and idx
lenses.
My question is: what is the most idiomatic way to construct combo1
out of arr
and idx
?
You could construct it like this:
combo :: Lens' M Int -- alias for 'Functor f => (Int -> f Int) -> (M -> f M)
combo f m = arr (\xs -> fmap (\r -> xs // [(i, r)]) $ f $ xs ! i) m
where i = m^.idx
The better Traversal
version would look like this:
betterCombo :: Traversal' M Int
betterCombo f m = arr (\xs -> maybe (pure xs)
((\r -> xs // [(i, r)]) <$> f)
xs^? ix i) m
where i = m^.idx
Which is equivalent to:
betterCombo f m = (arr . ix i) f m
where i = m^.idx