Refiriéndose a la excelente respuesta de jbapple con respecto a replicate
, pero usando replicateA
(que replicate
se basa en) en su lugar, se me ocurrió lo siguiente:
--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
(en una versión ligeramente más eficiente) ya está definido y utilizado internamente en Data.Sequence
para la construcción de árboles de los dedos que son resultados de las clases.
En general, la intuición para replicateA
es simple. replicateA
está construido encima de la función applytiveTree . applicativeTree
toma un pedazo de un árbol de un tamaño m
y produce un árbol bien equilibrado que contiene n
copias de este. Las cajas para n
hasta 8 (un solo Deep
dedo) están codificadas. Cualquier cosa por encima de esto, y se invoca recursivamente. El elemento "aplicativo" es simplemente que intercala la construcción del árbol con efectos de enhebrado, como, en el caso del código anterior, el estado.
La go
función, que se replica, es simplemente una acción que obtiene el estado actual, saca un elemento de la parte superior y reemplaza el resto. En cada invocación, por lo tanto, baja más abajo en la lista proporcionada como entrada.
Algunas notas más concretas
main = print (length (show (Seq.fromList [1..10000000::Int])))
En algunas pruebas simples, esto produjo una compensación de rendimiento interesante. La función principal anterior se ejecutó casi 1/3 más bajo con myFromList que con fromList
. Por otro lado, myFromList
usó un montón constante de 2 MB, mientras que el estándar fromList
usó hasta 926 MB. Ese 926 MB surge de la necesidad de mantener toda la lista en la memoria a la vez. Mientras tanto, la solución myFromList
es capaz de consumir la estructura de forma perezosa. El problema con la velocidad resulta del hecho de que myFromList
debe realizar aproximadamente el doble de asignaciones (como resultado de la construcción / destrucción del par de la mónada estatal) quefromList
. Podemos eliminar esas asignaciones moviéndonos a una mónada de estado transformada por CPS, pero eso da como resultado retener mucha más memoria en un momento dado, porque la pérdida de la pereza requiere atravesar la lista de manera no continua.
Por otro lado, si en lugar de forzar la secuencia completa con un espectáculo, me muevo a solo extraer la cabeza o el último elemento, myFromList
inmediatamente presenta una ganancia mayor: extraer el elemento de la cabeza es casi instantáneo y extraer el último elemento es 0.8s . Mientras tanto, con el estándar fromList
, extraer la cabeza o el último elemento cuesta ~ 2.3 segundos.
Todo esto son detalles, y es consecuencia de la pureza y la pereza. En una situación con mutación y acceso aleatorio, me imagino que la replicate
solución es estrictamente mejor.
Sin embargo, plantea la cuestión de si hay una manera de reescribir applicativeTree
tal que myFromList
sea estrictamente más eficiente. Creo que el problema es que las acciones aplicativas se ejecutan en un orden diferente al que se atraviesa naturalmente el árbol, pero no he trabajado completamente sobre cómo funciona esto, o si hay una manera de resolverlo.