Examinando a excelente resposta do jbapple em relação a replicate
, mas usando replicateA
(que replicate
é construído sobre), eu vim com o seguinte:
--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
(em uma versão ligeiramente mais eficiente) já está definido e usado internamente no Data.Sequence
para a construção de árvores de dedo que são resultados de tipos.
Em geral, a intuição para replicateA
é simples. replicateA
é construído sobre a função applicativeTree . applicativeTree
pega um pedaço de uma árvore de um tamanho m
e produz uma árvore bem equilibrada contendo n
cópias disso. Os casos para n
até 8 (um único Deep
dedo) são codificados. Qualquer coisa acima disso, e ela se invoca recursivamente. O elemento "aplicative" é simplesmente que ele intercala a construção da árvore com efeitos de encadeamento através de, como, no caso do código acima, estado.
A go
função, replicada, é simplesmente uma ação que obtém o estado atual, retira um elemento da parte superior e substitui o restante. Em cada chamada, portanto, desce a lista fornecida como entrada.
Algumas notas mais concretas
main = print (length (show (Seq.fromList [1..10000000::Int])))
Em alguns testes simples, isso resultou em uma troca interessante de desempenho. A função principal acima foi executada quase 1/3 mais baixa com myFromList do que com fromList
. Por outro lado, myFromList
usou um heap constante de 2 MB, enquanto o padrão fromList
usou até 926 MB. Esses 926MB surgem da necessidade de manter a lista inteira na memória de uma só vez. Enquanto isso, a solução myFromList
é capaz de consumir a estrutura de forma lenta e lenta. O problema da velocidade resulta do fato de que ele myFromList
deve executar aproximadamente o dobro de alocações (como resultado da construção / destruição do par da mônada do estado) quefromList
. Podemos eliminar essas alocações mudando para uma mônada de estado transformada em CPS, mas isso resulta em reter muito mais memória a qualquer momento, porque a perda de preguiça exige que a lista seja percorrida de maneira não contínua.
Por outro lado, se em vez de forçar toda a sequência com um show, passo apenas para extrair a cabeça ou o último elemento, myFromList
imediatamente apresenta uma vitória maior - extrair o elemento da cabeça é quase instantâneo e extrair o último elemento é 0,8s . Enquanto isso, com o padrão fromList
, extrair a cabeça ou o último elemento custa ~ 2,3 segundos.
Isso são todos os detalhes e é uma consequência da pureza e da preguiça. Em uma situação com mutação e acesso aleatório, eu imaginaria que a replicate
solução é estritamente melhor.
No entanto, levanta a questão de saber se existe uma maneira de reescrever applicativeTree
tal que myFromList
seja estritamente mais eficiente. Acho que a questão é que as ações do aplicativo são executadas em uma ordem diferente da que a árvore é atravessada naturalmente, mas ainda não trabalhei completamente como isso funciona ou se existe uma maneira de resolver isso.