generic finger-tree structure (http://staff.city.ac.uk/~ross/papers/FingerTree.html)
#9Splitting is too lazy; appending seems so too
splitTree
is much too lazy. Instead of building two trees, it builds two chains of thunks which, when forced, will build trees. There is very little to be gained from this, and the cost is high. Basically, it only makes sense when <>
is expensive, in which case a monoidal finger tree probably isn't the right data structure anyway. I suggest something like this:
splitTree :: (Measured v a) =>
(v -> Bool) -> v -> FingerTree v a -> Split (FingerTree v a) a
splitTree _ _ Empty = illegal_argument "splitTree"
splitTree _ _ (Single x) = Split Empty x Empty
splitTree p i (Deep _ pr m sf)
| p vpr = let !(Split l x r) = splitDigit p i pr
in Split (maybe Empty digitToTree l) x (deepL r m sf)
| p vm = let !(Split ml xs mr) = splitTree p vpr m
!(Split l x r) = splitNode p (vpr `mappend` measure ml) xs
in Split (deepR pr ml l) x (deepL r mr sf)
| otherwise = let !(Split l x r) = splitDigit p vm sf
in Split (deepR pr m l) x (maybe Empty digitToTree r)
where
vpr = i `mappend` measure pr
vm = vpr `mappend` measure m
(><)
is also excessively lazy, in my opinion. While there's a theoretical advantage to building only as much as is immediately necessary, repeated appends will build up thunk chains just under the first node, which are lousy for practical performance. I found that forcing (the rebuilt portion of) the spine was very helpful for appends in Data.Sequence
; I imagine it would be so here too.
- description updated
- description updated
I forgot: one helpful thing is to make the
Split
andSearchResult
constructors appropriately strict. That reduces the need for strictness annotations and makes GHC's analysis more reliable.