En me basant sur l'excellente réponse de jbapple concernant replicate
, mais en utilisant replicateA
(qui replicate
est construit sur) à la place, j'ai trouvé ce qui suit:
--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
(dans une version plus légère efficace) est déjà défini et utilisé en interne dans Data.Sequence
pour la construction d' arbres de doigts qui sont les résultats de toutes sortes.
En général, l'intuition de replicateA
est simple. replicateA
est construit au-dessus de la fonction applicativeTree . applicativeTree
prend un morceau d'arbre d'une taille m
et produit un arbre bien équilibré contenant des n
copies de celui-ci. Les cas pour n
jusqu'à 8 (un seul Deep
doigt) sont codés en dur. Tout ce qui est au-dessus de cela, et il s’invoque récursivement. L'élément "applicatif" est simplement qu'il entrelace la construction de l'arbre avec des effets de filetage à travers, comme, dans le cas du code ci-dessus, l'état.
La go
fonction, qui est répliquée, est simplement une action qui obtient l'état actuel, fait apparaître un élément par le haut et remplace le reste. A chaque appel, il descend ainsi plus loin dans la liste fournie en entrée.
Quelques notes plus concrètes
main = print (length (show (Seq.fromList [1..10000000::Int])))
Sur certains tests simples, cela a donné un compromis intéressant sur les performances. La fonction principale ci-dessus a fonctionné presque 1/3 de moins avec myFromList qu'avec fromList
. D'autre part, myFromList
utilisé un tas constant de 2 Mo, tandis que la norme fromList
utilisait jusqu'à 926 Mo. Ce 926 Mo découle de la nécessité de conserver la liste entière en mémoire à la fois. Pendant ce temps, la solution avec myFromList
est capable de consommer la structure en mode streaming paresseux. Le problème de la vitesse résulte du fait qu'il myFromList
doit effectuer environ deux fois plus d'allocations (en raison de la construction / destruction de la paire de la monade d'État) quefromList
. Nous pouvons éliminer ces allocations en passant à une monade d'état transformée par CPS, mais cela aboutit à conserver beaucoup plus de mémoire à un moment donné, car la perte de paresse nécessite de parcourir la liste de manière non continue.
D'un autre côté, si plutôt que de forcer toute la séquence avec un spectacle, je passe à l'extraction de la tête ou du dernier élément, myFromList
présente immédiatement un gain plus important - l'extraction de l'élément de tête est presque instantanée, et l'extraction du dernier élément est de 0,8 s . Pendant ce temps, avec la norme fromList
, l'extraction de la tête ou du dernier élément coûte environ 2,3 secondes.
Ce ne sont que des détails et une conséquence de la pureté et de la paresse. Dans une situation de mutation et d'accès aléatoire, j'imagine que la replicate
solution est strictement meilleure.
Cependant, cela soulève la question de savoir s'il existe un moyen de réécrire applicativeTree
ce qui myFromList
est strictement plus efficace. Le problème est, je pense, que les actions applicatives sont exécutées dans un ordre différent de celui de l'arbre qui est naturellement traversé, mais je n'ai pas complètement expliqué comment cela fonctionne, ou s'il existe un moyen de résoudre ce problème.