@@ -2,7 +2,7 @@
3-- | You probably don’t want to import anything from this module.
3-- | You probably don’t want to import anything from this module.
5−-- | ## Implementation Details
5+-- | # Implementation Details
7-- | We want our `Builder` to be a data structure with
7-- | We want our `Builder` to be a data structure with
8-- | * *O(1)* monoid append
8-- | * *O(1)* monoid append
@@ -57,20 +57,19 @@
57-- | __bytestring-tree-builder__
57-- | __bytestring-tree-builder__
58-- | which “according to the benchmarks … beats all the alternatives.”
58-- | which “according to the benchmarks … beats all the alternatives.”
59-- | However, we
59-- | However, we
60−-- | haven’t chosen this implementation because it’s fast, we've chosen
60+-- | haven't chosen this implementation because it's fast, we've chosen
61−-- | this implementation because it’s simple.
61+-- | this implementation because it's simple.
62−-- | If someone wants to create a fast PureScript `ArrayBuffer` serialization
62+-- | If someone wants to create a fast PureScript ArrayBuffer serialization
63-- | library, then they can benchmark against this one to prove that the new
63-- | library, then they can benchmark against this one to prove that the new
64-- | one is fast.
64-- | one is fast.
66-- | One relatively cheap and simple performance improvement for this library would be to
66-- | One relatively cheap and simple performance improvement for this library would be to
67−-- | remove the `Null` constructor of `Builder` and instead use Javascript nulls?
67+-- | remove the Null constructor of `Builder` and instead use Javascript nulls.
68+-- | __UPDATE__ Actually no, we need the Null constructor for the Monoid instance.
69-- | In the longer term, it might make sense to try to change the `Builder` so
70-- | In the longer term, it might make sense to try to change the `Builder` so
70-- | that it works like the
71-- | that it works like the
71−-- | https://hackage.haskell.org/package/bytestring/docs/Data-ByteString-Builder.html .
72+-- | https://hackage.haskell.org/package/bytestring/docs/Data-ByteString-Builder.html
72−-- | That’s the approach taken by
73−-- | https://pursuit.purescript.org/packages/purescript-dynamic-buffers .
75-- | Here are some benchmarks of different Haskell ByteString builders
74-- | Here are some benchmarks of different Haskell ByteString builders
76-- | https://github.com/haskell-perf/strict-bytestring-builders
75-- | https://github.com/haskell-perf/strict-bytestring-builders
@@ -78,63 +77,56 @@
78-- | We've tried to design the API for this library with minimal assumptions,
77-- | We've tried to design the API for this library with minimal assumptions,
79-- | so that if we want to change the `Builder` implementation later then we can.
78-- | so that if we want to change the `Builder` implementation later then we can.
80+-- | Another way this could go:
81+-- | https://kodimensional.dev/posts/2019-03-25-comonadic-builders
83+-- | Our convention is that functions which add `DataView`s to
84+-- | the `Builder` are suffixed with underscore (`_`).
81module Data.ArrayBuffer.Builder.Internal
86module Data.ArrayBuffer.Builder.Internal
82− ( Builder(..)
87+( Builder(..)
84− , DataBuff(..)
89+, Bytes(..)
86− , execBuilder
91+, execBuilder
90− , singleton
95+, singleton
93− , encodeUint8
101+, encodeUint8
94− , encodeInt8
102+, encodeInt8
95− , encodeUint16be
103+, encodeUint16be
96− , encodeUint16le
104+, encodeUint16le
97− , encodeInt16be
105+, encodeInt16be
98− , encodeInt16le
106+, encodeInt16le
99− , encodeUint32be
107+, encodeUint32be
100− , encodeUint32le
108+, encodeUint32le
101− , encodeInt32be
109+, encodeInt32be
102− , encodeInt32le
110+, encodeInt32le
103− , encodeFloat32be
111+, encodeFloat32be
104− , encodeFloat32le
112+, encodeFloat32le
105− , encodeFloat64be
113+, encodeFloat64be
106− , encodeFloat64le
114+, encodeFloat64le
109import Prelude
118import Prelude
111−import Control.Monad.Rec.Class (class MonadRec, Step(..), tailRecM)
112import Data.ArrayBuffer.ArrayBuffer as AB
120import Data.ArrayBuffer.ArrayBuffer as AB
113import Data.ArrayBuffer.DataView as DV
121import Data.ArrayBuffer.DataView as DV
114import Data.ArrayBuffer.Typed as AT
122import Data.ArrayBuffer.Typed as AT
115import Data.ArrayBuffer.Types (ArrayBuffer, ByteLength, DataView, Uint8Array)
123import Data.ArrayBuffer.Types (ArrayBuffer, ByteLength, DataView, Uint8Array)
116import Data.Float32 (Float32)
124import Data.Float32 (Float32)
117−import Data.Identity (Identity)
118−import Data.List (List(..), uncons, (:))
119import Data.Maybe (Maybe(..))
125import Data.Maybe (Maybe(..))
120−import Data.Newtype (unwrap)
121import Data.UInt (UInt)
126import Data.UInt (UInt)
122import Effect (Effect)
127import Effect (Effect)
123import Effect.Class (class MonadEffect, liftEffect)
128import Effect.Class (class MonadEffect, liftEffect)
125−-- | For distinguishing between `ArrayBuffer` and `DataView`.
130−-- Concerning DataViews.
131−-- https://v8.dev/blog/dataview
133−-- | View the contents of `DataBuff` as a `DataView`.
134−toView :: DataBuff -> DataView
135−toView (Buff ab) = DV.whole ab
136−toView (View dv) = dv
138-- | Monoidal builder for `ArrayBuffer`s.
130-- | Monoidal builder for `ArrayBuffer`s.
140-- | We can add two types of things to the `Builder`:
132-- | We can add two types of things to the `Builder`:
@@ -146,7 +138,7 @@ toView (View dv) = dv
146-- | from some other `ArrayBuffer`, so that we don’t
138-- | from some other `ArrayBuffer`, so that we don’t
147-- | need an extra intermediate copy of the slice.
139-- | need an extra intermediate copy of the slice.
148data Builder
140data Builder
149− = Node Builder DataBuff Builder
141+ = Node Builder Bytes Builder
152-- | ### Left-associative `<>>` append operator
144-- | ### Left-associative `<>>` append operator
@@ -190,62 +182,71 @@ instance semigroupBuilder :: Semigroup Builder where
190 append Null r = r
182 append Null r = r
191 append l (Node Null rx rr) = Node l rx rr -- this is the snoc case
183 append l (Node Null rx rr) = Node l rx rr -- this is the snoc case
192 append (Node ll lx Null) r = Node ll lx r -- found the rightmost Null
184 append (Node ll lx Null) r = Node ll lx r -- found the rightmost Null
193− -- of the left tree
185+ -- of the left tree
194 append (Node ll lx lr) r = Node ll lx $ append lr r -- search down the right
186 append (Node ll lx lr) r = Node ll lx $ append lr r -- search down the right
196−-- side of the left tree
187+ -- side of the left tree
197−-- to find the rightmost
188+ -- to find the rightmost
200infixl 5 append as <>>
191infixl 5 append as <>>
202instance monoidBuilder :: Monoid Builder where
193instance monoidBuilder :: Monoid Builder where
203 mempty = Null
194 mempty = Null
205−-- | Stack-safe `foldl` over a `Builder`. *O(n)*
196+-- | For distinguishing between `ArrayBuffer` and `DataView`.
198+ = ByteBuff ArrayBuffer
201+-- | `Builder` is not an instance of `Foldable` because `Builder` is monomorphic.
206−foldl :: forall a. (a -> DataBuff -> a) -> a -> Builder -> a
202+foldl :: forall b. (b -> Bytes -> b) -> b -> Builder -> b
207−foldl f a0 b = (unwrap :: Identity a -> a) $ foldM (\a' b' -> pure (f a' b')) a0 b
203+foldl _ a Null = a
204+foldl f a (Node l x r) = foldl f (f (foldl f a l) x) r
209−-- | Stack-safe `foldM` over a `Builder`. *O(n)*.
206+-- | Monomorphic foldM copied from
210−foldM :: forall m a. MonadRec m => (a -> DataBuff -> m a) -> a -> Builder -> m a
207+-- | [`Data.Foldable.foldM`](https://pursuit.purescript.org/packages/purescript-foldable-traversable/4.1.1/docs/Data.Foldable#v:foldM)
211−-- The Art of Computer Programming 2.3.1
208+foldM :: forall b m. (Monad m) => (b -> Bytes -> m b) -> b -> Builder -> m b
212−-- Inorder traversal using a stack
209+foldM f a0 = foldl (\ma b -> ma >>= flip f b) (pure a0)
213−-- https://yuyuan.org/MorrisAlgorithm/
210+
214−foldM f a0 b = tailRecM outer { p: b, stack: Nil, accum: a0 }
211+-- | Construct a `Builder` with a single `ArrayBuffer`. *O(1)*
215− where
212+singleton :: ArrayBuffer -> Builder
216− outer { p, stack, accum } = do
213+singleton buf = Node Null (ByteBuff buf) Null
217− stack' <- tailRecM inner { p_: p, stack_: stack }
214+
218− case uncons stack' of
219− Nothing -> pure $ Done accum
220− Just { head: Node _ x r, tail } -> do
222− pure $ Loop { p: r, stack: tail, accum: accum' }
223− Just { head: Null } -> pure $ Done accum
224− inner { p_: Null, stack_ } = pure $ Done stack_
225− inner { p_: n@(Node l _ _), stack_ } = pure $ Loop { p_: l, stack_: n : stack_ }
227−-- | Construct a `Builder` with a single `DataBuff`. *O(1)*
215+-- | Construct a `Builder` with a single `DataView`. *O(1)*
228−singleton :: DataBuff -> Builder
216+singleton_ :: DataView -> Builder
229−singleton buf = Node Null buf Null
217+singleton_ buf = Node Null (ByteView buf) Null
231-- | Calculate the total byte length of the `Builder`, without actually
219-- | Calculate the total byte length of the `Builder`, without actually
232-- | building it yet. *O(n)*
220-- | building it yet. *O(n)*
233length :: Builder -> ByteLength
221length :: Builder -> ByteLength
234length bldr = foldl (\b a -> b + len a) 0 bldr
222length bldr = foldl (\b a -> b + len a) 0 bldr
236− len (Buff ab) = AB.byteLength ab
224+ len (ByteBuff ab) = AB.byteLength ab
237− len (View dv) = DV.byteLength dv
225+ len (ByteView dv) = DV.byteLength dv
239−-- | Prepend a `DataBuff` to the beginning of the `Builder`. *O(1)*
227+ -- | View the contents of `Bytes` as a `DataView`.
240−cons :: DataBuff -> Builder -> Builder
228+toView :: Bytes -> DataView
241−cons x bs = Node Null x bs
229+toView (ByteBuff ab) = DV.whole ab
230+toView (ByteView dv) = dv
232+-- | Prepend an `ArrayBuffer` to the beginning of the `Builder`. *O(1)*
233+cons :: ArrayBuffer -> Builder -> Builder
234+cons x bs = Node Null (ByteBuff x) bs
236+-- | Prepend a `DataView` to the beginning of the `Builder`. *O(1)*
237+cons_ :: DataView -> Builder -> Builder
238+cons_ x bs = Node Null (ByteView x) bs
243−-- | Append a `DataBuff` to the end of the `Builder`. *O(1)*
240+-- | Append an `ArrayBuffer` to the end of the `Builder`. *O(1)*
244−snoc :: Builder -> DataBuff -> Builder
241+snoc :: Builder -> ArrayBuffer -> Builder
245−snoc bs x = Node bs x Null
242+snoc bs x = Node bs (ByteBuff x) Null
244+-- | Append a `DataView` to the end of the `Builder`. *O(1)*
245+snoc_ :: Builder -> DataView -> Builder
246+snoc_ bs x = Node bs (ByteView x) Null
247-- | Build a single `ArrayBuffer` from a `Builder`. *O(n)*
248-- | Build a single `ArrayBuffer` from a `Builder`. *O(n)*
248−execBuilder :: forall m. MonadEffect m => MonadRec m => Builder -> m ArrayBuffer
249+execBuilder :: forall m. (MonadEffect m) => Builder -> m ArrayBuffer
249execBuilder bldr = do
250execBuilder bldr = do
250 let buflen = length bldr
251 let buflen = length bldr
251 -- Allocate the final ArrayBuffer
252 -- Allocate the final ArrayBuffer
@@ -259,14 +260,12 @@ execBuilder bldr = do
259 aview <- liftEffect $ toUint8Array a
260 aview <- liftEffect $ toUint8Array a
260 _ <- liftEffect $ AT.setTyped newview (Just offset) aview
261 _ <- liftEffect $ AT.setTyped newview (Just offset) aview
261 pure $ offset + AT.byteLength aview
262 pure $ offset + AT.byteLength aview
267− toUint8Array :: DataBuff -> Effect Uint8Array
266+ toUint8Array :: Bytes -> Effect Uint8Array
268− toUint8Array (Buff ab) = AT.whole ab
267+ toUint8Array (ByteBuff ab) = AT.whole ab
269− toUint8Array (View dv) =
268+ toUint8Array (ByteView dv) =
270 AT.part (DV.buffer dv) (DV.byteOffset dv) (DV.byteLength dv)
269 AT.part (DV.buffer dv) (DV.byteOffset dv) (DV.byteLength dv)
272-- | Serialize an 8-bit unsigned integer (byte) into a new `ArrayBuffer`.
271-- | Serialize an 8-bit unsigned integer (byte) into a new `ArrayBuffer`.