Riffing sull'ottima risposta di jbapple per quanto riguarda replicate
, ma usando replicateA
(che replicate
è costruito su) invece, ho trovato quanto segue:
--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
(in una versione leggermente più efficiente) è già definito e utilizzato internamente in Data.Sequence
per la costruzione di alberi delle dita che sono i risultati di sorta.
In generale, l'intuizione per replicateA
è semplice. replicateA
è costruito sopra la funzione applicativeTree . applicativeTree
prende un pezzo di un albero di dimensioni m
e produce un albero ben bilanciato contenente n
copie di questo. I casi per un n
massimo di 8 (un singoloDeep
dito) sono codificati. Qualunque cosa al di sopra di questo, e si invoca ricorsivamente. L'elemento "applicativo" è semplicemente che intercala la costruzione dell'albero con effetti di threading, come, nel caso del codice precedente, stato.
Il go
funzione, che viene replicata, è semplicemente un'azione che ottiene lo stato corrente, fa apparire un elemento dall'alto e sostituisce il resto. Ad ogni invocazione, si sposta quindi più in basso nell'elenco fornito come input.
Alcune note più concrete
main = print (length (show (Seq.fromList [1..10000000::Int])))
In alcuni semplici test, questo ha prodotto un interessante compromesso delle prestazioni. La funzione principale sopra era quasi 1/3 inferiore con myFromList rispetto a fromList
. D'altra parte, ha myFromList
utilizzato un heap costante di 2 MB, mentre lo standard ha fromList
utilizzato fino a 926 MB. Quel 926 MB deriva dalla necessità di conservare l'intero elenco in memoria contemporaneamente. Nel frattempo, la soluzione con myFromList
è in grado di consumare la struttura in modo pigro in streaming. Il problema con la velocità deriva dal fatto che myFromList
deve eseguire all'incirca il doppio del numero di allocazioni (come risultato della costruzione / distruzione della coppia della monade dello stato) difromList
. Possiamo eliminare tali allocazioni passando a una monade di stato trasformata in CPS, ma ciò si traduce in una maggiore quantità di memoria in un dato momento, poiché la perdita di pigrizia richiede di attraversare l'elenco in modo non streaming.
D'altra parte, se invece di forzare l'intera sequenza con uno spettacolo, mi sposto semplicemente sull'estrazione della testa o dell'ultimo elemento, myFromList
presenta immediatamente una vittoria più grande - l'estrazione dell'elemento testa è quasi istantanea e l'estrazione dell'ultimo elemento è 0,8s . Nel frattempo, con lo standard fromList
, l'estrazione della testa o dell'ultimo elemento costa ~ 2,3 secondi.
Questo è tutti i dettagli ed è una conseguenza di purezza e pigrizia. In una situazione con mutazione e accesso casuale, immagino che la replicate
soluzione sia strettamente migliore.
Tuttavia, solleva la questione se esiste un modo per riscrivere in applicativeTree
modo myFromList
strettamente più efficiente. Il problema è, penso, che le azioni applicative siano eseguite in un ordine diverso rispetto a quello che l'albero viene attraversato naturalmente, ma non ho completamente lavorato su come funziona, o se c'è un modo per risolverlo.