Search code examples
haskellhaskell-lens

using Lens for nested folds with a threaded accumulator


I have this code to transform some data:

newtype XXX = XXX
  { xxx :: [ Text ]
  } deriving (Eq, Show)

oldToNew
  :: [XXX] -> [(Int, [(Int, Text)])]
  -> [[(Int, Int, Text)]]
oldToNew xs old                = map    go1          xs
 where
  go1 (XXX eqs)                = foldr  go2      []  eqs
  go2 l                    acc = foldr (go3 l)   acc old
  go3 l (i, w)             acc = foldr (go4 i l) acc w
  go4 idx1 label1 (idx2, label2) acc =
    if label1 == label2
    then (idx1, idx2, label2):acc
    else acc

Note two things:

  1. it threads an accumulator (initially empty) through all folds
  2. each fold picks up an argument from the previous fold to pass to the next "go"

How would one use Control.Lens and/or Optics to do the above.

Test case:

oldToNewSpec :: Spec
oldToNewSpec  =
  it "oldToNew" $
    oldToNew [ XXX ["this", "that", "also"]
             , XXX ["here", "there"] ]

             [ (0, [(4, "this")])
             , (0, [(1, "here")])
             , (1, [(5, "that")])
             , (2, [(2, "also")])
             , (2, [(3, "there")]) ]
    `shouldBe`
    [ [(0,4,"this"),(1,5,"that"),(2,2,"also")]
    , [(0,1,"here"),(2,3,"there")] ]

Solution

  • I don't think you need lenses to make this significantly more readable. List comprehensions ought to get you where you need to go without too much trouble.

    oldToNew :: [[Text]] -> [(Int, [(Int, Text)]] -> [[(Text, Int, Int)]]
    oldToNew tss rs = [[(t, b, e) | t <- ts, (b, te) <- rs, (e, t') <- te, t == t'] | ts <- tss]
    

    Using a proper data structure will make things significantly more efficient, and probably more readable.

    oldToNew :: Ord k => Map k v -> [[k]] -> [[(k, v)]]
    oldToNew kvs = map (concatMap look) where
        look k = case M.lookup k kvs of
            Nothing -> []
            Just v -> [(k, v)]