Опираясь на превосходный ответ jbapple относительно replicate
, но используя вместо этого replicateA
(который replicate
построен на), я придумал следующее:
--Unlike fromList, one needs the length explicitly.
myFromList :: Int -> [b] -> Seq b
myFromList l xs = flip evalState xs $ Seq.replicateA l go
where go = do
(y:ys) <- get
put ys
return y
myFromList
(в нескольких более эффективной версии) уже определен и используются внутри в Data.Sequence
построении пальцев дерев , которые являются результатами сортов.
В общем, интуиция для replicateA
проста. replicateA
построен поверх функции ApplicativeTree . applicativeTree
занимает кусок дерева размером m
и создает хорошо сбалансированное дерево, содержащее его n
копии. Случаи n
до 8 (один Deep
палец) жестко закодированы. Что-нибудь выше этого, и это вызывает себя рекурсивно. «Аппликативный» элемент заключается просто в том, что он чередует конструкцию дерева с помощью потоковых эффектов, таких как, в случае приведенного выше кода, состояние.
go
Функция, которая реплицируется, это просто действие , которое получает текущее состояние, появляется элемент с верхней, и заменяет остаток. Таким образом, при каждом вызове он перемещается вниз по списку, предоставленному в качестве ввода.
Еще несколько конкретных заметок
main = print (length (show (Seq.fromList [1..10000000::Int])))
На некоторых простых тестах это привело к интересному компромиссу производительности. Основная функция выше с myFromList почти на 1/3 ниже, чем сfromList
. С другой стороны, myFromList
используется постоянная куча 2 МБ, в то время как стандартная fromList
используется до 926 МБ. Это 926MB возникает из-за необходимости хранить весь список в памяти сразу. Между тем, решение с myFromList
возможностью использования структуры ленивым потоковым способом. Проблема со скоростью связана с тем, что myFromList
необходимо выполнить примерно вдвое больше распределений (в результате построения пары / разрушения государственной монады), чемfromList
, Мы можем устранить эти распределения, перейдя к монаде состояния, преобразованной в CPS, но это приводит к тому, что в любой момент времени удерживается гораздо больше памяти, потому что потеря лени требует обхода списка без использования потоковой передачи.
С другой стороны, если вместо того, чтобы форсировать всю последовательность шоу, я перехожу к простому извлечению головы или последнего элемента, myFromList
немедленно представляет больший выигрыш - извлечение элемента головы происходит практически мгновенно, а извлечение последнего элемента составляет 0,8 с. , Между тем, при использовании стандарта fromList
извлечение заголовка или последнего элемента стоит ~ 2,3 секунды.
Это все детали и является следствием чистоты и лени. В ситуации с мутацией и произвольным доступом, я думаю, что replicate
решение будет строго лучше.
Тем не менее, это поднимает вопрос о том, есть ли способ переписать applicativeTree
такой, который myFromList
является строго более эффективным. Проблема, я думаю, в том, что аппликативные действия выполняются не в том порядке, в котором обходится дерево, но я не до конца проработал, как это работает, или есть способ решить эту проблему.