Mutable List builder in the ST Monad
at master 266 lines 6.2 kB view raw
1{-# LANGUAGE DoAndIfThenElse, BangPatterns, NamedFieldPuns #-} 2-- | Mutable List Builder. 3-- 4-- A @ListBuilder s a@ is like a wrapper over @ST s [a]@, but uses unsafe 5-- mutation to achieve constant time append as well as prepend. 6-- 7-- As the builder is backed with a standard 'Data.List.List', it 8-- is light-weight and cheap to return to a list. 9-- 10-- Code from this module is derived from Scala's 11-- [ListBuffer](https://www.scala-lang.org/api/current/scala/collection/mutable/ListBuffer.html) 12-- module, using the unsafe set field technique described by 13-- [Twan van Laarhoven](https://www.twanvl.nl/blog/haskell/unsafe-sequence). 14module Data.ListBuilder ( 15 -- * Mutable list builder 16 ListBuilder 17 18 -- * Construction 19 , newBuilder 20 21 -- * Mutations 22 , append 23 , prepend 24 , insert 25 , filterInPlace 26 , clear 27 28 -- * Accessors 29 , readLength 30 , readFirst 31 , readLast 32 , readAt 33 34 -- * Conversions 35 , freeze 36 , unsafeFreeze 37 ) where 38 39import Data.ListBuilder.Unsafe 40import qualified Data.List 41 42import Control.Monad (when) 43import Control.Monad.ST 44import Control.Monad.ST.Unsafe 45 46import Data.Foldable (foldr') 47import Data.Maybe (listToMaybe) 48import Data.STRef (STRef, newSTRef, readSTRef, writeSTRef, modifySTRef') 49 50-- | A List Builder. 51-- 52-- This builder is backed by a standard haskell 'Data.List.List'. 53-- It offers predictable (and fast) operations, and doesn't 54-- pause for grow operations as an array based builder might. 55data ListBuilder s a = ListBuilder { 56 start :: STRef s [a] 57 , end :: STRef s [a] 58 , len :: STRef s Int 59} 60 61-- | Create a new, empty 'ListBuilder' 62newBuilder :: ST s (ListBuilder s a) 63newBuilder = do 64 start <- newSTRef [] 65 end <- newSTRef [] 66 len <- newSTRef 0 67 pure $ 68 ListBuilder start end len 69 70-- | Append an item to the back of the 'ListBuilder' 71-- 72-- /O(1)/ 73append :: a -> ListBuilder s a -> ST s () 74append a ListBuilder { start, end, len } = do 75 let 76 !last' = [a] 77 len' <- readSTRef len 78 79 if len' == 0 then do 80 writeSTRef start last' 81 writeSTRef end last' 82 else do 83 end' <- readSTRef end 84 unsafeIOToST $ 85 unsafeSetField 1 end' last' 86 writeSTRef end last' 87 88 modifySTRef' len (+1) 89 90-- | Prepend an item to the front of the 'ListBuilder' 91-- 92-- /O(1)/ 93prepend :: a -> ListBuilder s a -> ST s () 94prepend a ListBuilder { start, end, len } = do 95 front <- readSTRef start 96 len' <- readSTRef len 97 98 let 99 !front' = a : front 100 101 when (len' == 0) $ 102 writeSTRef end front' 103 104 writeSTRef start front' 105 modifySTRef' len (+1) 106 107 108-- | Internal function. Locates the previous cons cell. 109-- 110-- /O(N)/ 111locate :: Int -> ListBuilder s a -> ST s [a] 112locate 0 _ = error "Internal error: locate called on 0" 113locate i ListBuilder { start, end, len } = do 114 l <- readSTRef len 115 116 if l == i then 117 readSTRef end 118 119 else do 120 start' <- readSTRef start 121 cur <- newSTRef start' 122 let 123 go 0 = readSTRef cur 124 go j = do 125 modifySTRef' cur (drop 1) 126 go (j - 1) 127 go (i - 1) 128 129-- | Insert into a location in a 'ListBuilder'. 130-- 131-- This function doesn't create a new spine 132-- across the list builder, and only allocates 133-- the new cons cell itself. 134-- 135-- /O(N)/ 136insert :: Int -> a -> ListBuilder s a -> ST s () 137insert ix _ _ | ix < 0 = error "Index out of bounds" 138insert 0 a bldr = prepend a bldr 139insert ix a bldr= do 140 len' <- readSTRef (len bldr) 141 if ix == len' then 142 append a bldr 143 else if ix > len' then 144 error "Index out of bounds" 145 else do 146 prev <- locate ix bldr 147 let !pn = drop 1 prev 148 let !nx = a:pn 149 unsafeIOToST $ 150 unsafeSetField 1 prev nx 151 152 153-- | The current length of the 'ListBuilder'. 154-- 155-- /O(1)/ 156readLength :: ListBuilder s a -> ST s Int 157readLength bldr = 158 readSTRef (len bldr) 159 160 161-- | Empty the 'ListBuilder' of all values. 162-- 163-- /O(1)/ 164clear :: ListBuilder s a -> ST s () 165clear ListBuilder { start, end, len } = do 166 writeSTRef start [] 167 writeSTRef end [] 168 writeSTRef len 0 169 170 171-- | Filter the 'ListBuilder' with the supplied predicate 172-- 173-- /O(N)/ 174filterInPlace :: (a -> Bool) -> ListBuilder s a -> ST s () 175filterInPlace func ListBuilder { start, end, len } = do 176 prev <- newSTRef Nothing 177 start' <- readSTRef start 178 cur <- newSTRef start' 179 let 180 go = do 181 cur' <- readSTRef cur 182 case cur' of 183 [] -> return () 184 185 (h:follow) -> do 186 prev' <- readSTRef prev 187 if not (func h) then do 188 case prev' of 189 Nothing -> 190 writeSTRef start follow 191 Just y -> 192 unsafeIOToST $ 193 unsafeSetField 1 y follow 194 195 modifySTRef' len (\x -> x - 1) 196 else 197 writeSTRef prev (Just cur') 198 199 writeSTRef cur follow 200 go 201 202 go 203 204 prev' <- readSTRef prev 205 case prev' of 206 Nothing -> do 207 writeSTRef end [] 208 Just y -> 209 writeSTRef end y 210 211 212-- | Return the current last element in the 'ListBuilder' 213-- 214-- /O(1)/ 215readLast :: ListBuilder s a -> ST s (Maybe a) 216readLast ListBuilder { end } = do 217 listToMaybe <$> readSTRef end 218 219 220 221-- | Return the current first element in the 'ListBuilder' 222-- 223-- /O(1)/ 224readFirst :: ListBuilder s a -> ST s (Maybe a) 225readFirst ListBuilder { start } = do 226 listToMaybe <$> readSTRef start 227 228 229-- | Return the current element at a particular index for 230-- the 'ListBuilder' 231-- 232-- /O(N)/ 233readAt :: ListBuilder s a -> Int -> ST s (Maybe a) 234readAt ListBuilder { start } ix = do 235 (Data.List.!? ix) <$> readSTRef start 236 237 238-- | Return the 'Data.List.List' backing the 'ListBuilder'. 239-- 240-- This does /not/ stop mutations made to 241-- the builder from affecting the resultant 242-- list. So one must not continue to call the 243-- mutating functions. 244-- 245-- This function is safe in tail position within a 246-- call to @runST@. 247-- 248-- /O(1)/ 249unsafeFreeze :: ListBuilder s a -> ST s [a] 250unsafeFreeze bldr = 251 readSTRef (start bldr) 252 253 254-- | Freeze the result and return the list. 255-- 256-- This function strictly copies the spine 257-- of the list. 258-- 259-- /O(n)/ 260freeze :: ListBuilder s a -> ST s [a] 261freeze bldr = do 262 aliased <- 263 readSTRef (start bldr) 264 265 return $! 266 foldr' (:) [] aliased