# winery ![logo](https://github.com/fumieval/winery/blob/master/art/logo256px.png?raw=true) ![Haskell CI](https://github.com/fumieval/winery/workflows/Haskell%20CI/badge.svg) [![Hackage](https://img.shields.io/hackage/v/winery.svg)](https://hackage.haskell.org/package/winery) [![Discord](https://img.shields.io/discord/664807830116892674?color=%237095ec&label=Discord&style=plastic)](https://discord.gg/DG93Tgs) winery is a serialisation library focusing on __performance__, __compactness__ and __compatibility__. The primary feature is that metadata (types, field names, etc) are packed into one schema. A number of formats, like JSON and CBOR, attach metadata for each value: `[{"id": 0, "name": "Alice"}, {"id": 1, "name": "Bob"}]` In contrast, winery stores them separately, eliminating redundancy while guaranteeing well-typedness: ``` 0402 0402 0269 6410 046e 616d 6514 [{ id :: Integer, name :: Text }] 0200 0541 6c69 6365 0103 426f 62 [(0, "Alice"), (1, "Bob")] ``` Unlike other libraries that don't preserve metadata (e.g.` binary`, `cereal`, `store`) at all, winery also allows readers to decode values regardless of the current implementation. ## Interface The interface is simple; `serialise` encodes a value with its schema, and `deserialise` decodes a ByteString using the schema in it. ```haskell class Serialise a where schema :: Serialise a => proxy a -> Schema serialise :: Serialise a => a -> B.ByteString deserialise :: Serialise a => B.ByteString -> Either WineryException a ``` It's also possible to serialise schemata and data separately. `serialiseSchema` encodes a schema and its version number into a ByteString, and `serialiseOnly` serialises a value without a schema. ```haskell serialiseSchema :: Schema -> B.ByteString serialiseOnly :: Serialise a => a -> B.ByteString ``` In order to decode data generated this way, pass the result of `deserialiseSchema` to `getDecoder`. Finally run `evalDecoder` to deserialise them. ```haskell deserialiseSchema :: B.ByteString -> Either WineryException Schema getDecoder :: Serialise a => Schema -> Either WineryException (Decoder a) evalDecoder :: Decoder a -> B.ByteString -> a ``` ## Deriving an instance The recommended way to create an instance of `Serialise` is to use `DerivingVia`. ```haskell deriving Generic deriving Serialise via WineryRecord Foo ``` for single-constructor records, or just ```haskell deriving Generic deriving Serialise via WineryVariant Foo ``` for any ADT. The former explicitly describes field names in the schema, and the latter does constructor names. If you want to customise one of the methods, you can use `bundleVia` to supply the rest of definitions. ```haskell instance Serialise Foo where bundleSerialise = bundleVia WineryRecord extractor = buildExtractor $ Foo <$> extractField "foo" <*> extractField "bar" ``` ## Backward compatibility If the representation is not the same as the current version (i.e. the schema is different), the data cannot be decoded directly. This is where extractors come in. `Extractor` parses a schema and returns a function which gives a value back from a `Term`. You can build an extractor using combinators such as `extractField`, `extractConstructor`, etc. ```haskell buildExtractor $ ("None", \() -> Nothing) `extractConstructor` ("Some", Just) `extractConstructor` extractVoid :: Extractor (Maybe a) ``` If you want to customise the extractor, the pair of `gvariantExtractors` and `buildVariantExtractors` is handy. ```haskell gvariantExtractors :: (GSerialiseVariant (Rep a), Generic a) => HM.HashMap T.Text (Extractor a) buildVariantExtractor :: (Generic a, Typeable a) => HM.HashMap T.Text (Extractor a) -> Extractor a ``` `Extractor` is Alternative, meaning that multiple extractors (such as a default generic implementation and fallback plans) can be combined into one. Altering an instance for a record type is a little bit tricky. HKD can represent a record where each field is `Subextractor` instead of the orignal type. The [barbies-th](http://hackage.haskell.org/package/barbies-th) allows us to derive it from a plain declaration. ```haskell import Barbies.Bare import Barbies.TH declareBareB [d| data HRecB = HRec { baz :: !Int , qux :: !Text } |] type HRec = HRecB Bare Identity ``` Obtain a record of extractors using `bextractors :: forall b. (AllB Serialise b, ...) => b Subextractor`, update it as necessary, then build an extractor for an entire record by `buildRecordExtractor`. ```haskell instance Serialise HRec where bundleSerialise = bundleVia WineryRecord extractor = fmap bstrip $ buildRecordExtractor bextractors { qux = extractField "qux" <|> extractField "oldQux" } ``` More generic instance (for covered types) can be defined as below: ```haskell instance (Typeable h, AllBF Serialise h (HRecB Covered)) => Serialise (HRecB Covered h) where bundleSerialise = bundleVia Barbie extractor = buildRecordExtractorF bextractorsF { qux = Compose $ extractField "qux" <|> extractField "oldQux" } ``` ## Pretty-printing `Term` can be deserialised from any winery data. It can be pretty-printed using the `Pretty` instance: ``` { bar: "hello" , baz: 3.141592653589793 , foo: Just 42 } ``` You can use the `winery` command-line tool to inspect values. ``` $ winery '.[:10] | .first_name .last_name' benchmarks/data.winery Shane Plett Mata Snead Levon Sammes Irina Gourlay Brooks Titlow Antons Culleton Regine Emerton Starlin Laying Orv Kempshall Elizabeth Joseff Cathee Eberz ``` At the moment, the following queries are supported: * `.` return itself * `.[]` enumerate all the elements in a list * `.[i]` get the i-th element * `.[i:j]` enumerate i-th to j-th items * `.N` n-th element of a product * `.foo` Get a field named `foo` * `F | G` compose queries (left to right) ## Performance A useful library should also be fast. Benchmarking encoding/decoding of the following datatype. ```haskell data Gender = Male | Female data TestRec = TestRec { id_ :: !Int , first_name :: !Text , last_name :: !Text , email :: !Text , gender :: !Gender , num :: !Int , latitude :: !Double , longitude :: !Double } ``` Here's the result: | | encode 1 | encode 1000 | decode | length | |-----------|----------|-------------|---------| ------- | | winery | __0.28 μs__ | __0.26 ms__ | __0.81 ms__ | __58662__ | | cereal | 0.82 μs | 0.78 ms | 0.90 ms | 91709 | | binary | 1.7 μs | 1.7 ms | 2.0 ms | 125709 | | serialise | 0.61 μs | 0.50 ms | 1.4 ms | 65437 | | store | 54 ns | 56 μs | 0.13 ms | 126410 | | aeson | 9.9 μs | 9.7 ms | 17 ms | 160558 |