Search code examples
haskellcompositionhaskell-lenslenses

Array indexing lens out of array and index lenses


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?


Solution

  • 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
    

    EDIT

    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