Skip to content

Commit 0178d46

Browse files
vdukhovnihs-viktor
andauthored
New API to convert directly between lazy and short bytestrings (#619)
* New API to convert directly between Lazy and Short * fixup! New API to convert directly between Lazy and Short Matthew Craven review fixes --------- Co-authored-by: Viktor Dukhovni <[email protected]>
1 parent 5ecc0d7 commit 0178d46

File tree

4 files changed

+47
-2
lines changed

4 files changed

+47
-2
lines changed

Changelog.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
* [`Data.Data.dataTypeOf` for `StrictByteString` and `LazyByteString` now returns a `DataType` that uses `AlgRep` instead of `NoRep`.](https://github.com/haskell/bytestring/pull/614)
55
* This allows utilities like `syb:Data.Generics.Text.gread` to be meaningfully used at these types containing `ByteString`s.
66
* [`fromListN` in `instance IsList ByteString` truncates input list if it's longer than the size hint](https://github.com/haskell/bytestring/pull/672)
7+
8+
* API additions and behavior changes:
9+
* Data.ByteString.Short now provides `lazyToShort` and `lazyFromShort`.
10+
711
<!--
812
* Bug fixes:
9-
* API additions and behavior changes:
1013
* Deprecations:
1114
* Performance improvements:
1215
* Miscellaneous:

Data/ByteString/Short.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ module Data.ByteString.Short (
7474
unpack,
7575
fromShort,
7676
toShort,
77+
lazyFromShort,
78+
lazyToShort,
7779

7880
-- * Basic interface
7981
snoc,

Data/ByteString/Short/Internal.hs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ module Data.ByteString.Short.Internal (
3333
unpack,
3434
fromShort,
3535
toShort,
36+
lazyFromShort,
37+
lazyToShort,
3638

3739
-- * Basic interface
3840
snoc,
@@ -242,6 +244,7 @@ import Prelude
242244
)
243245

244246
import qualified Data.ByteString.Internal.Type as BS
247+
import qualified Data.ByteString.Lazy.Internal as LBS
245248

246249
import qualified Data.List as List
247250
import qualified GHC.Exts
@@ -450,6 +453,35 @@ toShortIO (BS fptr len) = do
450453
stToIO (copyAddrToByteArray ptr mba 0 len)
451454
ShortByteString <$> stToIO (unsafeFreezeByteArray mba)
452455

456+
-- | A simple wrapper around 'fromShort' that wraps the strict 'ByteString' as
457+
-- a one-chunk 'LBS.LazyByteString'.
458+
lazyFromShort :: ShortByteString -> LBS.ByteString
459+
lazyFromShort = LBS.fromStrict . fromShort
460+
461+
-- | /O(n)/. Convert an 'LBS.LazyByteString' into a 'ShortByteString'.
462+
--
463+
-- This makes a copy, so does not retain the input string. Naturally, best
464+
-- used only with sufficiently short lazy ByteStrings. The entire lazy
465+
-- ByteString is brought into memory before a copy is made.
466+
--
467+
lazyToShort :: LBS.ByteString -> ShortByteString
468+
lazyToShort LBS.Empty = empty
469+
lazyToShort lbs = unsafeDupablePerformIO $ do
470+
mba <- stToIO (newByteArray total)
471+
copy mba lbs
472+
ShortByteString <$> stToIO (unsafeFreezeByteArray mba)
473+
where
474+
!total = LBS.foldlChunks (\acc (BS _ l) -> checkedAdd "short.lazyToShort" acc l) 0 lbs
475+
476+
copy :: MutableByteArray RealWorld -> LBS.ByteString -> IO ()
477+
copy mba = go 0
478+
where
479+
go off (LBS.Chunk (BS fp len) cs) = do
480+
BS.unsafeWithForeignPtr fp $ \p ->
481+
stToIO $ copyAddrToByteArray p mba off len
482+
go (off + len) cs
483+
go !_ LBS.Empty = pure ()
484+
453485
-- | /O(n)/. Convert a 'ShortByteString' into a 'ByteString'.
454486
--
455487
fromShort :: ShortByteString -> ByteString

tests/Properties.hs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ prop_stimesOverflowScary bs =
299299
prop_stimesOverflowEmpty = forAll (choose (0, maxBound @Word)) $ \n ->
300300
stimes n mempty === mempty @P.ByteString
301301

302-
concat32bitOverflow :: (Int -> a) -> ([a] -> a) -> Property
302+
concat32bitOverflow :: (Int -> a) -> ([a] -> b) -> Property
303303
concat32bitOverflow replicateLike concatLike = let
304304
intBits = finiteBitSize @Int 0
305305
largeBS = concatLike $ replicate (bit 14) $ replicateLike (bit 17)
@@ -315,6 +315,10 @@ prop_32bitOverflow_Lazy_toStrict :: Property
315315
prop_32bitOverflow_Lazy_toStrict =
316316
concat32bitOverflow (`P.replicate` 0) (L.toStrict . L.fromChunks)
317317

318+
prop_32bitOverflow_Lazy_toShort :: Property
319+
prop_32bitOverflow_Lazy_toShort =
320+
concat32bitOverflow (`P.replicate` 0) (Short.lazyToShort . L.fromChunks)
321+
318322
prop_32bitOverflow_Short_mconcat :: Property
319323
prop_32bitOverflow_Short_mconcat =
320324
concat32bitOverflow makeShort mconcat
@@ -530,6 +534,8 @@ prop_short_pack_unpack xs =
530534
(Short.unpack . Short.pack) xs == xs
531535
prop_short_toShort_fromShort bs =
532536
(Short.fromShort . Short.toShort) bs == bs
537+
prop_short_lazyToShort_fromShort lbs =
538+
(Short.lazyFromShort . Short.lazyToShort) lbs == lbs
533539

534540
prop_short_toShort_unpack bs =
535541
(Short.unpack . Short.toShort) bs == P.unpack bs
@@ -596,6 +602,7 @@ prop_short_pinned (NonNegative (I# len#)) = runST $ ST $ \s ->
596602
short_tests =
597603
[ testProperty "pack/unpack" prop_short_pack_unpack
598604
, testProperty "toShort/fromShort" prop_short_toShort_fromShort
605+
, testProperty "lazyToShort/fromShort" prop_short_lazyToShort_fromShort
599606
, testProperty "toShort/unpack" prop_short_toShort_unpack
600607
, testProperty "pack/fromShort" prop_short_pack_fromShort
601608
, testProperty "empty" prop_short_empty
@@ -663,6 +670,7 @@ overflow_tests =
663670
, testProperty "StrictByteString stimes (empty)" prop_stimesOverflowEmpty
664671
, testProperty "StrictByteString mconcat" prop_32bitOverflow_Strict_mconcat
665672
, testProperty "LazyByteString toStrict" prop_32bitOverflow_Lazy_toStrict
673+
, testProperty "LazyByteString toShort" prop_32bitOverflow_Lazy_toShort
666674
, testProperty "ShortByteString mconcat" prop_32bitOverflow_Short_mconcat
667675
]
668676

0 commit comments

Comments
 (0)