Package

purescript-array-builder

Repository
paluh/purescript-array-builder
License
BSD-3-Clause
Uploaded by
paluh
Published on
2022-06-20T16:13:30Z

Monoidal array builder which should have much better performance than pure concatenation because it mutates the underling array.

Performance characteristics

Please be aware that JS engines are generaly optimized for push (snoc) operation - O(1) vs unshift (cons) operation - O(n). We have here characteristic which is opposite to the Data.List.

Limitations

Use it for relatively small arrays (length < 10000) otherwise you can get Nothing from build... or just a crash (call stack overflow) in the case of unsafeBuild.

Usage

Let me start with imports. This README is a part of the test suite so we need them.

module Test.README where

import Prelude

-- We are working with constant sized arrays
-- here so `unsafeBuild` is actually pretty safe ;-)
import Data.Array ((..))
import Data.Array.Builder (cons, consArray, snoc, snocArray, unsafeBuild, (:>), (+>), (<:), (<+), build)
import Data.Foldable (foldMap)
import Data.Maybe (Maybe(..))
import Data.Monoid as Monoid
import Effect (Effect)
import Test.Assert (assert)

<> is right associative so it works in practice as follows:

suite  Effect Unit
suite = do
  assert $ unsafeBuild (cons 8 <> cons 9 <> cons 10 <> mempty) == [8, 9, 10]

and

  assert $ unsafeBuild (snoc 8 <> snoc 9 <> snoc 10 <> mempty) == [10, 9, 8]

I'm not a huge fun of custom operators but in the case of List or Array constructors I feel that they are pretty useful. So we have right (:>, +> - cons, consArray) and left (<:, <+ - snoc,snocArray) associative operators provided by the lib which "should" behave as you would expect when mixed:

  assert $ unsafeBuild (-3 :> [-2, -1] +> 0 :> mempty <: 1 <+ [2, 3] <: 4) == -3..4

this API works nicely with tools like Monoid.guard, foldMap etc. because we have a "pretty" performant Monoid here:

  let
    b =
      snoc 5
      -- | It could be `foldMap snoc [3, 4]` as well
      -- | but `snocArray` is a single `push`.
      <> Monoid.guard true (snocArray [3, 4])
      <> snoc 2
      <> foldMap snoc (Just 1)
      <> Monoid.guard false (cons 0)

  assert (unsafeBuild b == (1..5))

The above example uses consistently more performant snoc* operations - mixing cons*, snoc* and <> can be tricky sometimes because we have to think about the order of the actual operations... So I prefer my fancy operators. Everyones loves their own lovely, clean and consistent operators and... not like the others ugly, unkown and unreadable operators ;-)

"And now for something completely different..." Let's blow up the stack:

  assert $ build (foldMap snoc (1..20000)) == Nothing

Nice!

Testing

$ npm test