Как сохранить нативную структуру циклического списка во время преобразований в Haskell?
Я изучаю графоподобные вещи в Хаскеле, используя технику "связывания узла". Полагаю, циклические списки - это просто внутренняя реализация бесконечного списка, поэтому в идеальном мире не нужно заботиться о subj. Но это может оказать существенное влияние на вычислительную сложность, рассмотрим пример 1D клеточных автоматов с зацикленным миром:
ribbon = let x = 0 : y
y = 1 : x
in x
update_cycle :: Num a => [a] -> [a]
update_cycle lst@(_:xs) = ( f lst : update_cycle xs)
update_cycle [] = error "infinite cycle assumed"
f :: Num a => [a] -> a -- some arbitrary list reduction
f (x:(y:_)) = traceShow "f called" $ x - y
Это цикл с двумя ячейками. Давайте сделаем шаг:
*Main> take 2 $ update_cycle ribbon
[-1,1]
"f called"
"f called"
Это хорошо, теперь два шага:
*Main> take 2 $ (update_cycle . update_cycle) ribbon
[-2,2]
"f called"
"f called"
"f called"
"f called"
"f called"
Это пять вызовов вместо четырех, и это фактически означает увеличение количества вызовов на каждом шаге, поэтому мы имеем квадратичную сложность общего количества шагов вместо линейного. Я мог бы сделать циклические явные, как это:
newtype UserDefCyclic a = UserDefCyclic { fromTC :: [a] }
udcToList :: UserDefCyclic a -> [a]
udcToList (UserDefCyclic x) = cycle x
update_udc :: Num a => UserDefCyclic a -> UserDefCyclic a
update_udc (UserDefCyclic core) = UserDefCyclic $ take (length core) (update_cycle ( cycle core ))
Но это ужасно, хотя мне действительно интересны более сложные структуры, такие как графы. Вопрос: есть ли способ, чтобы код был красивым и быстрым? Или есть надежда, что компилятор будет лучше обрабатывать приведенный выше код в будущем?