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.Sequenceper 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 . applicativeTreeprende un pezzo di un albero di dimensioni me produce un albero ben bilanciato contenente ncopie di questo. I casi per un nmassimo 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 myFromListutilizzato un heap costante di 2 MB, mentre lo standard ha fromListutilizzato 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 myFromListdeve 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, myFromListpresenta 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 replicatesoluzione sia strettamente migliore.
Tuttavia, solleva la questione se esiste un modo per riscrivere in applicativeTreemodo myFromListstrettamente 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.