packages feed

text-builder-linear 0.1.3 → 0.1.4

raw patch · 18 files changed

+298/−726 lines, 18 filesdep ~ghc-bignumdep ~tasty-benchPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: ghc-bignum, tasty-bench

API changes (from Hackage documentation)

+ Data.Text.Builder.Linear: instance GHC.Classes.Eq Data.Text.Builder.Linear.Builder
+ Data.Text.Builder.Linear: instance GHC.Classes.Ord Data.Text.Builder.Linear.Builder

Files

README.md view
@@ -44,7 +44,7 @@ Exponential growth provides for amortized linear time. Such structure can be implemented without linear types, but that would greatly affect user experience by polluting everything with `ST` monad.-Users are encouraged to use `Buffer` API, and built-in benchmarks refer to it.+Users are encouraged to use `Buffer` API, and **built-in benchmarks refer to it.**  The second interface is more traditional `newtype Builder = Builder (Buffer ⊸ Buffer)` with `Monoid` instance. This type provides easy migration from other builders,@@ -52,136 +52,52 @@ significantly faster than `Data.Text.Lazy.Builder`, as witnessed by benchmarks for `blaze-builder` below. -## Case study--Let's benchmark builders, which concatenate all `Char` from `minBound` to `maxBound`, producing a large `Text`:--```haskell-#!/usr/bin/env cabal-{- cabal:-build-depends: base, tasty-bench, text, text-builder, text-builder-linear-ghc-options: -O2--}--import qualified Data.Text as T-import qualified Data.Text.Lazy as TL-import qualified Data.Text.Lazy.Builder as TLB-import qualified Text.Builder as TB-import qualified Data.Text.Builder.Linear as TBL-import System.Environment (getArgs)-import Test.Tasty.Bench--mkBench :: Monoid a => String -> (Char -> a) -> (a -> Int) -> Benchmark-mkBench s f g = bench s $ nf (g . foldMap f . enumFromTo minBound) maxBound-{-# INLINE mkBench #-}--main :: IO ()-main = defaultMain-  [ mkBench "text, lazy" TLB.singleton (fromIntegral . TL.length . TLB.toLazyText)-  , mkBench "text, strict" TLB.singleton (T.length . TL.toStrict . TLB.toLazyText)-  , mkBench "text-builder" TB.char (T.length . TB.run)-  , mkBench "text-builder-linear" TBL.fromChar (T.length . TBL.runBuilder)-  ]-```--Running this program with `cabal run Main.hs -- +RTS -T` yields following results:--```-text, lazy:-  4.25 ms ± 107 μs,  11 MB allocated, 912 B  copied-text, strict:-  7.18 ms ± 235 μs,  24 MB allocated,  10 MB copied-text-builder:-  80.1 ms ± 3.0 ms, 218 MB allocated, 107 MB copied-text-builder-linear:-  5.37 ms ± 146 μs,  44 MB allocated,  78 KB copied-```--The first result seems the best both in time and memory and corresponds to the-usual `Text` builder, where we do not materialize the entire result at all.-It builds chunks of lazy `Text` lazily and consumes them at once by-`TL.length`. Thus there are 11 MB of allocations in nursery, none of which-survive generation 0 garbage collector, so nothing is copied.--The second result is again the usual `Text` builder, but emulates a strict-consumer: we materialize a strict `Text` before computing length. Allocation-are doubled, and half of them (corresponding to the strict `Text`) survive to-the heap. Time is also almost twice longer, but still quite good.--The third result is for `text-builder` and demonstrates how bad things could-go with strict builders, aiming to precompute the precise length of the-buffer: allocating a thunk per char is tremendously slow and expensive.--The last result corresponds to the current package. We generate a strict-`Text` by growing and reallocating the buffer, thus allocations are quite-high. Nevertheless, it is already faster than the usual `Text` builder with-strict consumer and does not strain the garbage collector.--Things get very different if we remove `{-# INLINE mkBench #-}`:--```-text, lazy:-  36.9 ms ± 599 μs, 275 MB allocated,  30 KB copied-text, strict:-  44.7 ms ± 1.3 ms, 287 MB allocated,  25 MB copied-text-builder:-  77.6 ms ± 2.2 ms, 218 MB allocated, 107 MB copied-text-builder-linear:-  5.35 ms ± 212 μs,  44 MB allocated,  79 KB copied-```--Builders from `text` package degrade rapidly, 6-8x slower and 10-20x more-allocations. That's because their constant factors rely crucially on-everything getting inlined, which makes their performance fragile and-unreliable in large-scale applications. On the bright side of things, our-builder remains as fast as before and now is a clear champion.- ## Benchmarks for `Text` -Measured with GHC 9.6 on aarch64:+Measured with GHC 9.12 on aarch64:  |Group / size|`text`|`text-builder`|  |This package|  | |------------|-----:|-------------:|-:|-----------:|-:| | **Text** ||||||-|1|47.4 ns|24.2 ns|0.51x|35.2 ns|0.74x|-|10|509 ns|195 ns|0.38x|197 ns|0.39x|-|100|4.94 μs|1.74 μs|0.35x|1.66 μs|0.34x|-|1000|52.6 μs|17.0 μs|0.32x|15.0 μs|0.28x|-|10000|646 μs|206 μs|0.32x|155 μs|0.24x|-|100000|12.2 ms|3.34 ms|0.27x|2.60 ms|0.21x|-|1000000|159 ms|55.3 ms|0.35x|16.1 ms|0.10x|+|1|63.3 ns|30.8 ns|0.49x|60.5 ns|0.95x|+|10|764 ns|267 ns|0.35x|319 ns|0.42x|+|100|7.53 μs|2.48 μs|0.33x|2.61 μs|0.35x|+|1000|80.5 μs|26.7 μs|0.33x|23.1 μs|0.29x|+|10000|949 μs|319 μs|0.34x|242 μs|0.26x|+|100000|18.5 ms|8.22 ms|0.44x|2.36 ms|0.13x|+|1000000|216 ms|107 ms|0.49x|22.9 ms|0.11x| | **Char** ||||||-|1|46.9 ns|21.1 ns|0.45x|22.3 ns|0.48x|-|10|229 ns|152 ns|0.66x|79.9 ns|0.35x|-|100|2.00 μs|1.23 μs|0.61x|618 ns|0.31x|-|1000|21.9 μs|10.3 μs|0.47x|6.28 μs|0.29x|-|10000|285 μs|153 μs|0.54x|68.5 μs|0.24x|-|100000|7.70 ms|4.08 ms|0.53x|992 μs|0.13x|-|1000000|110 ms|106 ms|0.96x|9.19 ms|0.08x|+|1|49.0 ns|34.8 ns|0.71x|38.1 ns|0.78x|+|10|365 ns|293 ns|0.80x|117 ns|0.32x|+|100|3.20 μs|2.38 μs|0.74x|804 ns|0.25x|+|1000|35.4 μs|18.4 μs|0.52x|7.68 μs|0.22x|+|10000|460 μs|265 μs|0.58x|86.5 μs|0.19x|+|100000|12.7 ms|6.96 ms|0.55x|930 μs|0.07x|+|1000000|175 ms|178 ms|1.02x|10.5 ms|0.06x| | **Decimal** ||||||-|1|97.7 ns|872 ns|8.92x|80.2 ns|0.82x|-|10|864 ns|8.72 μs|10.09x|684 ns|0.79x|-|100|9.07 μs|93.5 μs|10.32x|7.25 μs|0.80x|-|1000|92.4 μs|1.06 ms|11.44x|67.5 μs|0.73x|-|10000|1.13 ms|13.4 ms|11.88x|667 μs|0.59x|-|100000|18.7 ms|141 ms|7.57x|7.57 ms|0.41x|-|1000000|229 ms|1.487 s|6.48x|67.8 ms|0.30x|+|1|148 ns|490 ns|3.30x|126 ns|0.85x|+|10|1.34 μs|4.80 μs|3.57x|1.07 μs|0.80x|+|100|14.3 μs|53.6 μs|3.76x|10.8 μs|0.76x|+|1000|148 μs|738 μs|5.00x|106 μs|0.72x|+|10000|1.66 ms|19.8 ms|11.96x|1.05 ms|0.63x|+|100000|28.3 ms|251 ms|8.88x|10.7 ms|0.38x|+|1000000|334 ms|2.803 s|8.40x|108 ms|0.32x| | **Hexadecimal** ||||||-|1|403 ns|749 ns|1.86x|43.9 ns|0.11x|-|10|3.94 μs|7.66 μs|1.94x|308 ns|0.08x|-|100|42.8 μs|89.0 μs|2.08x|2.88 μs|0.07x|-|1000|486 μs|986 μs|2.03x|27.7 μs|0.06x|-|10000|7.10 ms|12.6 ms|1.77x|283 μs|0.04x|-|100000|80.1 ms|133 ms|1.65x|3.53 ms|0.04x|-|1000000|867 ms|1.340 s|1.55x|28.9 ms|0.03x|+|1|711 ns|81.2 ns|0.11x|74.2 ns|0.10x|+|10|7.06 μs|795 ns|0.11x|510 ns|0.07x|+|100|76.3 μs|8.04 μs|0.11x|4.62 μs|0.06x|+|1000|862 μs|141 μs|0.16x|44.6 μs|0.05x|+|10000|12.4 ms|1.73 ms|0.14x|451 μs|0.04x|+|100000|138 ms|20.4 ms|0.15x|4.52 ms|0.03x|+|1000000|1.502 s|228 ms|0.15x|45.9 ms|0.03x| | **Double** ||||||-|1|7.56 μs|18.3 μs|2.42x|414 ns|0.05x|-|10|76.5 μs|188 μs|2.46x|4.23 μs|0.06x|-|100|754 μs|2.35 ms|3.11x|44.4 μs|0.06x|-|1000|7.94 ms|25.8 ms|3.25x|436 μs|0.05x|-|10000|79.1 ms|285 ms|3.60x|4.90 ms|0.06x|-|100000|796 ms|2.938 s|3.69x|45.1 ms|0.06x|-|1000000|8.003 s|32.411 s|4.05x|436 ms|0.05x|+|1|13.4 μs|35.3 μs|2.63x|638 ns|0.05x|+|10|137 μs|393 μs|2.88x|6.52 μs|0.05x|+|100|1.35 ms|5.62 ms|4.15x|67.9 μs|0.05x|+|1000|14.2 ms|71.9 ms|5.05x|671 μs|0.05x|+|10000|143 ms|750 ms|5.25x|7.18 ms|0.05x|+|100000|1.435 s|7.941 s|5.53x|70.4 ms|0.05x|+|1000000|14.366 s|101.342 s|7.05x|689 ms|0.05x|  If you are not convinced by synthetic data, here are benchmarks for@@ -207,53 +123,55 @@ ## Benchmarks for `ByteString`  Somewhat surprisingly, `text-builder-linear` now offers rendering to strict `ByteString`-as well. It is consistently faster than `bytestring` when a string gets over 32k+as well. It gets consistently faster than `bytestring`+in all benchmarks except `Double` ones+once a string gets over 32k (which is `defaultChunkSize` for `bytestring` builder). For mid-sized strings `bytestring` is slightly faster in certain disciplines, mostly by virtue of using `cbits` via FFI, while this package remains 100% native Haskell. -Benchmarks below were measured with GHC 9.6 on aarch64 and include comparison+Benchmarks below were measured with GHC 9.12 on aarch64 and include comparison to [`bytestring-strict-builder`](https://hackage.haskell.org/package/bytestring-strict-builder):  |Group / size|`bytestring`|`…-strict-builder`|  |This package|  | |------------|-----------:|-----------------:|-:|-----------:|-:| | **Text** ||||||-|1|106 ns|33.5 ns|0.32x|35.2 ns|0.33x|-|10|322 ns|217 ns|0.68x|197 ns|0.61x|-|100|2.49 μs|1.89 μs|0.76x|1.66 μs|0.67x|-|1000|21.8 μs|18.5 μs|0.85x|15.0 μs|0.69x|-|10000|231 μs|212 μs|0.92x|155 μs|0.67x|-|100000|3.97 ms|3.54 ms|0.89x|2.60 ms|0.66x|-|1000000|81.2 ms|51.5 ms|0.63x|16.1 ms|0.20x|+|1|156 ns|55.9 ns|0.36x|60.5 ns|0.39x|+|10|552 ns|374 ns|0.68x|319 ns|0.58x|+|100|4.71 μs|3.25 μs|0.69x|2.61 μs|0.55x|+|1000|41.7 μs|31.9 μs|0.76x|23.1 μs|0.56x|+|10000|438 μs|366 μs|0.84x|242 μs|0.55x|+|100000|7.58 ms|6.52 ms|0.86x|2.36 ms|0.31x|+|1000000|112 ms|88.1 ms|0.78x|22.9 ms|0.20x| | **Char** ||||||-|1|99.0 ns|19.4 ns|0.20x|22.3 ns|0.23x|-|10|270 ns|82.9 ns|0.31x|79.9 ns|0.30x|-|100|1.77 μs|723 ns|0.41x|618 ns|0.35x|-|1000|20.4 μs|8.37 μs|0.41x|6.28 μs|0.31x|-|10000|322 μs|129 μs|0.40x|68.5 μs|0.21x|-|100000|10.4 ms|2.50 ms|0.24x|992 μs|0.10x|-|1000000|143 ms|67.4 ms|0.47x|9.19 ms|0.06x|+|1|138 ns|30.9 ns|0.22x|38.1 ns|0.28x|+|10|408 ns|136 ns|0.33x|117 ns|0.29x|+|100|2.96 μs|1.25 μs|0.42x|804 ns|0.27x|+|1000|30.4 μs|14.2 μs|0.47x|7.68 μs|0.25x|+|10000|394 μs|218 μs|0.55x|86.5 μs|0.22x|+|100000|11.8 ms|4.00 ms|0.34x|930 μs|0.08x|+|1000000|161 ms|112 ms|0.69x|10.5 ms|0.07x| | **Decimal** ||||||-|1|152 ns|174 ns|1.14x|80.2 ns|0.53x|-|10|685 ns|1.55 μs|2.26x|684 ns|1.00x|-|100|5.88 μs|17.2 μs|2.93x|7.25 μs|1.23x|-|1000|60.3 μs|196 μs|3.25x|67.5 μs|1.12x|-|10000|648 μs|4.25 ms|6.57x|667 μs|1.03x|-|100000|11.2 ms|62.8 ms|5.62x|7.57 ms|0.68x|-|1000000|150 ms|655 ms|4.37x|67.8 ms|0.45x|+|1|209 ns|295 ns|1.41x|126 ns|0.60x|+|10|1.10 μs|2.67 μs|2.43x|1.07 μs|0.98x|+|100|9.76 μs|29.8 μs|3.05x|10.8 μs|1.11x|+|1000|100 μs|340 μs|3.40x|106 μs|1.06x|+|10000|1.02 ms|7.32 ms|7.15x|1.05 ms|1.02x|+|100000|14.6 ms|103 ms|7.04x|10.7 ms|0.73x|+|1000000|179 ms|1.233 s|6.87x|108 ms|0.60x| | **Hexadecimal** ||||||-|1|94.7 ns|||43.9 ns|0.46x|-|10|255 ns|||308 ns|1.21x|-|100|1.72 μs|||2.88 μs|1.67x|-|1000|18.9 μs|||27.7 μs|1.46x|-|10000|250 μs|||283 μs|1.13x|-|100000|6.94 ms|||3.53 ms|0.51x|-|1000000|93.2 ms|||28.9 ms|0.31x|+|1|131 ns|||74.2 ns|0.57x|+|10|360 ns|||510 ns|1.42x|+|100|2.76 μs|||4.62 μs|1.68x|+|1000|28.6 μs|||44.6 μs|1.56x|+|10000|330 μs|||451 μs|1.37x|+|100000|7.30 ms|||4.52 ms|0.62x|+|1000000|103 ms|||45.9 ms|0.45x| | **Double** ||||||-|1|457 ns|||414 ns|0.91x|-|10|3.94 μs|||4.23 μs|1.07x|-|100|40.3 μs|||44.4 μs|1.10x|-|1000|398 μs|||436 μs|1.10x|-|10000|5.65 ms|||4.90 ms|0.87x|-|100000|63.3 ms|||45.1 ms|0.71x|-|1000000|673 ms|||436 ms|0.65x|+|1|456 ns|||638 ns|1.40x|+|10|3.58 μs|||6.52 μs|1.82x|+|100|36.2 μs|||67.9 μs|1.87x|+|1000|367 μs|||671 μs|1.83x|+|10000|5.17 ms|||7.18 ms|1.39x|+|100000|59.0 ms|||70.4 ms|1.19x|+|1000000|605 ms|||689 ms|1.14x|
bench/BenchChar.hs view
@@ -10,16 +10,12 @@ import Data.Char import qualified Data.Text as T import Data.Text.Builder.Linear.Buffer-import qualified Data.Text.Lazy as TL import Data.Text.Lazy (toStrict)-import qualified Data.Text.Lazy.Builder as TB import Data.Text.Lazy.Builder (toLazyText, singleton)-import qualified Data.Text.Internal.Fusion.Common as Fusion-import qualified Data.Text.Internal.Fusion as Fusion import Test.Tasty.Bench  #ifdef MIN_VERSION_text_builder-import qualified Text.Builder+import qualified TextBuilder #endif  #ifdef MIN_VERSION_bytestring_strict_builder@@ -44,10 +40,10 @@  #ifdef MIN_VERSION_text_builder benchStrictBuilder ∷ Int → T.Text-benchStrictBuilder = Text.Builder.run . go mempty+benchStrictBuilder = TextBuilder.toText . go mempty   where     go !acc 0 = acc-    go !acc n = let ch = chr n in go (Text.Builder.char ch <> (acc <> Text.Builder.char ch)) (n - 1)+    go !acc n = let ch = chr n in go (TextBuilder.char ch <> (acc <> TextBuilder.char ch)) (n - 1) #endif  #ifdef MIN_VERSION_bytestring_strict_builder@@ -65,15 +61,15 @@     go !acc 0 = acc     go !acc n = let ch = chr n in go (ch .<| (acc |>. ch)) (n - 1) -benchSingleChar ∷ Benchmark-benchSingleChar = bgroup "Single" $ map mkGroupChar [1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6]+benchSingleChar ∷ [Benchmark]+benchSingleChar = map mkGroupChar [1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6]  mkGroupChar :: Int → Benchmark mkGroupChar n = bgroup (show n)   [ bench "Data.Text.Lazy.Builder" $ nf benchLazyBuilder n   , bench "Data.ByteString.Builder" $ nf benchLazyBuilderBS n #ifdef MIN_VERSION_text_builder-  , bench "Text.Builder" $ nf benchStrictBuilder n+  , bench "TextBuilder" $ nf benchStrictBuilder n #endif #ifdef MIN_VERSION_bytestring_strict_builder   , bench "ByteString.StrictBuilder" $ nf benchStrictBuilderBS n@@ -82,160 +78,8 @@   ]  ----------------------------------------------------------------------------------- Multiple chars-----------------------------------------------------------------------------------charCount :: Word-charCount = 3--benchCharsLazyBuilder ∷ Int → T.Text-benchCharsLazyBuilder = TL.toStrict . TB.toLazyText . go mempty-  where-    go !acc 0 = acc-    go !acc n = let ch = chr n in go (replicateChar ch <> (acc <> replicateChar ch)) (n - 1)--    replicateChar ch = TB.fromText (Fusion.unstream (Fusion.replicateCharI charCount ch))--{- [FIXME] bad performance-benchCharsLazyBuilderBS ∷ Int → B.ByteString-benchCharsLazyBuilderBS = B.toStrict . B.toLazyByteString . go mempty-  where-    go !acc 0 = acc-    go !acc n =-      let ch = chr n-      in go (replicateChar ch <> (acc <> replicateChar ch)) (n - 1)--    replicateChar ch = stimes charCount (B.charUtf8 ch)--}--#ifdef MIN_VERSION_text_builder-benchCharsStrictBuilder ∷ Int → T.Text-benchCharsStrictBuilder = Text.Builder.run . go mempty-  where-    go !acc 0 = acc-    go !acc n = let ch = chr n in go (replicateChar ch <> (acc <> replicateChar ch)) (n - 1)--    -- [TODO] Is there a better way?-    replicateChar ch = Text.Builder.padFromRight (fromIntegral charCount) ch mempty-#endif--{- [TODO]-#ifdef MIN_VERSION_bytestring_strict_builder-benchCharsStrictBuilderBS ∷ Int → B.ByteString-benchCharsStrictBuilderBS = ByteString.StrictBuilder.builderBytes . go mempty-  where-    go !acc 0 = acc-    go !acc n = let ch = chr n in go _ (n - 1)-#endif--}--benchCharsLinearBuilder ∷ Int → T.Text-benchCharsLinearBuilder m = runBuffer (\b → go b m)-  where-    go ∷ Buffer ⊸ Int → Buffer-    go !acc 0 = acc-    go !acc n = let ch = chr n in go (prependChars charCount ch (appendChars charCount ch acc)) (n - 1)--benchMultipleChars ∷ Benchmark-benchMultipleChars = bgroup "Multiple" $ map mkGroupChars [1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6]--mkGroupChars :: Int → Benchmark-mkGroupChars n = bgroup (show n)-  [ bench "Data.Text.Lazy.Builder" $ nf benchCharsLazyBuilder n-  -- , bench "Data.ByteString.Builder" $ nf benchCharsLazyBuilderBS n-#ifdef MIN_VERSION_text_builder-  , bench "Text.Builder" $ nf benchCharsStrictBuilder n-#endif--- #ifdef MIN_VERSION_bytestring_strict_builder---   , bench "ByteString.StrictBuilder" $ nf benchCharsStrictBuilderBS n--- #endif-  , bench "Data.Text.Builder.Linear" $ nf benchCharsLinearBuilder n-  ]------------------------------------------------------------------------------------- Padding-----------------------------------------------------------------------------------benchPaddingLazyBuilder ∷ Int → T.Text-benchPaddingLazyBuilder = toStrict . toLazyText . go mempty 0-  where-    go !acc !_ 0 = acc-    go !acc l  n =-      let ch = chr n-          !l' = l + 2 * fromIntegral charCount-      in go (withText (T.justifyLeft l' ch)-                      (withText (T.justifyRight (l + fromIntegral charCount) ch) acc))-            l'-            (n - 1)--    withText f = TB.fromText . f . TL.toStrict . TB.toLazyText--{- [TODO]-benchPaddingLazyBuilderBS ∷ Int → B.ByteString-benchPaddingLazyBuilderBS = B.toStrict . B.toLazyByteString . go mempty-  where-    go !acc 0 = acc-    go !acc n = let ch = chr n in go _ (n - 1)--}--#ifdef MIN_VERSION_text_builder-benchPaddingStrictBuilder ∷ Int → T.Text-benchPaddingStrictBuilder = Text.Builder.run . go mempty 0-  where-    go !acc !_ 0 = acc-    go !acc l  n =-      let ch = chr n-          !l' = l + 2 * fromIntegral charCount-      in go (Text.Builder.padFromRight l' ch (Text.Builder.padFromLeft (l + fromIntegral charCount) ch acc))-            l'-            (n - 1)-#endif--{- [TODO]-#ifdef MIN_VERSION_bytestring_strict_builder-benchPaddingStrictBuilderBS ∷ Int → B.ByteString-benchPaddingStrictBuilderBS = ByteString.StrictBuilder.builderBytes . go mempty-  where-    go !acc 0 = acc-    go !acc n = let ch = chr n in go _ (n - 1)-#endif--}--benchPaddingLinearBuilder ∷ Int → T.Text-benchPaddingLinearBuilder m = runBuffer (\b → go b 0 m)-  where-    go ∷ Buffer ⊸ Word → Int → Buffer-    go !acc !_ 0 = acc-    go !acc l  n =-      let ch = chr n-          !l' = l + 2 * charCount-      in go (justifyLeft l' ch (justifyRight (l + charCount) ch acc))-            l'-            (n - 1)--benchPadding ∷ Benchmark-benchPadding = bgroup "Padding" $ map mkGroupPadding [1e0, 1e1, 1e2, 1e3, 1e4{-, 1e5, 1e6-}] -- NOTE: too long with 1e5--mkGroupPadding :: Int → Benchmark-mkGroupPadding n = bgroup (show n)-  [ bench "Data.Text.Lazy.Builder" $ nf benchPaddingLazyBuilder n-  -- , bench "Data.ByteString.Builder" $ nf benchPaddingLazyBuilderBS n-#ifdef MIN_VERSION_text_builder-  , bench "Text.Builder" $ nf benchPaddingStrictBuilder n-#endif--- #ifdef MIN_VERSION_bytestring_strict_builder---   , bench "ByteString.StrictBuilder" $ nf benchPaddingStrictBuilderBS n--- #endif-  , bench "Data.Text.Builder.Linear" $ nf benchPaddingLinearBuilder n-  ]---------------------------------------------------------------------------------- -- All benchmarks --------------------------------------------------------------------------------  benchChar ∷ Benchmark-benchChar = bgroup "Char"-  [ benchSingleChar-  , benchMultipleChars-  , benchPadding ]-+benchChar = bgroup "Char" benchSingleChar
bench/BenchDecimal.hs view
@@ -2,144 +2,76 @@ -- Copyright:   (c) 2022 Andrew Lelechenko -- Licence:     BSD3 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>+ module BenchDecimal (benchDecimal) where -import Data.ByteString qualified as B-import Data.ByteString.Builder qualified as B-import Data.Text qualified as T-import Data.Text.Builder.Linear.Buffer (Buffer, runBuffer, ($$<|), ($<|), (|>$), (|>$$))+import qualified Data.ByteString as B+import qualified Data.ByteString.Builder as B+import qualified Data.Text as T+import Data.Text.Builder.Linear.Buffer import Data.Text.Lazy (toStrict) import Data.Text.Lazy.Builder (toLazyText) import Data.Text.Lazy.Builder.Int (decimal)-import Test.Tasty.Bench (Benchmark, bench, bgroup, nf)+import Test.Tasty.Bench  #ifdef MIN_VERSION_text_builder-import qualified Text.Builder+import qualified TextBuilder #endif  #ifdef MIN_VERSION_bytestring_strict_builder import qualified ByteString.StrictBuilder #endif -benchDecimal ∷ Benchmark-benchDecimal = bgroup "Decimal" [benchBoundedDecimal, benchUnboundedDecimal]------------------------------------------------------------------------------------- Bounded-----------------------------------------------------------------------------------int ∷ Int+int :: Int int = 123456789123456789 -benchLazyBuilder ∷ Integral a ⇒ a → Int → T.Text-benchLazyBuilder k = toStrict . toLazyText . go mempty+benchLazyBuilder ∷ Int → T.Text+benchLazyBuilder = toStrict . toLazyText . go mempty   where     go !acc 0 = acc-    go !acc n = let i = fromIntegral n * k in go (decimal i <> (acc <> decimal i)) (n - 1)-{-# SPECIALIZE benchLazyBuilder ∷ Int → Int → T.Text #-}-{-# SPECIALIZE benchLazyBuilder ∷ Integer → Int → T.Text #-}+    go !acc n = let i = n * int in go (decimal i <> (acc <> decimal i)) (n - 1) -benchLazyBuilderBS ∷ Int → Int → B.ByteString-benchLazyBuilderBS k = B.toStrict . B.toLazyByteString . go mempty+benchLazyBuilderBS ∷ Int → B.ByteString+benchLazyBuilderBS = B.toStrict . B.toLazyByteString . go mempty   where     go !acc 0 = acc-    go !acc n = let i = n * k in go (B.intDec i <> (acc <> B.intDec i)) (n - 1)+    go !acc n = let i = n * int in go (B.intDec i <> (acc <> B.intDec i)) (n - 1)  #ifdef MIN_VERSION_text_builder-benchStrictBuilder ∷ (Integral a) ⇒ a → Int → T.Text-benchStrictBuilder k = Text.Builder.run . go mempty+benchStrictBuilder ∷ Int → T.Text+benchStrictBuilder = TextBuilder.toText . go mempty   where     go !acc 0 = acc-    go !acc n = let i = fromIntegral n * k in go (Text.Builder.decimal i <> (acc <> Text.Builder.decimal i)) (n - 1)-{-# SPECIALIZE benchStrictBuilder ∷ Int → Int → T.Text #-}-{-# SPECIALIZE benchStrictBuilder ∷ Integer → Int → T.Text #-}+    go !acc n = let i = n * int in go (TextBuilder.decimal i <> (acc <> TextBuilder.decimal i)) (n - 1) #endif  #ifdef MIN_VERSION_bytestring_strict_builder-benchStrictBuilderBS ∷ (Integral a) ⇒ a  → Int → B.ByteString-benchStrictBuilderBS k = ByteString.StrictBuilder.builderBytes . go mempty+benchStrictBuilderBS ∷ Int → B.ByteString+benchStrictBuilderBS = ByteString.StrictBuilder.builderBytes . go mempty   where     go !acc 0 = acc-    go !acc n = let i = fromIntegral n * k in go (ByteString.StrictBuilder.asciiIntegral i <> (acc <> ByteString.StrictBuilder.asciiIntegral i)) (n - 1)-{-# SPECIALIZE benchStrictBuilderBS ∷ Int → Int → B.ByteString #-}-{-# SPECIALIZE benchStrictBuilderBS ∷ Integer → Int → B.ByteString #-}+    go !acc n = let i = n * int in go (ByteString.StrictBuilder.asciiIntegral i <> (acc <> ByteString.StrictBuilder.asciiIntegral i)) (n - 1) #endif -benchBoundedLinearBuilder ∷ Int → Int → T.Text-benchBoundedLinearBuilder k m = runBuffer (\b → go b m)+benchLinearBuilder ∷ Int → T.Text+benchLinearBuilder m = runBuffer (\b → go b m)   where     go ∷ Buffer ⊸ Int → Buffer     go !acc 0 = acc-    go !acc n = let i = n * k in go (i $<| (acc |>$ i)) (n - 1)--benchBoundedDecimal ∷ Benchmark-benchBoundedDecimal = bgroup "Bounded" $ map mkBoundedGroup [1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6]--mkBoundedGroup ∷ Int → Benchmark-mkBoundedGroup n =-  bgroup-    (show n)-    [ bench "Data.Text.Lazy.Builder" $ nf (benchLazyBuilder int) n-    , bench "Data.ByteString.Builder" $ nf (benchLazyBuilderBS int) n-#ifdef MIN_VERSION_text_builder-    , bench "Text.Builder" $ nf (benchStrictBuilder int) n-#endif-#ifdef MIN_VERSION_bytestring_strict_builder-    , bench "ByteString.StrictBuilder" $ nf (benchStrictBuilderBS int) n-#endif-    , bench "Data.Text.Builder.Linear" $ nf (benchBoundedLinearBuilder int) n-    ]------------------------------------------------------------------------------------- Unbounded-----------------------------------------------------------------------------------integerSmall ∷ Integer-integerSmall = toInteger (div @Word maxBound 20)--integerBig ∷ Integer-integerBig = toInteger (maxBound @Word - 1) ^ (10 ∷ Word)--integerHuge ∷ Integer-integerHuge = toInteger (maxBound @Word - 1) ^ (100 ∷ Word)--benchUnboundedDecimal ∷ Benchmark-benchUnboundedDecimal =-  bgroup-    "Unbounded"-    [ bgroup "Small" $ map (mkUnboundedGroup integerSmall) [1e0, 1e1, 1e2, 1e3, 1e4, 1e5]-    , bgroup "Big" $ map (mkUnboundedGroup integerBig) [1e0, 1e1, 1e2, 1e3, 1e4]-    , bgroup "Huge" $ map (mkUnboundedGroup integerHuge) [1e0, 1e1, 1e2, 1e3]-    ]---- NOTE: In the following benchmarks, the ByteString builder would share work--- if the prepender and the appender are identical, while our linear buffer does--- not. So we increment the appender to get a fair benchmark.--benchUnboundedLazyBuilderBS ∷ Integer → Int → B.ByteString-benchUnboundedLazyBuilderBS k = B.toStrict . B.toLazyByteString . go mempty-  where-    go !acc 0 = acc-    go !acc n = let i = fromIntegral n * k in go (B.integerDec i <> (acc <> B.integerDec (i + 1))) (n - 1)+    go !acc n = let i = n * int in go (i $<| (acc |>$ i)) (n - 1) -benchUnboundedLinearBuilder ∷ Integer → Int → T.Text-benchUnboundedLinearBuilder k m = runBuffer (\b → go b m)-  where-    go ∷ Buffer ⊸ Int → Buffer-    go !acc 0 = acc-    go !acc n = let i = fromIntegral n * k in go (i $$<| (acc |>$$ (i + 1))) (n - 1)+benchDecimal ∷ Benchmark+benchDecimal = bgroup "Decimal" $ map mkGroup [1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6] -mkUnboundedGroup ∷ Integer → Int → Benchmark-mkUnboundedGroup integer n =-  bgroup-    (show n)-    [ bench "Data.Text.Lazy.Builder" $ nf (benchLazyBuilder integer) n-    , bench "Data.ByteString.Builder" $ nf (benchUnboundedLazyBuilderBS integer) n+mkGroup :: Int → Benchmark+mkGroup n = bgroup (show n)+  [ bench "Data.Text.Lazy.Builder" $ nf benchLazyBuilder n+  , bench "Data.ByteString.Builder" $ nf benchLazyBuilderBS n #ifdef MIN_VERSION_text_builder-    , bench "Text.Builder" $ nf (benchStrictBuilder integer) n+  , bench "TextBuilder" $ nf benchStrictBuilder n #endif #ifdef MIN_VERSION_bytestring_strict_builder-    , bench "ByteString.StrictBuilder" $ nf (benchStrictBuilderBS integer) n+  , bench "ByteString.StrictBuilder" $ nf benchStrictBuilderBS n #endif-    , bench "Data.Text.Builder.Linear" $ nf (benchUnboundedLinearBuilder integer) n-    ]+  , bench "Data.Text.Builder.Linear" $ nf benchLinearBuilder n+  ]
− bench/BenchDecimalUnbounded.hs
@@ -1,174 +0,0 @@-{-# LANGUAGE NumDecimals #-}---- |--- Copyright:   (c) 2022 Andrew Lelechenko--- Licence:     BSD3--- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>-module BenchDecimalUnbounded (benchDecimalUnbounded) where--import Data.ByteString qualified as B-import Data.ByteString.Builder qualified as B-import Data.Text qualified as T-import Data.Text.Builder.Linear.Buffer (Buffer, runBuffer, ($$<|), (|>$$))-import Data.Text.Lazy qualified as TL-import Data.Text.Lazy.Builder qualified as TB-import Data.Text.Lazy.Builder.Int qualified as TB-import Test.Tasty.Bench (Benchmark, bench, bgroup, nf)--benchUnboundedLinearBuilderAppend ∷ Integer → Int → T.Text-benchUnboundedLinearBuilderAppend k m = runBuffer (`go` m)-  where-    go ∷ Buffer ⊸ Int → Buffer-    go !acc 0 = acc-    go !acc n = let i = fromIntegral n * k in go (acc |>$$ i) (n - 1)--benchUnboundedLinearBuilderPrepend ∷ Integer → Int → T.Text-benchUnboundedLinearBuilderPrepend k m = runBuffer (`go` m)-  where-    go ∷ Buffer ⊸ Int → Buffer-    go !acc 0 = acc-    go !acc n = let i = fromIntegral n * k in go (i $$<| acc) (n - 1)---- NOTE: In the following benchmark, the ByteString builder would share work--- if the prepender and the appender are identical, while our linear buffer does--- not. So we increment the appender to get a fair benchmark.--benchUnboundedLinearBuilder ∷ Integer → Int → T.Text-benchUnboundedLinearBuilder k m = runBuffer (`go` m)-  where-    go ∷ Buffer ⊸ Int → Buffer-    go !acc 0 = acc-    go !acc n = let i = fromIntegral n * k in go (i $$<| (acc |>$$ (i + 1))) (n - 1)--benchUnboundedLazyBuilderBSAppend ∷ Integer → Int → B.ByteString-benchUnboundedLazyBuilderBSAppend k = B.toStrict . B.toLazyByteString . go mempty-  where-    go !acc 0 = acc-    go !acc n = let i = fromIntegral n * k in go (acc <> B.integerDec i) (n - 1)--benchUnboundedLazyBuilderBSPrepend ∷ Integer → Int → B.ByteString-benchUnboundedLazyBuilderBSPrepend k = B.toStrict . B.toLazyByteString . go mempty-  where-    go !acc 0 = acc-    go !acc n = let i = fromIntegral n * k in go (B.integerDec i <> acc) (n - 1)--benchUnboundedLazyBuilderBS ∷ Integer → Int → B.ByteString-benchUnboundedLazyBuilderBS k = B.toStrict . B.toLazyByteString . go mempty-  where-    go !acc 0 = acc-    go !acc n = let i = fromIntegral n * k in go (B.integerDec i <> (acc <> B.integerDec (i + 1))) (n - 1)--benchLazyBuilderAppend ∷ Integer → Int → T.Text-benchLazyBuilderAppend k = TL.toStrict . TB.toLazyText . go mempty-  where-    go !acc 0 = acc-    go !acc n = let i = fromIntegral n * k in go (acc <> TB.decimal i) (n - 1)--benchLazyBuilderPrepend ∷ Integer → Int → T.Text-benchLazyBuilderPrepend k = TL.toStrict . TB.toLazyText . go mempty-  where-    go !acc 0 = acc-    go !acc n = let i = fromIntegral n * k in go (TB.decimal i <> acc) (n - 1)--benchLazyBuilder ∷ Integer → Int → T.Text-benchLazyBuilder k = TL.toStrict . TB.toLazyText . go mempty-  where-    go !acc 0 = acc-    go !acc n = let i = fromIntegral n * k in go (TB.decimal i <> (acc <> TB.decimal (i + 1))) (n - 1)--data NamedInteger = I !String !Integer--mkGroup-  ∷ String-  → [Int]-  → (Integer → Int → T.Text)-  → (Integer → Int → B.ByteString)-  → (Integer → Int → T.Text)-  → [NamedInteger]-  → Benchmark-mkGroup name counts f g h = bgroup name . map mkBenches-  where-    mkBenches (I benchName i) =-      bgroup-        benchName-        (map (\count → bgroup (show count) (mkBench i count)) counts)-    mkBench i count =-      [ bench "Data.Text.Lazy.Builder" $ nf (f i) count-      , bench "Data.ByteString.Builder" $ nf (g i) count-      , bench "Data.Text.Builder.Linear" $ nf (h i) count-      ]-{-# INLINE mkGroup #-}--integers ∷ [NamedInteger]-integers =-  [ I "Small" (toInteger (div @Word maxBound 20)) -- ~ 9e17-  , I "Big01" (toInteger (maxBound @Word - 1) ^ (2 ∷ Word)) -- ~3e38-  , I "Big02" (toInteger (maxBound @Word - 1) ^ (5 ∷ Word)) -- ~2e96-  , I "Big03" (toInteger (maxBound @Word - 1) ^ (10 ∷ Word)) -- ~5e192-  , I "Big04" (toInteger (maxBound @Word - 1) ^ (15 ∷ Word)) -- ~1e289-  , I "Big05" (toInteger (maxBound @Word - 1) ^ (20 ∷ Word)) -- ~2e385-  -- , I "Big05a" (toInteger (maxBound @Word - 1) ^ (21 ∷ Word)) -- ~4e404-  -- , I "Big05b" (toInteger (maxBound @Word - 1) ^ (22 ∷ Word)) -- ~7e423-  -- , I "Big05c" (toInteger (maxBound @Word - 1) ^ (23 ∷ Word)) -- ~1e443-  -- , I "Big05d" (toInteger (maxBound @Word - 1) ^ (24 ∷ Word)) -- ~2e462-  , I "Big06" (toInteger (maxBound @Word - 1) ^ (25 ∷ Word)) -- ~4e481-  -- , I "Big06a" (toInteger (maxBound @Word - 1) ^ (26 ∷ Word)) -- ~8e500-  -- , I "Big06b" (toInteger (maxBound @Word - 1) ^ (27 ∷ Word)) -- ~2e520-  -- , I "Big06c" (toInteger (maxBound @Word - 1) ^ (28 ∷ Word))-  -- , I "Big06d" (toInteger (maxBound @Word - 1) ^ (29 ∷ Word))-  , I "Big07" (toInteger (maxBound @Word - 1) ^ (30 ∷ Word)) -- ~ 9e577-  , I "Big08" (toInteger (maxBound @Word - 1) ^ (35 ∷ Word)) -- ~ 2e674-  , I "Big09" (toInteger (maxBound @Word - 1) ^ (40 ∷ Word)) -- ~ 4e770-  , I "Big10" (toInteger (maxBound @Word - 1) ^ (45 ∷ Word)) -- ~ 9e866-  , I "Big11" (toInteger (maxBound @Word - 1) ^ (50 ∷ Word)) -- ~ 2e963-  , I "Huge01" (toInteger (maxBound @Word - 1) ^ (75 ∷ Word)) -- ~9e1444-  , I "Huge02" (toInteger (maxBound @Word - 1) ^ (100 ∷ Word)) -- ~4e1926-  , I "Huge03" (toInteger (maxBound @Word - 1) ^ (200 ∷ Word)) -- ~2e3853-  , I "Huge04" (toInteger (maxBound @Word - 1) ^ (300 ∷ Word)) -- ~6e5779-  , I "Huge05" (toInteger (maxBound @Word - 1) ^ (400 ∷ Word)) -- ~2e7706-  -- , I "Huge05a" (toInteger (maxBound @Word - 1) ^ (450 ∷ Word))-  , I "Huge06" (toInteger (maxBound @Word - 1) ^ (500 ∷ Word)) -- ~9e9632-  -- , I "Huge06b" (toInteger (maxBound @Word - 1) ^ (600 ∷ Word))-  , I "Huge07" (toInteger (maxBound @Word - 1) ^ (700 ∷ Word)) -- ~1e13486-  , I "Huge08" (toInteger (maxBound @Word - 1) ^ (1000 ∷ Word)) -- ~8e19265-  , I "Huge09" (toInteger (maxBound @Word - 1) ^ (3000 ∷ Word)) -- ~6e57797-  , I "Huge10" (toInteger (maxBound @Word - 1) ^ (5000 ∷ Word)) -- ~4e96329-  , I "Huge11" (toInteger (maxBound @Word - 1) ^ (10000 ∷ Word)) -- ~2e192659-  , I "Huge12" (toInteger (maxBound @Word - 1) ^ (100000 ∷ Word)) -- ~9e1926591-  -- , I "Huge13" (toInteger (maxBound @Word - 1) ^ (1000000 ∷ Word))-  , I "1e20" 1e20-  , I "1e100" 1e100-  , I "1e300" (10 ^ (300 ∷ Word))-  , I "1e500" (10 ^ (500 ∷ Word))-  , I "1e1000" (10 ^ (1000 ∷ Word))-  ]--benchDecimalUnbounded ∷ Benchmark-benchDecimalUnbounded =-  bgroup-    "Decimal: detailed unbounded"-    [ mkGroup-        "Append"-        counts-        benchLazyBuilderAppend-        benchUnboundedLazyBuilderBSAppend-        benchUnboundedLinearBuilderAppend-        integers-    , mkGroup-        "Prepend"-        counts-        benchLazyBuilderPrepend-        benchUnboundedLazyBuilderBSPrepend-        benchUnboundedLinearBuilderPrepend-        integers-    , mkGroup-        "Both"-        counts-        benchLazyBuilder-        benchUnboundedLazyBuilderBS-        benchUnboundedLinearBuilder-        integers-    ]-  where-    counts ∷ [Int]-    counts = [1e0, 1e1, 1e2]
bench/BenchDouble.hs view
@@ -15,7 +15,8 @@ import Test.Tasty.Bench  #ifdef MIN_VERSION_text_builder-import qualified Text.Builder+import qualified TextBuilder+import qualified TextBuilderDev as TextBuilder #endif  dbl :: Double@@ -35,10 +36,10 @@  #ifdef MIN_VERSION_text_builder benchStrictBuilder ∷ Int → T.Text-benchStrictBuilder = Text.Builder.run . go mempty+benchStrictBuilder = TextBuilder.toText . go mempty   where     go !acc 0 = acc-    go !acc n = let d = fromIntegral n * dbl in go (Text.Builder.fixedDouble 17 d <> (acc <> Text.Builder.fixedDouble 17 d)) (n - 1)+    go !acc n = let d = fromIntegral n * dbl in go (TextBuilder.doubleFixedPoint 17 d <> (acc <> TextBuilder.doubleFixedPoint 17 d)) (n - 1) #endif  benchLinearBuilder ∷ Int → T.Text@@ -56,7 +57,7 @@   [ bench "Data.Text.Lazy.Builder" $ nf benchLazyBuilder n   , bench "Data.ByteString.Builder" $ nf benchLazyBuilderBS n #ifdef MIN_VERSION_text_builder-  , bench "Text.Builder" $ nf benchStrictBuilder n+  , bench "TextBuilder" $ nf benchStrictBuilder n #endif   , bench "Data.Text.Builder.Linear" $ nf benchLinearBuilder n   ]
bench/BenchHexadecimal.hs view
@@ -15,56 +15,48 @@ import Test.Tasty.Bench  #ifdef MIN_VERSION_text_builder-import qualified Text.Builder+import qualified TextBuilder #endif  word :: Word word = 123456789123456789 -benchLazyBuilder ∷ Word → T.Text+benchLazyBuilder ∷ Int → T.Text benchLazyBuilder = toStrict . toLazyText . go mempty   where     go !acc 0 = acc-    go !acc n = let i = n * word in go (hexadecimal i <> (acc <> hexadecimal i)) (n - 1)+    go !acc n = let i = fromIntegral n * word in go (hexadecimal i <> (acc <> hexadecimal i)) (n - 1) -benchLazyBuilderBS ∷ Word → B.ByteString+benchLazyBuilderBS ∷ Int → B.ByteString benchLazyBuilderBS = B.toStrict . B.toLazyByteString . go mempty   where     go !acc 0 = acc-    go !acc n = go (B.wordHex n <> (acc <> B.wordHex n)) (n - 1)+    go !acc n = go (B.wordHex (fromIntegral n) <> (acc <> B.wordHex (fromIntegral n))) (n - 1)  #ifdef MIN_VERSION_text_builder-benchStrictBuilder ∷ Word → T.Text-benchStrictBuilder = Text.Builder.run . go mempty+benchStrictBuilder ∷ Int → T.Text+benchStrictBuilder = TextBuilder.toText . go mempty   where     go !acc 0 = acc-    go !acc n = let i = n * word in go (Text.Builder.hexadecimal i <> (acc <> Text.Builder.hexadecimal i)) (n - 1)+    go !acc n = let i = fromIntegral n * word in go (TextBuilder.hexadecimal i <> (acc <> TextBuilder.hexadecimal i)) (n - 1) #endif -benchLinearBuilderWord ∷ Word → T.Text-benchLinearBuilderWord m = runBuffer (\b → go b m)-  where-    go ∷ Buffer ⊸ Word → Buffer-    go !acc 0 = acc-    go !acc n = let i = n * word in go (i &<| (acc |>& i)) (n - 1)--benchLinearBuilderInt ∷ Word → T.Text-benchLinearBuilderInt m = runBuffer (\b → go b (fromIntegral m))+benchLinearBuilder ∷ Int → T.Text+benchLinearBuilder m = runBuffer (\b → go b m)   where     go ∷ Buffer ⊸ Int → Buffer     go !acc 0 = acc-    go !acc n = let i = n * fromIntegral word in go (i &<| (acc |>& i)) (n - 1)+    go !acc n = let i = fromIntegral n * word in go (i &<| (acc |>& i)) (n - 1)  benchHexadecimal ∷ Benchmark benchHexadecimal = bgroup "Hexadecimal" $ map mkGroup [1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6] -mkGroup :: Word → Benchmark+mkGroup :: Int → Benchmark mkGroup n = bgroup (show n)   [ bench "Data.Text.Lazy.Builder" $ nf benchLazyBuilder n   , bench "Data.ByteString.Builder" $ nf benchLazyBuilderBS n #ifdef MIN_VERSION_text_builder-  , bench "Text.Builder" $ nf benchStrictBuilder n+  , bench "TextBuilder" $ nf benchStrictBuilder n #endif-  , bench "Data.Text.Builder.Linear (Word)" $ nf benchLinearBuilderWord n-  , bench "Data.Text.Builder.Linear (Int)" $ nf benchLinearBuilderInt n+  , bench "Data.Text.Builder.Linear" $ nf benchLinearBuilder n   ]
bench/BenchText.hs view
@@ -15,7 +15,7 @@ import Test.Tasty.Bench  #ifdef MIN_VERSION_text_builder-import qualified Text.Builder+import qualified TextBuilder #endif  #ifdef MIN_VERSION_bytestring_strict_builder@@ -41,9 +41,9 @@  #ifdef MIN_VERSION_text_builder benchStrictBuilder ∷ Int → T.Text-benchStrictBuilder = Text.Builder.run . go mempty+benchStrictBuilder = TextBuilder.toText . go mempty   where-    txtB = Text.Builder.text txt+    txtB = TextBuilder.text txt     go !acc 0 = acc     go !acc n = go (txtB <> (acc <> txtB)) (n - 1) #endif@@ -72,7 +72,7 @@   [ bench "Data.Text.Lazy.Builder" $ nf benchLazyBuilder n   , bench "Data.ByteString.Builder" $ nf benchLazyBuilderBS n #ifdef MIN_VERSION_text_builder-  , bench "Text.Builder" $ nf benchStrictBuilder n+  , bench "TextBuilder" $ nf benchStrictBuilder n #endif #ifdef MIN_VERSION_bytestring_strict_builder   , bench "ByteString.StrictBuilder" $ nf benchStrictBuilderBS n
bench/Main.hs view
@@ -2,6 +2,7 @@ -- Copyright:   (c) 2022 Andrew Lelechenko -- Licence:     BSD3 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>+ module Main where  import Test.Tasty.Bench@@ -9,28 +10,23 @@  import BenchChar import BenchDecimal-import BenchDecimalUnbounded (benchDecimalUnbounded) import BenchDouble import BenchHexadecimal import BenchText  main ∷ IO ()-main =-  defaultMain $-    map (mapLeafBenchmarks addCompare) $-      [ benchText-      , benchChar-      , benchDecimal-      , benchDecimalUnbounded-      , benchHexadecimal-      , benchDouble-      ]+main = defaultMain $ map (mapLeafBenchmarks addCompare) $+  [ benchText+  , benchChar+  , benchDecimal+  , benchHexadecimal+  , benchDouble+  ] -textBenchName ∷ String--- textBenchName = "Data.Text.Lazy.Builder"-textBenchName = "Data.ByteString.Builder"+textBenchName :: String+textBenchName = "Data.Text.Lazy.Builder" -addCompare ∷ ([String] → Benchmark → Benchmark)+addCompare :: ([String] -> Benchmark -> Benchmark) addCompare (name : path)   | name /= textBenchName = bcompare (printAwkExpr (locateBenchmark (textBenchName : path))) addCompare _ = id
changelog.md view
@@ -1,3 +1,8 @@+## 0.1.4++* Add `instance Eq Builder` and `instance Ord Builder`.+* Fix a bug in `dropBuffer` and `takeBuffer`.+ ## 0.1.3  * Add decimal builders for unbounded inputs: `fromUnboundedDec`, `(|>$$)` and `($$<|)`.
src/Data/Text/Builder/Linear.hs view
@@ -1,9 +1,11 @@+{-# LANGUAGE CPP #-}+ -- | -- Copyright:   (c) 2022 Andrew Lelechenko -- Licence:     BSD3 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com> ----- Builder for strict 'Text' and 'ByteString', based on linear types. It consistently+-- Builder for strict t'Text' and 'ByteString', based on linear types. It consistently -- outperforms "Data.Text.Lazy.Builder" -- from @text@ as well as a strict builder from @text-builder@, -- and scales better.@@ -24,10 +26,15 @@ import Data.ByteString.Internal (ByteString (..)) import Data.Text.Internal (Text (..)) import GHC.Exts (Addr#, IsString (..))+#if MIN_VERSION_base(4,17,0)+import GHC.Exts (Multiplicity)+#else+import GHC.Base (Multiplicity)+#endif  import Data.Text.Builder.Linear.Buffer --- | Thin wrapper over 'Buffer' with a handy 'Semigroup' instance.+-- | Thin wrapper over t'Buffer' with a handy 'Semigroup' instance. -- -- >>> :set -XOverloadedStrings -XMagicHash -- >>> fromText "foo" <> fromChar '_' <> fromAddr "bar"#@@ -36,12 +43,24 @@ -- Remember: this is a strict builder, so on contrary to "Data.Text.Lazy.Builder" -- for optimal performance you should use strict left folds instead of lazy right ones. ----- Note that (similar to other builders) concatenation of 'Builder's allocates+-- Note that (similar to other builders) concatenation of t'Builder's allocates -- thunks. This is to a certain extent mitigated by aggressive inlining,--- but it is faster to use 'Buffer' directly.+-- but it is faster to use t'Buffer' directly. newtype Builder = Builder {unBuilder ∷ Buffer ⊸ Buffer} --- | Run 'Builder' computation on an empty 'Buffer', returning strict 'Text'.+-- | @since 0.1.4+instance Eq Builder where+  b1 == b2 = runBuilder b1 == runBuilder b2++-- | @since 0.1.4+instance Ord Builder where+  compare b1 b2 = compare (runBuilder b1) (runBuilder b2)+  b1 <= b2 = runBuilder b1 <= runBuilder b2+  b1 < b2 = runBuilder b1 < runBuilder b2+  b1 >= b2 = runBuilder b1 >= runBuilder b2+  b1 > b2 = runBuilder b1 > runBuilder b2++-- | Run t'Builder' computation on an empty t'Buffer', returning strict t'Text'. -- -- >>> :set -XOverloadedStrings -XMagicHash -- >>> runBuilder (fromText "foo" <> fromChar '_' <> fromAddr "bar"#)@@ -49,12 +68,12 @@ -- -- This function has a polymorphic arrow and thus can be used both in -- usual and linear contexts.-runBuilder ∷ ∀ m. Builder %m → Text+runBuilder ∷ ∀ (m ∷ Multiplicity). Builder %m → Text runBuilder (Builder f) = runBuffer f {-# INLINE runBuilder #-}  -- | Same as 'runBuilder', but returning a UTF-8 encoded strict 'ByteString'.-runBuilderBS ∷ ∀ m. Builder %m → ByteString+runBuilderBS ∷ ∀ (m ∷ Multiplicity). Builder %m → ByteString runBuilderBS (Builder f) = runBufferBS f {-# INLINE runBuilderBS #-} @@ -69,12 +88,12 @@   mempty = Builder (\b → b)   {-# INLINE mempty #-} --- | Use 'fromString' to create 'Builder' from 'String'.+-- | Use 'fromString' to create t'Builder' from 'String'. instance IsString Builder where   fromString = fromText . fromString   {-# INLINE fromString #-} --- | Create 'Builder', containing a given 'Text'.+-- | Create t'Builder', containing a given t'Text'. -- -- >>> :set -XOverloadedStrings -- >>> fromText "foo" <> fromText "bar"@@ -85,7 +104,7 @@ fromText x = Builder $ \b → b |> x {-# INLINE fromText #-} --- | Create 'Builder', containing a given 'Char'.+-- | Create t'Builder', containing a given 'Char'. -- -- >>> fromChar 'x' <> fromChar 'y' -- "xy"@@ -96,7 +115,7 @@ fromChar x = Builder $ \b → b |>. x {-# INLINE fromChar #-} --- | Create 'Builder', containing a null-terminated UTF-8 string, specified by 'Addr#'.+-- | Create t'Builder', containing a null-terminated UTF-8 string, specified by 'Addr#'. -- -- >>> :set -XMagicHash -- >>> fromAddr "foo"# <> fromAddr "bar"#@@ -108,7 +127,7 @@ fromAddr x = Builder $ \b → b |># x {-# INLINE fromAddr #-} --- | Create 'Builder', containing decimal representation of a given /bounded/ integer.+-- | Create t'Builder', containing decimal representation of a given /bounded/ integer. -- -- >>> fromChar 'x' <> fromDec (123 :: Int) -- "x123"@@ -116,7 +135,7 @@ fromDec x = Builder $ \b → b |>$ x {-# INLINE fromDec #-} --- | Create 'Builder', containing decimal representation of a given /unbounded/ integer.+-- | Create t'Builder', containing decimal representation of a given /unbounded/ integer. -- -- >>> fromChar 'x' <> fromUnboundedDec (1e24 :: Integer) -- "x1000000000000000000000000"@@ -126,7 +145,7 @@ fromUnboundedDec x = Builder $ \b → b |>$$ x {-# INLINE fromUnboundedDec #-} --- | Create 'Builder', containing hexadecimal representation of a given integer.+-- | Create t'Builder', containing hexadecimal representation of a given integer. -- -- >>> :set -XMagicHash -- >>> fromAddr "0x"# <> fromHex (0x123def :: Int)@@ -135,7 +154,7 @@ fromHex x = Builder $ \b → b |>& x {-# INLINE fromHex #-} --- | Create 'Builder', containing decimal representation of a given 'Double'.+-- | Create t'Builder', containing decimal representation of a given 'Double'. -- -- >>> :set -XMagicHash -- >>> fromAddr "pi="# <> fromDouble pi
src/Data/Text/Builder/Linear/Array.hs view
@@ -16,17 +16,16 @@ ) where  import Data.Text.Array qualified as A-import GHC.Exts (Int (..), isByteArrayPinned#, isTrue#, setByteArray#, sizeofByteArray#) import GHC.ST (ST (..)) -#if __GLASGOW_HASKELL__ >= 909-import GHC.Exts (unsafeThawByteArray#)+#if MIN_VERSION_base(4,20,0)+import GHC.Exts (Int (..), isByteArrayPinned#, isTrue#, setByteArray#, sizeofByteArray#, unsafeThawByteArray#) #else-import GHC.Exts (unsafeCoerce#)+import GHC.Exts (Int (..), isByteArrayPinned#, isTrue#, setByteArray#, sizeofByteArray#, unsafeCoerce#) #endif  unsafeThaw ∷ A.Array → ST s (A.MArray s)-#if __GLASGOW_HASKELL__ >= 909+#if MIN_VERSION_base(4,20,0) unsafeThaw (A.ByteArray a) = ST $ \s# → case unsafeThawByteArray# a s# of   (# s'#, ma #) -> (# s'#, A.MutableByteArray ma #) #else
src/Data/Text/Builder/Linear/Buffer.hs view
@@ -4,7 +4,7 @@ -- Licence:     BSD3 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com> ----- 'Buffer' for strict 'Text', based on linear types.+-- t'Buffer' for strict t'Text', based on linear types. module Data.Text.Builder.Linear.Buffer (   -- * Type   Buffer,@@ -83,7 +83,7 @@ import Data.Text.Builder.Linear.Double import Data.Text.Builder.Linear.Hex --- | Append 'Text' suffix to a 'Buffer' by mutating it.+-- | Append t'Text' suffix to a t'Buffer' by mutating it. -- If a suffix is statically known, consider using '(|>#)' for optimal performance. -- -- >>> :set -XOverloadedStrings -XLinearTypes@@ -98,7 +98,7 @@     (\dst dstOff → A.copyI srcLen dst dstOff src srcOff)     buffer --- | Prepend 'Text' prefix to a 'Buffer' by mutating it.+-- | Prepend t'Text' prefix to a t'Buffer' by mutating it. -- If a prefix is statically known, consider using '(#<|)' for optimal performance. -- -- >>> :set -XOverloadedStrings -XLinearTypes@@ -114,7 +114,7 @@     buffer  -- | Append a null-terminated UTF-8 string--- to a 'Buffer' by mutating it. E. g.,+-- to a t'Buffer' by mutating it. E. g., -- -- >>> :set -XOverloadedStrings -XLinearTypes -XMagicHash -- >>> runBuffer (\b -> b |># "foo"# |># "bar"#)@@ -134,7 +134,7 @@     srcLen = I# (cstringLength# addr#)  -- | Prepend a null-terminated UTF-8 string--- to a 'Buffer' by mutating it. E. g.,+-- to a t'Buffer' by mutating it. E. g., -- -- >>> :set -XOverloadedStrings -XLinearTypes -XMagicHash -- >>> runBuffer (\b -> "foo"# #<| "bar"# #<| b)
src/Data/Text/Builder/Linear/Char.hs view
@@ -31,7 +31,7 @@ -- Single char -------------------------------------------------------------------------------- --- | Append 'Char' to a 'Buffer' by mutating it.+-- | Append 'Char' to a t'Buffer' by mutating it. -- -- >>> :set -XLinearTypes -- >>> runBuffer (\b -> b |>. 'q' |>. 'w')@@ -45,7 +45,7 @@ infixl 6 |>. buffer |>. ch = appendBounded 4 (\dst dstOff → unsafeWrite dst dstOff ch) buffer --- | Prepend 'Char' to a 'Buffer' by mutating it.+-- | Prepend 'Char' to a t'Buffer' by mutating it. -- -- >>> :set -XLinearTypes -- >>> runBuffer (\b -> 'q' .<| 'w' .<| b)@@ -95,11 +95,15 @@ -- Multiple chars -------------------------------------------------------------------------------- --- | Prepend a given count of a 'Char' to a 'Buffer'.+-- | Prepend a given count of a 'Char' to a t'Buffer'. -- -- >>> :set -XLinearTypes -- >>> runBuffer (\b -> prependChars 3 'x' (b |>. 'A')) -- "xxxA"+--+-- __Warning:__ In contrast to 'Data.Text.Lazy.Builder.singleton', it is the+-- responsibility of the caller to sanitize surrogate code points with+-- 'Data.Text.Internal.safe'. prependChars ∷ Word → Char → Buffer ⊸ Buffer prependChars count ch buff   | count == 0 = buff@@ -115,11 +119,15 @@               )               buff --- | Apppend a given count of a 'Char' to a 'Buffer'.+-- | Apppend a given count of a 'Char' to a t'Buffer'. -- -- >>> :set -XLinearTypes -- >>> runBuffer (\b -> appendChars 3 'x' (b |>. 'A')) -- "Axxx"+--+-- __Warning:__ In contrast to 'Data.Text.Lazy.Builder.singleton', it is the+-- responsibility of the caller to sanitize surrogate code points with+-- 'Data.Text.Internal.safe'. appendChars ∷ Word → Char → Buffer ⊸ Buffer appendChars count ch buff   | count == 0 = buff@@ -164,6 +172,10 @@ -- -- >>> runBuffer (\b -> (b |> "Test:") `appendJustified` "AAA" `appendJustified` "BBBBBBB") -- "Test:         AAA     BBBBBBB"+--+-- __Warning:__ In contrast to 'Data.Text.Lazy.Builder.singleton', it is the+-- responsibility of the caller to sanitize surrogate code points with+-- 'Data.Text.Internal.safe'. justifyRight ∷ Word → Char → Buffer ⊸ Buffer justifyRight n ch buff = case lengthOfBuffer buff of   (# buff', len #) →@@ -183,6 +195,10 @@ -- -- Note that 'newEmptyBuffer' is needed in some situations. See 'justifyRight' -- for an example.+--+-- __Warning:__ In contrast to 'Data.Text.Lazy.Builder.singleton', it is the+-- responsibility of the caller to sanitize surrogate code points with+-- 'Data.Text.Internal.safe'. justifyLeft ∷ Word → Char → Buffer ⊸ Buffer justifyLeft n ch buff = case lengthOfBuffer buff of   (# buff', len #) →@@ -201,6 +217,10 @@ -- -- Note that 'newEmptyBuffer' is needed in some situations. See 'justifyRight' -- for an example.+--+-- __Warning:__ In contrast to 'Data.Text.Lazy.Builder.singleton', it is the+-- responsibility of the caller to sanitize surrogate code points with+-- 'Data.Text.Internal.safe'. center ∷ Word → Char → Buffer ⊸ Buffer center n ch buff = case lengthOfBuffer buff of   (# buff', len #) →
src/Data/Text/Builder/Linear/Core.hs view
@@ -4,7 +4,7 @@ -- Licence:     BSD3 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com> ----- Low-level routines for 'Buffer' manipulations.+-- Low-level routines for t'Buffer' manipulations. module Data.Text.Builder.Linear.Core (   -- * Type   Buffer,
src/Data/Text/Builder/Linear/Double.hs view
@@ -21,8 +21,6 @@  -- | Append the decimal representation of a 'Double'. ----- Matches 'show' in displaying in standard or scientific notation:--- -- >>> runBuffer (\b -> b |>% 123.456) -- "123.456" --@@ -38,9 +36,6 @@     buffer  -- | Prepend the decimal representation of a 'Double'.------ Matches 'show' in displaying in standard or scientific notation--- (see examples in @'(|>%)'@). (%<|) ∷ Double → Buffer ⊸ Buffer  infixr 6 %<|
src/Data/Text/Builder/Linear/Internal.hs view
@@ -1,10 +1,12 @@+{-# LANGUAGE CPP #-}+ -- | -- Copyright:   (c) 2022 Andrew Lelechenko --              (c) 2023 Pierre Le Marre -- Licence:     BSD3 -- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com> ----- Internal routines for 'Buffer' manipulations.+-- Internal routines for t'Buffer' manipulations. module Data.Text.Builder.Linear.Internal (   -- * Type   Buffer,@@ -35,21 +37,26 @@ import Data.Text qualified as T import Data.Text.Array qualified as A import Data.Text.Internal (Text (..))-import GHC.Exts (Int (..), Levity (..), RuntimeRep (..), TYPE, byteArrayContents#, plusAddr#, unsafeCoerce#) import GHC.ForeignPtr (ForeignPtr (..), ForeignPtrContents (..)) import GHC.ST (ST (..), runST) +#if MIN_VERSION_base(4,20,0)+import GHC.Exts (Int (..), Levity (..), RuntimeRep (..), TYPE, byteArrayContents#, plusAddr#, unsafeThawByteArray#, realWorld#)+#else+import GHC.Exts (Int (..), Levity (..), RuntimeRep (..), TYPE, byteArrayContents#, plusAddr#, unsafeCoerce#)+#endif+ import Data.Text.Builder.Linear.Array --- | Internally 'Buffer' is a mutable buffer.--- If a client gets hold of a variable of type 'Buffer',+-- | Internally t'Buffer' is a mutable buffer.+-- If a client gets hold of a variable of type t'Buffer', -- they'd be able to pass a mutable buffer to concurrent threads. -- That's why API below is carefully designed to prevent such possibility:--- clients always work with linear functions 'Buffer' ⊸ 'Buffer' instead--- and run them on an empty 'Buffer' to extract results.+-- clients always work with linear functions t'Buffer' ⊸ t'Buffer' instead+-- and run them on an empty t'Buffer' to extract results. -- -- In terms of [@linear-base@](https://hackage.haskell.org/package/linear-base)--- 'Buffer' is [@Consumable@](https://hackage.haskell.org/package/linear-base/docs/Prelude-Linear.html#t:Consumable)+-- t'Buffer' is [@Consumable@](https://hackage.haskell.org/package/linear-base/docs/Prelude-Linear.html#t:Consumable) -- (see 'consumeBuffer') -- and [@Dupable@](https://hackage.haskell.org/package/linear-base/docs/Prelude-Linear.html#t:Dupable) -- (see 'dupBuffer'),@@ -63,19 +70,19 @@ -- Remember: this is a strict builder, so on contrary to "Data.Text.Lazy.Builder" -- for optimal performance you should use strict left folds instead of lazy right ones. ----- 'Buffer' is an unlifted datatype,+-- t'Buffer' is an unlifted datatype, -- so you can put it into an unboxed tuple @(# ..., ... #)@, -- but not into @(..., ...)@. data Buffer ∷ TYPE ('BoxedRep 'Unlifted) where   Buffer ∷ {-# UNPACK #-} !Text → Buffer --- | Unwrap 'Buffer', no-op.+-- | Unwrap t'Buffer', no-op. -- Most likely, this is not the function you're looking for -- and you need 'runBuffer' instead. unBuffer ∷ Buffer ⊸ Text unBuffer (Buffer x) = x --- | Run a linear function on an empty 'Buffer', producing a strict 'Text'.+-- | Run a linear function on an empty t'Buffer', producing a strict t'Text'. -- -- Be careful to write @runBuffer (\\b -> ...)@ instead of @runBuffer $ \\b -> ...@, -- because current implementation of linear types lacks special support for '($)'.@@ -88,10 +95,8 @@ -- 'runBuffer' is similar in spirit to mutable arrays API in -- [@Data.Array.Mutable.Linear@](https://hackage.haskell.org/package/linear-base/docs/Data-Array-Mutable-Linear.html), -- which provides functions like--- [@fromList@](https://hackage.haskell.org/package/linear-base/docs/Data-Array-Mutable-Linear.html#v:fromList) ∷ [@a@] → (@Vector@ @a@ ⊸ [@Ur@](https://hackage.haskell.org/package/linear-base-0.3.0/docs/Prelude-Linear.html#t:Ur) b) ⊸ [@Ur@](https://hackage.haskell.org/package/linear-base-0.3.0/docs/Prelude-Linear.html#t:Ur) @b@.--- Here the initial buffer is always empty and @b@ is 'Text'. Since 'Text' is--- [@Movable@](https://hackage.haskell.org/package/linear-base/docs/Prelude-Linear.html#t:Movable),--- 'Text' and [@Ur@](https://hackage.haskell.org/package/linear-base-0.3.0/docs/Prelude-Linear.html#t:Ur) 'Text' are equivalent.+-- [@fromList@](https://hackage.haskell.org/package/linear-base/docs/Data-Array-Mutable-Linear.html#v:fromList) ∷ @Movable@ @b@ ⇒ [@a@] → (@Array@ @a@ ⊸ @b@) ⊸ @b@.+-- Here the initial buffer is always empty and @b@ is t'Text'. runBuffer ∷ (Buffer ⊸ Buffer) ⊸ Text runBuffer f = unBuffer (shrinkBuffer (f (Buffer mempty))) {-# NOINLINE runBuffer #-}@@ -100,7 +105,7 @@   See https://github.com/Bodigrim/linear-builder/issues/19   and https://github.com/tweag/linear-base/pull/187#discussion_r489081926   for the discussion why NOINLINE here and below in 'runBufferBS' is necessary.-  Without it CSE (common subexpression elimination) can pull out 'Buffer's from+  Without it CSE (common subexpression elimination) can pull out t'Buffer's from   different 'runBuffer's and share them, which is absolutely not what we want. -} @@ -110,7 +115,11 @@   Buffer (Text (A.ByteArray arr) (I# from) len) → BS fp len     where       addr# = byteArrayContents# arr `plusAddr#` from+#if MIN_VERSION_base(4,20,0)+      fp = ForeignPtr addr# (PlainPtr (let !(# _, ma #) = unsafeThawByteArray# arr realWorld# in ma))+#else       fp = ForeignPtr addr# (PlainPtr (unsafeCoerce# arr))+#endif {-# NOINLINE runBufferBS #-}  shrinkBuffer ∷ Buffer ⊸ Buffer@@ -126,9 +135,9 @@   arr ← A.unsafeFreeze marr   pure $ Text arr 0 0 --- | Create an empty 'Buffer'.+-- | Create an empty t'Buffer'. ----- The first 'Buffer' is the input and the second is a new empty 'Buffer'.+-- The first t'Buffer' is the input and the second is a new empty t'Buffer'. -- -- This function is needed in some situations, e.g. with -- 'Data.Text.Builder.Linear.Buffer.justifyRight'. The following example creates@@ -149,7 +158,7 @@ -- "Test:         AAA     BBBBBBB" -- -- Note: a previous buffer is necessary in order to create an empty buffer with--- the same characteristics.+-- the same pinnedness. newEmptyBuffer ∷ Buffer ⊸ (# Buffer, Buffer #) newEmptyBuffer (Buffer t@(Text arr _ _)) =   (# Buffer t, Buffer (if isPinned arr then memptyPinned else mempty) #)@@ -160,7 +169,7 @@ -- from [@linear-base@](https://hackage.haskell.org/package/linear-base). -- -- It is a bit tricky to use because of--- <https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/linear_types.html#limitations current limitations>+-- <https://downloads.haskell.org/ghc/9.8.1/docs/users_guide/exts/linear_types.html#limitations current limitations> -- of linear types with regards to @let@ and @where@. E. g., one cannot write -- -- > let (# b1, b2 #) = dupBuffer b in ("foo" <| b1) >< (b2 |> "bar")@@ -172,7 +181,7 @@ -- >>> runBuffer (\b -> case dupBuffer b of (# b1, b2 #) -> ("foo" <| b1) >< (b2 |> "bar")) -- "foobar" ----- Note the unboxed tuple: 'Buffer' is an unlifted datatype,+-- Note the unboxed tuple: t'Buffer' is an unlifted datatype, -- so it cannot be put into @(..., ...)@. dupBuffer ∷ Buffer ⊸ (# Buffer, Buffer #) dupBuffer (Buffer x) = (# Buffer x, Buffer (T.copy x) #)@@ -184,7 +193,7 @@ consumeBuffer ∷ Buffer ⊸ () consumeBuffer Buffer {} = () --- | Erase buffer's content, replacing it with an empty 'Text'.+-- | Erase buffer's content, replacing it with an empty t'Text'. eraseBuffer ∷ Buffer ⊸ Buffer eraseBuffer (Buffer (Text arr _ _)) =   Buffer (if isPinned arr then memptyPinned else mempty)@@ -208,23 +217,23 @@ lengthOfBuffer ∷ Buffer ⊸ (# Buffer, Word #) lengthOfBuffer (Buffer t) = (# Buffer t, fromIntegral (T.length t) #) --- | Slice 'Buffer' by dropping given number of 'Char's.+-- | Slice t'Buffer' by dropping given number of 'Char's. dropBuffer ∷ Word → Buffer ⊸ Buffer dropBuffer nChar (Buffer t@(Text arr off len))-  | nByte <= 0 = Buffer (Text arr (off + len) 0)+  | nByte < 0 = Buffer (Text arr (off + len) 0)   | otherwise = Buffer (Text arr (off + nByte) (len - nByte))   where     nByte = T.measureOff (fromIntegral nChar) t --- | Slice 'Buffer' by taking given number of 'Char's.+-- | Slice t'Buffer' by taking given number of 'Char's. takeBuffer ∷ Word → Buffer ⊸ Buffer takeBuffer nChar (Buffer t@(Text arr off _))-  | nByte <= 0 = Buffer t+  | nByte < 0 = Buffer t   | otherwise = Buffer (Text arr off nByte)   where     nByte = T.measureOff (fromIntegral nChar) t --- | Low-level routine to append data of unknown size to a 'Buffer'.+-- | Low-level routine to append data of unknown size to a t'Buffer'. appendBounded   ∷ Int   -- ^ Upper bound for the number of bytes, written by an action@@ -248,7 +257,7 @@   pure $ Text new dstOff (dstLen + srcLen) {-# INLINE appendBounded #-} --- | Low-level routine to append data of unknown size to a 'Buffer', giving+-- | Low-level routine to append data of unknown size to a t'Buffer', giving -- the action the choice between two strategies. -- -- See also: 'appendBounded'.@@ -296,12 +305,12 @@           -- Note: we rely on copyM allowing overlaps           A.copyM newM (dstOff + dstLen) newM (off' - count) count           pure (dstOff, count)-  !(dstOff', srcLen) ← writer append prepend+  (dstOff', srcLen) ← writer append prepend   new ← A.unsafeFreeze newM   pure $ Text new dstOff' (dstLen + srcLen) {-# INLINE appendBounded' #-} --- | Low-level routine to append data of known size to a 'Buffer'.+-- | Low-level routine to append data of known size to a t'Buffer'. appendExact   ∷ Int   -- ^ Exact number of bytes, written by an action@@ -315,7 +324,7 @@     (\dst dstOff → appender dst dstOff >> pure srcLen) {-# INLINE appendExact #-} --- | Low-level routine to prepend data of unknown size to a 'Buffer'.+-- | Low-level routine to prepend data of unknown size to a t'Buffer'. prependBounded   ∷ Int   -- ^ Upper bound for the number of bytes, written by an action@@ -344,7 +353,7 @@       pure $ Text new newOff (dstLen + srcLen) {-# INLINE prependBounded #-} --- | Low-level routine to prepend data of unknown size to a 'Buffer'.+-- | Low-level routine to prepend data of unknown size to a t'Buffer'. -- -- Contrary to 'prependBounded', only use a prepend action. --@@ -374,7 +383,7 @@       pure $ Text new (off - srcLen) (dstLen + srcLen) {-# INLINE prependBounded' #-} --- | Low-level routine to append data of known size to a 'Buffer'.+-- | Low-level routine to append data of known size to a t'Buffer'. prependExact   ∷ Int   -- ^ Exact number of bytes, written by an action@@ -389,7 +398,7 @@     (\dst dstOff → appender dst dstOff >> pure srcLen) {-# INLINE prependExact #-} --- | Concatenate two 'Buffer's, potentially mutating both of them.+-- | Concatenate two t'Buffer's, potentially mutating both of them. -- -- You likely need to use 'dupBuffer' to get hold on two builders at once: --
test/Main.hs view
@@ -21,6 +21,7 @@ import qualified Data.Text as T import qualified Data.Text.Encoding as T import Data.Text.Builder.Linear.Buffer+import Data.Text.Builder.Linear.Core (dropBuffer, takeBuffer) import Data.Text.Internal (Text(..)) import Data.Text.Lazy (toStrict) import Data.Text.Lazy.Builder qualified as TB@@ -120,7 +121,7 @@     where       arbitraryCharCount = chooseBoundedIntegral (0, 6)       arbitraryTotalLength = chooseBoundedIntegral (3, 20)-      arbitraryInteger = chooseInteger +      arbitraryInteger = chooseInteger         ( fromIntegral @Int minBound ^ (3 :: Word)         , fromIntegral @Int maxBound ^ (3 :: Word) ) @@ -186,12 +187,7 @@       then hexadecimal x       else hexadecimal (fromIntegral @_ @Word64 x .&. (shiftL 1 (intSize x) - 1)) -    hexadecimalSW (SomeWordN x) = hexadecimalW x--    hexadecimalW ∷ (KnownNat n) ⇒ WordN n → TB.Builder-    hexadecimalW x = if x >= 0-      then hexadecimal x-      else hexadecimal (fromIntegral @_ @Word64 x .&. (shiftL 1 (intSize x) - 1))+    hexadecimalSW (SomeWordN x) = hexadecimal x      intersperseText ∷ [TB.Builder] → Text     intersperseText bs =@@ -242,7 +238,24 @@   , testProperty "CSE 1" prop6   , testProperty "CSE 2" prop7   , testProperty "unbounded integers" prop8+  , testProperty "dropBuffer" propDropBuffer+  , testProperty "dropBuffer" propDropBuffer+  , testProperty "takeBuffer" propTakeBuffer   ]++propDropBuffer :: Word → Text → Property+propDropBuffer n xs =+  suff === runBuffer (\b → dropBuffer n (b |> xs)) .&&.+  T.encodeUtf8 suff === runBufferBS (\b → dropBuffer n (b |> xs))+  where+    suff = T.drop (fromIntegral n) xs++propTakeBuffer :: Word → Text → Property+propTakeBuffer n xs =+  pref === runBuffer (\b → takeBuffer n (b |> xs)) .&&.+  T.encodeUtf8 pref === runBufferBS (\b → takeBuffer n (b |> xs))+  where+    pref = T.take (fromIntegral n) xs  prop1 ∷ [Action] → Property prop1 acts = interpretOnText acts mempty ===
text-builder-linear.cabal view
@@ -1,12 +1,15 @@ cabal-version:   2.4 name:            text-builder-linear-version:         0.1.3+version:         0.1.4 license:         BSD-3-Clause license-file:    LICENSE copyright:       2022 Andrew Lelechenko maintainer:      Andrew Lelechenko <andrew.lelechenko@gmail.com> author:          Andrew Lelechenko-tested-with:     ghc ==9.2.8 ghc ==9.4.8 ghc ==9.6.6 ghc ==9.8.2 ghc ==9.10.1+tested-with:+    ghc ==9.2.8 ghc ==9.4.8 ghc ==9.6.7 ghc ==9.8.4 ghc ==9.10.3+    ghc ==9.12.2 ghc ==9.14.1+ homepage:        https://github.com/Bodigrim/linear-builder synopsis:        Builder for Text and ByteString based on linear types description:@@ -20,7 +23,7 @@  source-repository head     type:     git-    location: git://github.com/Bodigrim/linear-builder.git+    location: git@github.com:Bodigrim/linear-builder.git  library     exposed-modules:@@ -48,7 +51,7 @@         base >=4.16 && <5,         text >=2.0 && <2.2,         bytestring >=0.11 && <0.13,-        ghc-bignum >=1.1 && < 2.0,+        ghc-bignum >=1.1 && <2,         quote-quot >=0.2.1 && <0.3  test-suite linear-builder-tests@@ -57,8 +60,8 @@     hs-source-dirs:     test     default-language:   GHC2021     default-extensions:-        DerivingStrategies LinearTypes MagicHash NumDecimals PatternSynonyms-        UnboxedTuples UnicodeSyntax+        DerivingStrategies LinearTypes MagicHash NumDecimals+        PatternSynonyms UnboxedTuples UnicodeSyntax      ghc-options:         -Wall -Wno-orphans -threaded -rtsopts "-with-rtsopts -N"@@ -77,7 +80,6 @@     other-modules:         BenchChar         BenchDecimal-        BenchDecimalUnbounded         BenchDouble         BenchHexadecimal         BenchText@@ -93,7 +95,8 @@         -- NOTE: The following packages are optional, but are not required that         --       often. While they could be guarded by a flag, we prefer keeping         --       the Hackage page simple. Just uncomment these lines when needed.-        -- bytestring-strict-builder >= 0.4.5 && < 0.5,-        -- text-builder >= 0.6.7 && < 0.7,+        -- bytestring-strict-builder >=0.4.5 && <0.5,+        -- text-builder >=1.0 && <1.1,+        -- text-builder-dev >=0.4 && <0.5,         tasty,-        tasty-bench >=0.4 && <0.5+        tasty-bench >=0.4 && <0.6