$\begingroup$

Riffing on jbapple's excellent answer regarding replicate , but using replicateA (which replicate is built on) instead, I came up with the following:

--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 a slightly more efficient version) is already defined and used internally in Data.Sequence for constructing finger trees that are results of sorts.

In general, the intuition for replicateA is simple. replicateA is built on top of the applicativeTree function. applicativeTree takes a piece of a tree of a size m , and produces a well-balanced tree containing n copies of this. The cases for n up to 8 (a single Deep finger) are hard coded. Anything above this, and it invokes itself recursively. The "applicative" element is simply that it interleaves the construction of the tree with threading effects through, such as, in the case of the above code, state.

The go function, which is replicated, is simply an action which gets the current state, pops an element off the top, and replaces the remainder. On each invocation, it thus steps further down the list provided as input.

Some more concrete notes

main = print (length (show (Seq.fromList [1..10000000::Int])))

On some simple tests, this yielded an interesting performance tradeoff. The main function above ran almost 1/3 lower with myFromList than with fromList . On the other hand, myFromList used a constant heap of 2MB, while the standard fromList used up to 926MB. That 926MB arises from needing to hold the entire list in memory at once. Meanwhile, the solution with myFromList is able to consume the structure in a lazy streaming fashion. The issue with speed results from the fact that myFromList must perform roughly twice as many allocations (as a result of the pair construction/destruction of the state monad) as fromList . We can eliminate those allocations by moving to a CPS-transformed state monad, but that results in holding on to far more memory at any given time, because the loss of laziness requires traversing the list in a non-streaming manner.

On the other hand, if rather than forcing the entire sequence with a show, I move to just extracting the head or last element, myFromList immediately presents a bigger win -- extracting the head element is nearly instant, and extracting the last element is 0.8s. Meanwhile, with the standard fromList , extracting either the head or last element costs ~2.3 seconds.

This is all details, and is a consequence of purity and laziness. In a situation with mutation and random access, I would imagine the replicate solution is strictly better.