1-- | This module provides a `Builder` monoid and a `PutM` monad
2-- | for serializing `Data.ArrayBuffer.Types.ArrayBuffer`s.
3-- | See the package README for usage examples.
5-- | Writing to an `ArrayBuffer` is an `Effect`ful activity, so most
6-- | functions in this module must be run in a `MonadEffect` context.
7module Data.ArrayBuffer.Builder
51import Control.Monad.Writer.Trans (WriterT, execWriterT, tell)
52import Data.ArrayBuffer.ArrayBuffer as AB
53import Data.ArrayBuffer.DataView as DV
54import Data.ArrayBuffer.Typed as AT
55import Data.ArrayBuffer.Types (ArrayBuffer, Uint8Array)
56import Data.Float32 (Float32)
57import Data.Maybe (Maybe(..))
58import Data.UInt (UInt)
60import Effect.Class (class MonadEffect, liftEffect)
62-- | The `PutM` monad is a Writer transformer monad which
63-- | [gives us access to do-notation](https://wiki.haskell.org/Do_notation_considered_harmful#Library_design)
64-- | for building `ArrayBuffer`s.
65type PutM = WriterT Builder
67-- | `Put` monad is the non-transformer version of `PutM`.
70-- | Monoidal builder for `ArrayBuffer`s.
72-- | ### Left-associative `<>>` append operator
74-- | __TL;DR__ You probably don't want to use the `Builder` monoid directly
75-- | in your code, it’s better to use the `PutM` monad with do-notation instead.
77-- | The `Builder` monoid in this library is efficient when we `snoc` single
78-- | items onto the end of it, or when we only `cons` single items to the
79-- | beginning, but it can be less efficient when we are mixing `cons` and
81-- | This matters when we're appending three or more `Builder`s
82-- | in one associative expression, because the
83-- | `Semigroup` append operator `<>` is right-associative.
85-- | To solve this, we provide an operator `<>>` for appending `Builders`.
86-- | `<>>` is exactly the same as `<>`, but left-associative.
87-- | This only matters when we're chaining together three
88-- | or more `Builder`s in a single associative expression.
91-- | builder₁ <> builder₂ <> builder₃
93-- | we should always prefer to
96-- | builder₁ <>> builder₂ <>> builder₃
98-- | so that we get the efficient
99-- | `snoc`ing of `Builder`s.
101-- | If we build our `ArrayBuffer`s with the `PutM` monad instead of appending by
102-- | using the `Semigroup` instance of `Builder`, then we always get the efficient
106 = Node Builder ArrayBuffer Builder
109instance semigroupBuilder :: Semigroup Builder where
110 append Null Null = Null
113 append l (Node Null rx rr) = Node l rx rr -- this is the snoc case
114 append (Node ll lx Null) r = Node ll lx r -- found the rightmost Null
116 append (Node ll lx lr) r = Node ll lx $ append lr r -- search down the right
117 -- side of the left tree
118 -- to find the rightmost
121infixl 5 append as <>>
123instance monoidBuilder :: Monoid Builder where
126-- | `Builder` is not an instance of `Foldable` because `Builder` is monomorphic.
127foldl :: forall b. (b -> ArrayBuffer -> b) -> b -> Builder -> b
129foldl f a (Node l x r) = foldl f (f (foldl f a l) x) r
131-- | Monomorphic foldM copied from
132-- | [`Data.Foldable.foldM`](https://pursuit.purescript.org/packages/purescript-foldable-traversable/4.1.1/docs/Data.Foldable#v:foldM)
133foldM :: forall b m. (Monad m) => (b -> ArrayBuffer -> m b) -> b -> Builder -> m b
134foldM f a0 = foldl (\ma b -> ma >>= flip f b) (pure a0)
136-- | Construct a `Builder` with a single `ArrayBuffer`. *O(1)*
137singleton :: ArrayBuffer -> Builder
138singleton buf = Node Null buf Null
140-- | Add an `ArrayBuffer` to the end of the `Builder`. *O(1)*
141snoc :: Builder -> ArrayBuffer -> Builder
142snoc bs x = Node bs x Null
144-- | Build a single `ArrayBuffer` from a `Builder`. *O(n)*
145execBuilder :: forall m. (MonadEffect m) => Builder -> m ArrayBuffer
147 let buflen = foldl (\b a -> b + AB.byteLength a) 0 bldr
148 -- Allocate the final ArrayBuffer
149 buf <- liftEffect $ AB.empty buflen
150 -- Then copy each ArrayBuffer into the final ArrayBuffer.
151 -- To memcpy from one ArrayBuffer to another, apparently we have to first view
152 -- both ArrayBuffers as ArrayView (Typed Array).
153 newview <- liftEffect (AT.whole buf :: Effect Uint8Array)
156 aview <- liftEffect (AT.whole a :: Effect Uint8Array)
157 _ <- liftEffect $ AT.setTyped newview (Just offset) aview
158 pure $ offset + AB.byteLength a
162-- | Build an `ArrayBuffer` with do-notation in any `MonadEffect`. *O(n)*
163execPutM :: forall m. (MonadEffect m) => PutM m Unit -> m ArrayBuffer
164execPutM = execBuilder <=< execWriterT
166-- | Build an `ArrayBuffer` with do-notation. *O(n)*
167execPut :: Put Unit -> Effect ArrayBuffer
170-- | Append an `ArrayBuffer` to the builder.
171putArrayBuffer :: forall m. (MonadEffect m) => ArrayBuffer -> PutM m Unit
172putArrayBuffer = tell <<< singleton
174-- | Serialize an 8-bit unsigned integer (byte) into a new `ArrayBuffer`.
175encodeUint8 :: forall m. (MonadEffect m) => UInt -> m ArrayBuffer
177 buf <- liftEffect $ AB.empty 1
178 _ <- liftEffect $ DV.setUint8 (DV.whole buf) 0 x
181-- | Append an 8-bit unsigned integer (byte) to the builder.
182putUint8 :: forall m. (MonadEffect m) => UInt -> PutM m Unit
183putUint8 = putArrayBuffer <=< encodeUint8
185-- | Serialize an 8-bit two’s-complement signed integer (char) into a new `ArrayBuffer`.
186encodeInt8 :: forall m. (MonadEffect m) => Int -> m ArrayBuffer
188 buf <- liftEffect $ AB.empty 1
189 _ <- liftEffect $ DV.setInt8 (DV.whole buf) 0 x
192-- | Append an 8-bit two’s-complement signed integer (char) to the builder.
193putInt8 :: forall m. (MonadEffect m) => Int -> PutM m Unit
194putInt8 = putArrayBuffer <=< encodeInt8
196-- | Serialize a 16-bit big-endian unsigned integer into a new `ArrayBuffer`.
197encodeUint16be :: forall m. (MonadEffect m) => UInt -> m ArrayBuffer
199 buf <- liftEffect $ AB.empty 2
200 _ <- liftEffect $ DV.setUint16be (DV.whole buf) 0 x
203-- | Append a 16-bit big-endian unsigned integer to the builder.
204putUint16be :: forall m. (MonadEffect m) => UInt -> PutM m Unit
205putUint16be = putArrayBuffer <=< encodeUint16be
207-- | Serialize a 16-bit little-endian unsigned integer into a new `ArrayBuffer`.
208encodeUint16le :: forall m. (MonadEffect m) => UInt -> m ArrayBuffer
210 buf <- liftEffect $ AB.empty 2
211 _ <- liftEffect $ DV.setUint16le (DV.whole buf) 0 x
214-- | Append a 16-bit little-endian unsigned integer to the builder.
215putUint16le :: forall m. (MonadEffect m) => UInt -> PutM m Unit
216putUint16le = putArrayBuffer <=< encodeUint16le
218-- | Serialize a 16-bit big-endian two’s-complement signed integer into a new `ArrayBuffer`.
219encodeInt16be :: forall m. (MonadEffect m) => Int -> m ArrayBuffer
221 buf <- liftEffect $ AB.empty 2
222 _ <- liftEffect $ DV.setInt16be (DV.whole buf) 0 x
225-- | Append a 16-bit big-endian two’s-complement signed integer to the builder.
226putInt16be :: forall m. (MonadEffect m) => Int -> PutM m Unit
227putInt16be = putArrayBuffer <=< encodeInt16be
229-- | Serialize a 16-bit little-endian two’s-complement signed integer into a new `ArrayBuffer`.
230encodeInt16le :: forall m. (MonadEffect m) => Int -> m ArrayBuffer
232 buf <- liftEffect $ AB.empty 2
233 _ <- liftEffect $ DV.setInt16le (DV.whole buf) 0 x
236-- | Append a 16-bit little-endian two’s-complement signed integer to the builder.
237putInt16le :: forall m. (MonadEffect m) => Int -> PutM m Unit
238putInt16le = putArrayBuffer <=< encodeInt16le
240-- | Serialize a 32-bit big-endian unsigned integer into a new `ArrayBuffer`.
241encodeUint32be :: forall m. (MonadEffect m) => UInt -> m ArrayBuffer
243 buf <- liftEffect $ AB.empty 4
244 _ <- liftEffect $ DV.setUint32be (DV.whole buf) 0 x
247-- | Append a 32-bit big-endian unsigned integer to the builder.
248putUint32be :: forall m. (MonadEffect m) => UInt -> PutM m Unit
249putUint32be = putArrayBuffer <=< encodeUint32be
251-- | Serialize a 32-bit little-endian unsigned integer into a new `ArrayBuffer`.
252encodeUint32le :: forall m. (MonadEffect m) => UInt -> m ArrayBuffer
254 buf <- liftEffect $ AB.empty 4
255 _ <- liftEffect $ DV.setUint32le (DV.whole buf) 0 x
258-- | Append a 32-bit little-endian unsigned integer to the builder.
259putUint32le :: forall m. (MonadEffect m) => UInt -> PutM m Unit
260putUint32le = putArrayBuffer <=< encodeUint32le
262-- | Serialize a 32-bit big-endian two’s-complement signed integer into a new `ArrayBuffer`.
263encodeInt32be :: forall m. (MonadEffect m) => Int -> m ArrayBuffer
265 buf <- liftEffect $ AB.empty 4
266 _ <- liftEffect $ DV.setInt32be (DV.whole buf) 0 x
269-- | Append a 32-bit big-endian two’s-complement signed integer to the builder.
270putInt32be :: forall m. (MonadEffect m) => Int -> PutM m Unit
271putInt32be = putArrayBuffer <=< encodeInt32be
273-- | Serialize a 32-bit little-endian two’s-complement signed integer into a new `ArrayBuffer`.
274encodeInt32le :: forall m. (MonadEffect m) => Int -> m ArrayBuffer
276 buf <- liftEffect $ AB.empty 4
277 _ <- liftEffect $ DV.setInt32le (DV.whole buf) 0 x
280-- | Append a 32-bit little-endian two’s-complement signed integer to the builder.
281putInt32le :: forall m. (MonadEffect m) => Int -> PutM m Unit
282putInt32le = putArrayBuffer <=< encodeInt32le
284-- | Serialize a 32-bit big-endian IEEE single-precision float into a new `ArrayBuffer`.
285encodeFloat32be :: forall m. (MonadEffect m) => Float32 -> m ArrayBuffer
286encodeFloat32be x = do
287 buf <- liftEffect $ AB.empty 4
288 _ <- liftEffect $ DV.setFloat32be (DV.whole buf) 0 x
291-- | Append a 32-bit big-endian IEEE single-precision float to the builder.
292putFloat32be :: forall m. (MonadEffect m) => Float32 -> PutM m Unit
293putFloat32be = putArrayBuffer <=< encodeFloat32be
295-- | Serialize a 32-bit little-endian IEEE single-precision float into a new `ArrayBuffer`.
296encodeFloat32le :: forall m. (MonadEffect m) => Float32 -> m ArrayBuffer
297encodeFloat32le x = do
298 buf <- liftEffect $ AB.empty 4
299 _ <- liftEffect $ DV.setFloat32le (DV.whole buf) 0 x
302-- | Append a 32-bit little-endian IEEE single-precision float to the builder.
303putFloat32le :: forall m. (MonadEffect m) => Float32 -> PutM m Unit
304putFloat32le = putArrayBuffer <=< encodeFloat32le
306-- | Serialize a 64-bit big-endian IEEE double-precision float into a new `ArrayBuffer`.
307encodeFloat64be :: forall m. (MonadEffect m) => Number -> m ArrayBuffer
308encodeFloat64be x = do
309 buf <- liftEffect $ AB.empty 8
310 _ <- liftEffect $ DV.setFloat64be (DV.whole buf) 0 x
313-- | Append a 64-bit big-endian IEEE double-precision float to the builder.
314putFloat64be :: forall m. (MonadEffect m) => Number -> PutM m Unit
315putFloat64be = putArrayBuffer <=< encodeFloat64be
317-- | Serialize a 64-bit little-endian IEEE double-precision float into a new `ArrayBuffer`.
318encodeFloat64le :: forall m. (MonadEffect m) => Number -> m ArrayBuffer
319encodeFloat64le x = do
320 buf <- liftEffect $ AB.empty 8
321 _ <- liftEffect $ DV.setFloat64le (DV.whole buf) 0 x
324-- | Append a 64-bit little-endian IEEE double-precision float to the builder.
325putFloat64le :: forall m. (MonadEffect m) => Number -> PutM m Unit
326putFloat64le = putArrayBuffer <=< encodeFloat64le
329-- ## Implementation Details
331-- We want our `Builder` to be a data structure with
332-- * *O(1)* monoid append
335-- Our `Builder` implementation is an unbalanced binary tree.
337-- For monoid `append`, what we actually get is *O(1)* when either the
338-- left or right tree is a singleton. If that's not true, then in the
339-- unlikely worst case `append` might be *O(n)*.
341-- `Builder` is optimized what we consider to be normal usage, that is,
342-- `snoc`ing singleton elements to the end of the `Builder`.
344-- If a Builder is built entirely by `snoc`ing, it will look like a
345-- left-only binary tree, a.k.a. a linked list.
357-- If two of these `snoc`-built trees are `append`ed, then the new tree
372-- This is all similar to
373-- [__bytestring-tree-builder__](https://hackage.haskell.org/package/bytestring-tree-builder)
375-- [`Tree`](https://hackage.haskell.org/package/bytestring-tree-builder-0.2.7.3/docs/src/ByteString.TreeBuilder.Tree.html#Tree)
376-- structure in __bytestring-tree-builder__ only carries values in its
377-- leaves, which is how it achieves *O(1)* appending, at the cost of
378-- a higher coefficient time factor on the fold.
380-- We hope that this implementation is fairly fast, but we
381-- haven't chosen this implementation because it's fast, we've chosen
382-- this implementation because it's simple.
383-- If someone wants to create a fast Purescript ArrayBuffer serialization
384-- library, then they can benchmark against this one to prove that the new
387-- One relatively cheap and simple performance improvement for this library would be to
388-- remove the Null constructor of `Builder` and instead use Javascript nulls.
390-- In the longer term, it might make sense to try to change the Builder so
391-- that it works like the
392-- https://hackage.haskell.org/package/bytestring/docs/Data-ByteString-Builder.html
394-- Here are some benchmarks of different Haskell ByteString builders
395-- https://github.com/haskell-perf/strict-bytestring-builders
397-- We've tried to design the API for this library with minimal assumptions,
398-- so that if we want to change the Builder implementation later then we can.
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list