winery 0.1 → 0.1.1
raw patch · 6 files changed
+199/−79 lines, 6 filesdep +cpudep +fingertreedep −fast-builder
Dependencies added: cpu, fingertree
Dependencies removed: fast-builder
Files
- README.md +29/−2
- benchmarks/bench.hs +1/−1
- src/Data/Winery.hs +38/−37
- src/Data/Winery/Internal.hs +31/−33
- src/Data/Winery/Internal/Builder.hs +89/−0
- winery.cabal +11/−6
README.md view
@@ -3,8 +3,7 @@ winery is a serialisation library for Haskell. It tries to achieve two goals: compact representation and perpetual inspectability. -The `binary` library provides a compact representation, but there is no way to-inspect the serialised value without the original instance.+The standard `binary` library has no way to inspect the serialised value without the original instance. There's `serialise`, which is an alternative library based on CBOR. Every value has to be accompanied with tags, so it tends to be redundant for arrays of small values. Encoding records with field names is also redudant. @@ -114,3 +113,31 @@ , foo: Just 42 } ```++## Benchmark++```haskell+data TestRec = TestRec+ { id_ :: !Int+ , first_name :: !Text+ , last_name :: !Text+ , email :: !Text+ , gender :: !Gender+ , num :: !Int+ , latitude :: !Double+ , longitude :: !Double+ } deriving (Show, Generic)+```++(De)serialisation of the datatype above using generic instances:++```+serialise/winery mean 658.6 μs ( +- 45.04 μs )+serialise/binary mean 1.056 ms ( +- 58.95 μs )+serialise/serialise mean 258.8 μs ( +- 5.654 μs )+deserialise/winery mean 706.4 μs ( +- 52.41 μs )+deserialise/binary mean 1.393 ms ( +- 56.71 μs )+deserialise/serialise mean 765.8 μs ( +- 30.26 μs )+```++Not bad, considering that binary and serialise don't encode field names.
benchmarks/bench.hs view
@@ -46,7 +46,7 @@ winery <- B.readFile "benchmarks/data.winery" binary <- B.readFile "benchmarks/data.binary" cbor <- B.readFile "benchmarks/data.cbor"- Right (values :: [TestRec]) <- return $ deserialise winery+ values :: [TestRec] <- return $ B.decode $ BL.fromStrict binary defaultMain [ bgroup "serialise" [ bench "winery" $ nf serialise values
src/Data/Winery.hs view
@@ -12,6 +12,7 @@ {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE StandaloneDeriving #-} module Data.Winery ( Schema(..) , Serialise(..)@@ -59,7 +60,7 @@ import Control.Monad.Trans.Cont import Control.Monad.Reader import qualified Data.ByteString as B-import qualified Data.ByteString.FastBuilder as BB+import qualified Data.Winery.Internal.Builder as BB import Data.Bits import Data.Dynamic import Data.Functor.Compose@@ -216,8 +217,9 @@ -- | Serialise a value along with its schema. serialise :: Serialise a => a -> B.ByteString-serialise a = BB.toStrictByteString $ mappend (BB.word8 currentSchemaVersion)- $ encodingBuilder $ toEncoding (schema [a], a)+serialise a = BB.toByteString $ mappend (BB.word8 currentSchemaVersion)+ $ toEncoding (schema [a], a)+{-# INLINE serialise #-} -- | Deserialise a 'serialise'd 'B.Bytestring'. deserialise :: Serialise a => B.ByteString -> Either StrategyError a@@ -232,7 +234,7 @@ -- | Serialise a value without its schema. serialiseOnly :: Serialise a => a -> B.ByteString-serialiseOnly = BB.toStrictByteString . encodingBuilder . toEncoding+serialiseOnly = BB.toByteString . toEncoding {-# INLINE serialiseOnly #-} substSchema :: Serialise a => Proxy a -> [TypeRep] -> Schema@@ -298,8 +300,8 @@ instance Serialise Bool where schemaVia _ _ = SBool- toEncoding False = Encoding 1 (BB.word8 0)- toEncoding True = Encoding 1 (BB.word8 1)+ toEncoding False = BB.word8 0+ toEncoding True = BB.word8 1 deserialiser = Deserialiser $ Plan $ \case SBool -> pure $ (/=0) <$> evalContT getWord8 s -> unexpectedSchema "Serialise Bool" s@@ -307,7 +309,7 @@ instance Serialise Word8 where schemaVia _ _ = SWord8- toEncoding x = Encoding 1 (BB.word8 x)+ toEncoding = BB.word8 deserialiser = Deserialiser $ Plan $ \case SWord8 -> pure $ evalContT getWord8 s -> unexpectedSchema "Serialise Word8" s@@ -315,7 +317,7 @@ instance Serialise Word16 where schemaVia _ _ = SWord16- toEncoding x = Encoding 2 (BB.word16BE x)+ toEncoding = BB.word16 deserialiser = Deserialiser $ Plan $ \case SWord16 -> pure $ evalContT $ do a <- getWord8@@ -326,7 +328,7 @@ instance Serialise Word32 where schemaVia _ _ = SWord32- toEncoding x = Encoding 4 (BB.word32BE x)+ toEncoding = BB.word32 deserialiser = Deserialiser $ Plan $ \case SWord32 -> pure word32be s -> unexpectedSchema "Serialise Word32" s@@ -334,7 +336,7 @@ instance Serialise Word64 where schemaVia _ _ = SWord64- toEncoding x = Encoding 8 (BB.word64BE x)+ toEncoding = BB.word64 deserialiser = Deserialiser $ Plan $ \case SWord64 -> pure word64be s -> unexpectedSchema "Serialise Word64" s@@ -342,7 +344,7 @@ instance Serialise Word where schemaVia _ _ = SWord64- toEncoding x = Encoding 8 $ BB.word64BE $ fromIntegral x+ toEncoding = BB.word64 . fromIntegral deserialiser = Deserialiser $ Plan $ \case SWord64 -> pure $ fromIntegral <$> word64be s -> unexpectedSchema "Serialise Word" s@@ -350,7 +352,7 @@ instance Serialise Int8 where schemaVia _ _ = SInt8- toEncoding x = Encoding 1 (BB.int8 x)+ toEncoding = BB.word8 . fromIntegral deserialiser = Deserialiser $ Plan $ \case SInt8 -> pure $ fromIntegral <$> evalContT getWord8 s -> unexpectedSchema "Serialise Int8" s@@ -358,7 +360,7 @@ instance Serialise Int16 where schemaVia _ _ = SInt16- toEncoding x = Encoding 2 (BB.int16BE x)+ toEncoding = BB.word16 . fromIntegral deserialiser = Deserialiser $ Plan $ \case SInt16 -> pure $ fromIntegral <$> word16be s -> unexpectedSchema "Serialise Int16" s@@ -366,7 +368,7 @@ instance Serialise Int32 where schemaVia _ _ = SInt32- toEncoding x = Encoding 4 (BB.int32BE x)+ toEncoding = BB.word32 . fromIntegral deserialiser = Deserialiser $ Plan $ \case SInt32 -> pure $ fromIntegral <$> word32be s -> unexpectedSchema "Serialise Int32" s@@ -374,7 +376,7 @@ instance Serialise Int64 where schemaVia _ _ = SInt64- toEncoding x = Encoding 8 (BB.int64BE x)+ toEncoding = BB.word64 . fromIntegral deserialiser = Deserialiser $ Plan $ \case SInt64 -> pure $ fromIntegral <$> word64be s -> unexpectedSchema "Serialise Int64" s@@ -389,7 +391,7 @@ instance Serialise Float where schemaVia _ _ = SFloat- toEncoding x = Encoding 4 $ BB.word32BE $ unsafeCoerce x+ toEncoding = BB.word32 . unsafeCoerce deserialiser = Deserialiser $ Plan $ \case SFloat -> pure $ unsafeCoerce <$> word32be s -> unexpectedSchema "Serialise Float" s@@ -397,7 +399,7 @@ instance Serialise Double where schemaVia _ _ = SDouble- toEncoding x = Encoding 8 $ BB.word64BE $ unsafeCoerce x+ toEncoding = BB.word64 . unsafeCoerce deserialiser = Deserialiser $ Plan $ \case SDouble -> pure $ unsafeCoerce <$> word64be s -> unexpectedSchema "Serialise Double" s@@ -452,7 +454,7 @@ instance Serialise B.ByteString where schemaVia _ _ = SBytes- toEncoding bs = Encoding (B.length bs) (BB.byteString bs)+ toEncoding = BB.bytes deserialiser = Deserialiser $ Plan $ \case SBytes -> pure id s -> unexpectedSchema "Serialise ByteString" s@@ -463,7 +465,7 @@ Just s -> SArray (VarInt s) (substSchema (Proxy :: Proxy a) ts) toEncoding xs = case constantSize (Proxy :: Proxy a) of Nothing -> encodeVarInt (length xs)- <> encodeMulti (map toEncoding xs)+ <> encodeMulti (\r -> foldr (encodeItem . toEncoding) r xs) Just _ -> encodeVarInt (length xs) <> foldMap toEncoding xs deserialiser = extractListWith deserialiser @@ -528,12 +530,6 @@ toEncoding = toEncoding . toList deserialiser = Seq.fromList <$> deserialiser -instance Serialise a => Serialise (Identity a) where- schemaVia _ = schemaVia (Proxy :: Proxy a)- toEncoding = toEncoding . runIdentity- deserialiser = Identity <$> deserialiser- constantSize _ = constantSize (Proxy :: Proxy a)- -- | Extract a field of a record. extractField :: Serialise a => T.Text -> Deserialiser a extractField = extractFieldWith deserialiser@@ -571,7 +567,7 @@ sa = substSchema (Proxy :: Proxy a) ts sb = substSchema (Proxy :: Proxy b) ts toEncoding (a, b) = case constantSize (Proxy :: Proxy (a, b)) of- Nothing -> encodeMulti [toEncoding a, toEncoding b]+ Nothing -> encodeMulti (encodeItem (toEncoding a) . encodeItem (toEncoding b)) Just _ -> toEncoding a <> toEncoding b deserialiser = Deserialiser $ Plan $ \case SProduct [sa, sb] -> do@@ -597,7 +593,7 @@ sb = substSchema (Proxy :: Proxy b) ts sc = substSchema (Proxy :: Proxy c) ts toEncoding (a, b, c) = case constantSize (Proxy :: Proxy (a, b, c)) of- Nothing -> encodeMulti [toEncoding a, toEncoding b, toEncoding c]+ Nothing -> encodeMulti $ encodeItem (toEncoding a) . encodeItem (toEncoding b) . encodeItem (toEncoding c) Just _ -> toEncoding a <> toEncoding b <> toEncoding c deserialiser = Deserialiser $ Plan $ \case SProduct [sa, sb, sc] -> do@@ -627,7 +623,7 @@ sc = substSchema (Proxy :: Proxy c) ts sd = substSchema (Proxy :: Proxy d) ts toEncoding (a, b, c, d) = case constantSize (Proxy :: Proxy (a, b, c)) of- Nothing -> encodeMulti [toEncoding a, toEncoding b, toEncoding c, toEncoding d]+ Nothing -> encodeMulti $ encodeItem (toEncoding a) . encodeItem (toEncoding b) . encodeItem (toEncoding c) . encodeItem (toEncoding d) Just _ -> toEncoding a <> toEncoding b <> toEncoding c <> toEncoding d deserialiser = Deserialiser $ Plan $ \case SProduct [sa, sb, sc, sd] -> do@@ -653,8 +649,8 @@ instance (Serialise a, Serialise b) => Serialise (Either a b) where schemaVia _ ts = SVariant [("Left", [substSchema (Proxy :: Proxy a) ts]) , ("Right", [substSchema (Proxy :: Proxy b) ts])]- toEncoding (Left a) = Encoding 1 (BB.word8 0) <> toEncoding a- toEncoding (Right b) = Encoding 1 (BB.word8 1) <> toEncoding b+ toEncoding (Left a) = BB.word8 0 <> toEncoding a+ toEncoding (Right b) = BB.word8 1 <> toEncoding b deserialiser = Deserialiser $ Plan $ \case SVariant [(_, [sa]), (_, [sb])] -> do getA <- unwrapDeserialiser deserialiser sa@@ -726,16 +722,17 @@ where rep = "gdeserialiserRecord :: Deserialiser " <> viaShow (typeRep (Proxy :: Proxy a))+{-# INLINE gdeserialiserRecord #-} class GEncodeRecord f where- recordEncoder :: f x -> [Encoding]+ recordEncoder :: f x -> EncodingMulti -> EncodingMulti instance (GEncodeRecord f, GEncodeRecord g) => GEncodeRecord (f :*: g) where- recordEncoder (f :*: g) = recordEncoder f ++ recordEncoder g+ recordEncoder (f :*: g) = recordEncoder f . recordEncoder g {-# INLINE recordEncoder #-} instance Serialise a => GEncodeRecord (S1 c (K1 i a)) where- recordEncoder (M1 (K1 a)) = pure $! toEncoding a+ recordEncoder (M1 (K1 a)) = encodeItem (toEncoding a) {-# INLINE recordEncoder #-} instance GEncodeRecord f => GEncodeRecord (C1 c f) where@@ -774,17 +771,17 @@ class GSerialiseProduct f where productSchema :: proxy f -> [TypeRep] -> [Schema]- productEncoder :: f x -> [Encoding]+ productEncoder :: f x -> EncodingMulti -> EncodingMulti productDecoder :: TransFusion (FieldDecoder ()) Decoder (Decoder (f x)) instance GSerialiseProduct U1 where productSchema _ _ = []- productEncoder _ = []+ productEncoder _ = id productDecoder = pure (pure U1) instance (Serialise a) => GSerialiseProduct (K1 i a) where productSchema _ ts = [substSchema (Proxy :: Proxy a) ts]- productEncoder (K1 a) = [toEncoding a]+ productEncoder (K1 a) = encodeItem (toEncoding a) productDecoder = TransFusion $ \k -> fmap (fmap K1) $ k $ FieldDecoder () Nothing (getDeserialiser deserialiser) instance GSerialiseProduct f => GSerialiseProduct (M1 i c f) where@@ -794,7 +791,7 @@ instance (GSerialiseProduct f, GSerialiseProduct g) => GSerialiseProduct (f :*: g) where productSchema _ ts = productSchema (Proxy :: Proxy f) ts ++ productSchema (Proxy :: Proxy g) ts- productEncoder (f :*: g) = productEncoder f ++ productEncoder g+ productEncoder (f :*: g) = productEncoder f . productEncoder g productDecoder = liftA2 (:*:) <$> productDecoder <*> productDecoder deserialiserProduct' :: GSerialiseProduct f => [Schema] -> Strategy (Decoder (f x))@@ -870,3 +867,7 @@ variantSchema _ ts = variantSchema (Proxy :: Proxy f) ts variantEncoder i (M1 a) = variantEncoder i a variantDecoder = fmap (fmap (fmap (fmap M1))) <$> variantDecoder++instance Serialise Ordering+deriving instance Serialise a => Serialise (Identity a)+deriving instance (Serialise a, Typeable b) => Serialise (Const a (b :: *))
src/Data/Winery/Internal.hs view
@@ -8,8 +8,10 @@ {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeFamilies #-} module Data.Winery.Internal- ( Encoding(..)+ ( Encoding+ , EncodingMulti , encodeMulti+ , encodeItem , encodeVarInt , Decoder , decodeAt@@ -35,10 +37,9 @@ import Control.Monad.Fix import Control.Monad.ST import Control.Monad.Trans.Cont-import Data.ByteString.FastBuilder import qualified Data.ByteString as B import qualified Data.ByteString.Unsafe as B-import qualified Data.ByteString.FastBuilder as BB+import Data.Winery.Internal.Builder import Data.Bits import Data.Dynamic import Data.Monoid@@ -48,16 +49,6 @@ import qualified Data.Vector.Unboxed.Mutable as UM import Data.Word -data Encoding = Encoding- { encodingLength :: !Int, encodingBuilder :: !Builder }--instance Monoid Encoding where- mempty = Encoding 0 mempty- mappend (Encoding m a) (Encoding n b) = Encoding (m + n) (mappend a b)--rebuildEncoding :: Encoding -> Encoding-rebuildEncoding (Encoding l b) = Encoding l (BB.rebuild b)- type Decoder = (->) B.ByteString decodeAt :: (Int, Int) -> Decoder a -> Decoder a@@ -67,17 +58,18 @@ encodeVarInt n | n < 0 = case negate n of n'- | n' < 0x40 -> e1 (fromIntegral n' `setBit` 6)- | otherwise -> go (e1 (0xc0 .|. fromIntegral n')) (shiftR n' 6)- | n < 0x40 = e1 (fromIntegral n)- | otherwise = go (e1 (fromIntegral n `setBit` 7 `clearBit` 6)) (shiftR n 6)- where- e1 = Encoding 1 . BB.word8- go !acc m- | m < 0x80 = acc `mappend` e1 (fromIntegral m)- | otherwise = go (acc <> e1 (setBit (fromIntegral m) 7)) (shiftR m 7)-{-# INLINE encodeVarInt #-}+ | n' < 0x40 -> word8 (fromIntegral n' `setBit` 6)+ | otherwise -> encodesUVarInt (word8 (0xc0 .|. fromIntegral n')) (unsafeShiftR n' 6)+ | n < 0x40 = word8 (fromIntegral n)+ | otherwise = encodesUVarInt (word8 (fromIntegral n `setBit` 7 `clearBit` 6)) (unsafeShiftR n 6)+{-# SPECIALISE encodeVarInt :: Int -> Encoding #-} +encodesUVarInt :: (Bits a, Integral a) => Encoding -> a -> Encoding+encodesUVarInt !acc m+ | m < 0x80 = acc `mappend` word8 (fromIntegral m)+ | otherwise = encodesUVarInt (acc <> word8 (setBit (fromIntegral m) 7)) (unsafeShiftR m 7)+{-# SPECIALISE encodesUVarInt :: Encoding -> Int -> Encoding #-}+ getWord8 :: ContT r Decoder Word8 getWord8 = ContT $ \k bs -> case B.uncons bs of Nothing -> k 0 bs@@ -89,15 +81,15 @@ n | testBit n 7 -> do m <- getWord8 >>= go if testBit n 6- then return $! negate $ shiftL m 6 .|. fromIntegral n .&. 0x3f- else return $! shiftL m 6 .|. clearBit (fromIntegral n) 7+ then return $! negate $ unsafeShiftL m 6 .|. fromIntegral n .&. 0x3f+ else return $! unsafeShiftL m 6 .|. clearBit (fromIntegral n) 7 | testBit n 6 -> return $ negate $ fromIntegral $ clearBit n 6 | otherwise -> return $ fromIntegral n where go n | testBit n 7 = do m <- getWord8 >>= go- return $! shiftL m 7 .|. clearBit (fromIntegral n) 7+ return $! unsafeShiftL m 7 .|. clearBit (fromIntegral n) 7 | otherwise = return $ fromIntegral n {-# INLINE decodeVarInt #-} @@ -130,14 +122,20 @@ (fromIntegral (s `B.unsafeIndex` 7) ) else error $ "word64be" ++ show s -encodeMulti :: [Encoding] -> Encoding-encodeMulti xs = rebuildEncoding- $ foldMap (encodeVarInt . encodingLength) (safeInit xs) <> mconcat xs- where- safeInit [] = []- safeInit [_] = []- safeInit (y:ys) = y : safeInit ys+data EncodingMulti = EncodingMulti0+ | EncodingMulti !Encoding !Encoding++encodeMulti :: (EncodingMulti -> EncodingMulti) -> Encoding+encodeMulti f = case f EncodingMulti0 of+ EncodingMulti0 -> mempty+ EncodingMulti r s -> mappend r s {-# INLINE encodeMulti #-}++encodeItem :: Encoding -> EncodingMulti -> EncodingMulti+encodeItem e EncodingMulti0 = EncodingMulti mempty e+encodeItem e (EncodingMulti a b) = EncodingMulti+ (mappend (encodeVarInt (getSize e)) a) (mappend e b)+{-# INLINE encodeItem #-} type Offsets = U.Vector (Int, Int)
+ src/Data/Winery/Internal/Builder.hs view
@@ -0,0 +1,89 @@+{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, BangPatterns #-}+module Data.Winery.Internal.Builder+ ( Encoding+ , getSize+ , toByteString+ , word8+ , word16+ , word32+ , word64+ , bytes+ ) where++import qualified Data.ByteString as B+import qualified Data.ByteString.Internal as B+import Data.Word+import Foreign.Ptr+import Foreign.ForeignPtr+import Foreign.Storable+import System.IO.Unsafe+import System.Endian++data Encoding = Encoding {-# UNPACK #-}!Int !Tree+ | Empty++data Tree = Bin Tree Tree+ | LWord8 {-# UNPACK #-} !Word8+ | LWord16 {-# UNPACK #-} !Word16+ | LWord32 {-# UNPACK #-} !Word32+ | LWord64 {-# UNPACK #-} !Word64+ | LBytes !B.ByteString++instance Monoid Encoding where+ mempty = Empty+ {-# INLINE mempty #-}+ mappend Empty a = a+ mappend a Empty = a+ mappend (Encoding s a) (Encoding t b) = Encoding (s + t) (Bin a b)+ {-# INLINE mappend #-}++getSize :: Encoding -> Int+getSize Empty = 0+getSize (Encoding s _) = s+{-# INLINE getSize #-}++toByteString :: Encoding -> B.ByteString+toByteString Empty = B.empty+toByteString (Encoding len tree) = unsafeDupablePerformIO $ do+ fp <- B.mallocByteString len+ withForeignPtr fp $ \ptr -> do+ let copyBS ofs (B.PS fp' sofs len') = withForeignPtr fp'+ $ \src -> B.memcpy (ptr `plusPtr` ofs) (src `plusPtr` sofs) len'+ let go :: Int -> Tree -> IO ()+ go ofs l = case l of+ LWord8 w -> pokeByteOff ptr ofs w+ LWord16 w -> pokeByteOff ptr ofs $ toBE16 w+ LWord32 w -> pokeByteOff ptr ofs $ toBE32 w+ LWord64 w -> pokeByteOff ptr ofs $ toBE64 w+ LBytes bs -> copyBS ofs bs+ Bin a b -> rotate ofs a b++ rotate :: Int -> Tree -> Tree -> IO ()+ rotate ofs (LWord8 w) t = pokeByteOff ptr ofs w >> go (ofs + 1) t+ rotate ofs (LWord16 w) t = pokeByteOff ptr ofs (toBE16 w) >> go (ofs + 2) t+ rotate ofs (LWord32 w) t = pokeByteOff ptr ofs (toBE32 w) >> go (ofs + 4) t+ rotate ofs (LWord64 w) t = pokeByteOff ptr ofs (toBE64 w) >> go (ofs + 8) t+ rotate ofs (LBytes bs) t = copyBS ofs bs >> go (ofs + B.length bs) t+ rotate ofs (Bin c d) t = rotate ofs c (Bin d t)+ go 0 tree+ return (B.PS fp 0 len)++word8 :: Word8 -> Encoding+word8 = Encoding 1 . LWord8+{-# INLINE word8 #-}++word16 :: Word16 -> Encoding+word16 = Encoding 2 . LWord16+{-# INLINE word16 #-}++word32 :: Word32 -> Encoding+word32 = Encoding 4 . LWord32+{-# INLINE word32 #-}++word64 :: Word64 -> Encoding+word64 = Encoding 8 . LWord64+{-# INLINE word64 #-}++bytes :: B.ByteString -> Encoding+bytes bs = Encoding (B.length bs) $ LBytes bs+{-# INLINE bytes #-}
winery.cabal view
@@ -2,10 +2,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: cd7f7da4332784c7d2fa7dab5a2c1c418d2f8c00ede5bbb3c1b560a6323fb4a6+-- hash: 750a23f7c3318207ce7adf5a4637dd1630f0e706c5efab6184ecbb0fc4b4882e name: winery-version: 0.1+version: 0.1.1 synopsis: Sustainable serialisation library description: Please see the README on Github at <https://github.com/fumieval/winery#readme> category: Data@@ -35,7 +35,8 @@ base >=4.7 && <5 , bytestring , containers- , fast-builder+ , cpu+ , fingertree , hashable , mtl , prettyprinter@@ -49,6 +50,7 @@ Data.Winery.Term Data.Winery.Internal other-modules:+ Data.Winery.Internal.Builder Paths_winery default-language: Haskell2010 @@ -60,7 +62,8 @@ base >=4.7 && <5 , bytestring , containers- , fast-builder+ , cpu+ , fingertree , hashable , mtl , prettyprinter@@ -83,7 +86,8 @@ base >=4.7 && <5 , bytestring , containers- , fast-builder+ , cpu+ , fingertree , hashable , mtl , prettyprinter@@ -109,8 +113,9 @@ , bytestring , cassava , containers+ , cpu , deepseq- , fast-builder+ , fingertree , gauge , hashable , mtl