Как изменить поля вложенного пользовательского типа данных с помощью линз, когда изменения зависят от индексов - PullRequest
0 голосов
/ 05 марта 2020

Учитывая следующее:

{-# LANGUAGE TemplateHaskell   #-}

import Control.Lens

data Typex = Typex 
    { _level       :: Int
    , _coordinate  :: (Int, Int)
    , _connections :: [(Int,(Int,Int))]
    } deriving Show
makeLenses ''Typex

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

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

setConnections :: [[Typex]] -> [[Typex]]
setConnections = ?

Как я могу использовать линзы для изменения connections во всех Typex с функцией типа [[Typex]] -> [[Typex]] таким образом, чтобы в каждом Typex

connections = [(level of Typex being modified +1, (x, y))] where
x,y = 0..(length of next [Typex] in [[Typex]])/2

X и y оба должны go через эту длину следующего [Typex]. Окончательный [Typex] должен быть оставлен без изменений, если это возможно. Таким образом, все соединения каждого Typex в одном и том же [Typex] одинаковы.

Выход для setConnections $ buildNestedTypexs [(0,1),(1,1)] должен быть:

[ [ Typex { _level = 0
          , _coordinate = (0,0)
          , _connections = [(1,(0,0)), (1,(0,1)), (1,(1,0)), (1,(1,1))] }
  , Typex { _level = 0
          , _coordinate = (0,1)
          , _connections = [(1,(0,0)), (1,(0,1)), (1,(1,0)), (1,(1,1))] }
  , Typex { _level = 0
          , _coordinate = (1,0)
          , _connections = [(1,(0,0)), (1,(0,1)), (1,(1,0)), (1,(1,1))] }
  , Typex { _level = 0
          , _coordinate = (1,1)
          , _connections = [(1,(0,0)), (1,(0,1)), (1,(1,0)), (1,(1,1))] }
  ]
 ,[ Typex { _level = 1
          , _coordinate = (0,0)
          , _connections = [(0,(0,0))] }
  , Typex { _level = 1
          , _coordinate = (0,1)
          , _connections = [(0,(0,0))] }
  , Typex { _level = 1
          , _coordinate = (1,0)
          , _connections = [(0,(0,0))] }
  , Typex { _level = 1
          , _coordinate = (1,1)
          , _connections = [(0,(0,0))] }
  ]]

Полагаю, мне понадобится import Control.Lens.Indexed но это все, поэтому вся помощь приветствуется.

1 Ответ

3 голосов
/ 06 марта 2020

Это то, что вы хотите?

{-# LANGUAGE TupleSections #-}

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

Это не чисто решение для линз, но я считаю, что, как правило, при работе с линзами не всегда хорошая идея получить линзы сделать все . Это просто делает вещи трудными для написания и трудными для понимания.

Здесь я использовал "plain Haskell" во многих местах: для сопоставления с шаблоном с ручной рекурсией для пар процесса x, y последовательных [Typex] с, и я использовал map до connect каждый Typex в первом x :: [Typex] со вторым y :: [Typex]. Я также использовал map, чтобы добавить новый уровень в список координат, чтобы сгенерировать новое значение connections.

Здесь используются только следующие выражения объектива:

  • tx & connections .~ (...), которое заменяет connections поле tx :: Typex новым значением
  • tx ^. level, которое выбирает уровень текущего tx :: Typex
  • txs ^.. traverse.coordinate, которое выбирает coordinate поля всех Typex значений в списке txs :: [Typex] и возвращает их в виде списка [(Int,Int)]

По моему мнению, такой вид баланса между объективами и "обычным Haskell" лучший способ справиться со сложными преобразованиями.

...