Search code examples
haskellindiceshaskell-lenscustom-data-type

How to use iset with nested datatype and lenses?


I can't get the types in the last function to line up. Point is to set the all price Doubles in connections with a function that depends only on the index of the 3-tuple. The original Double value in the tuple can be discarded.

{-# LANGUAGE TemplateHaskell   #-}
{-# LANGUAGE TupleSections #-}

import Control.Lens

data Typex = Typex 
    { _level       :: Int
    , _coordinate  :: (Int, Int)
    , _connections :: [(Int, (Int, Int), Double)]  -- = (level, coordinate, price)
    } deriving Show
makeLenses ''Typex

initTypexLevel :: Int -> Int -> Int -> [Typex] 
initTypexLevel a b c = [ Typex a (x, y) [(0,(0,0),0.0)]
                       | x <- [0..b], y <- [0..c]
                       ]

buildNestedTypexs :: [(Int, Int)] -> [[Typex]]
buildNestedTypexs pts
     = setConnectionsx [ initTypexLevel i y y
                      | (i,(_,y)) <- zip [0..] pts
                      ]

setConnectionsx :: [[Typex]] -> [[Typex]]
setConnectionsx (x:rest@(y:_)) = map (connect y) x : setConnectionsx rest
  where connect :: [Typex] -> Typex -> Typex
        connect txs tx
          = tx & connections .~ (map ((tx ^. level) + 1, , 0.0) $ txs ^.. traverse.coordinate)
setConnectionsx lst = lst

setInitPrices  :: [[Typex]] -> [[Typex]]
setInitPrices  (x:rest) = map setIndexPrices x : setInitPrices  rest
  where setIndexPrices :: Typex -> Typex
        setIndexPrices tx =  n & connections .~ ??? -- using iset (?), set the price in every 3-tuple so that price = f (index of the 3-tuple) where f = i*2
setInitPrices  lst = lst

Solution

  • You are probably looking for:

      where setIndexPrices :: Typex -> Typex
            setIndexPrices tx =  tx & connections .> traversed <. _3 .@~ f
            f i = 2 * fromIntegral i
    

    Here, .@~ is the operator version of iset, and .> and <. are variants of the composition operator . used to combine indexed optics.

    If you consider the simpler unindexed optic:

    connections . traverse . _3
    

    This optic takes a TypeX, focuses on its _connections fields, traverses the list of connections, and focuses on the third field (the price) of each connection. The result is an optic that traverses all the prices in the TypeX in order.

    To index this optic, we need to "upgrade" the unindexed traverse to the indexed traversed. Then, we want to use the index-preserving composition operators .> and <. where the less/greater than signs point to the part of the optic that has the index we want. (In more complicated scenarios with multiple indexes, you can use <.> to combine indexes from two optics into index pairs (i,j).)

    That's how we get:

    connections .> traversed <. _3
    

    It still traverses all the prices in the TypeX in order, but it also carries around the index from the traversal.

    Note that setInitPrices is actually one of those functions that's easily written as an "all at once" lens computation. The map setIndexPrices and recursion just traverse the nested list, so they're the equivalent of the optic traverse . traverse. So, we can use:

    setInitPrices' :: [[Typex]] -> [[Typex]]
    setInitPrices' = traverse .> traverse .> connections .> traversed <. _3 .@~ f
      where f i = 2 * fromIntegral i
    

    Finally, it's maybe worth noting that if you have a complicated indexed optic like:

    a .> b .> c .> d <. e <. f <. g
    

    for obscure reasons (right associativity of the operators and the fact that .> is identical to .) this is always equivalent to:

    a . b . c .> d <. e . f . g
    

    and this is the more common way to write it. So, the final version of setInitPrices' would be:

    setInitPrices' :: [[Typex]] -> [[Typex]]
    setInitPrices' = traverse . traverse . connections .> traversed <. _3 .@~ f
      where f i = 2 * fromIntegral i