Mutable List builder in the ST Monad
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