Skip to content

Commit

Permalink
Improve wording in README (#324)
Browse files Browse the repository at this point in the history
* Improve wording in README

* Apply suggestions from code review

Co-authored-by: Veronika Romashkina <[email protected]>

Co-authored-by: Veronika Romashkina <[email protected]>
  • Loading branch information
chshersh and vrom911 authored May 19, 2020
1 parent 7d10644 commit 316c893
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 55 deletions.
153 changes: 102 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,30 @@
> “A library is like an island in the middle of a vast sea of ignorance,
> particularly if the library is very tall and the surrounding area has been
> flooded.”
>
> ― Lemony Snicket, Horseradish
Bidirectional TOML serialization. The following blog post has more details about
library design:
`tomland` is a Haskell library for _Bidirectional TOML
Serialization_. It provides the composable interface for implementing
[TOML](https://github.com/toml-lang/toml) codecs. If you want to use
TOML as a configuration for your tool or application, you can use
`tomland` to easily convert in both ways between textual TOML
representation and Haskell types.

✍️ `tomland` supports [TOML spec version 0.5.0](https://github.com/toml-lang/toml/wiki#v050-compliant).

The following blog post has more details about the library design and
internal implementation details:

* [`tomland`: Bidirectional TOML serialization](https://kowainik.github.io/posts/2019-01-14-tomland)
* [`tomland`: Bidirectional TOML Serialization](https://kowainik.github.io/posts/2019-01-14-tomland)

This README contains a basic usage example of the `tomland` library. All code
below can be compiled and run with the following command:

```
cabal new-run readme
cabal run readme
```

> `tomland` supports TOML spec version 0.5.0.
## Preamble: imports and language extensions

Since this is a literate haskell file, we need to specify all our language
Expand All @@ -41,15 +48,15 @@ extensions and imports up front.
{-# LANGUAGE OverloadedStrings #-}

import Control.Applicative ((<|>))
import Control.Category ((>>>))
import Data.Text (Text)
import Toml (TomlBiMap, TomlCodec, (.=))
import Data.Time (Day)
import Toml (TomlCodec, (.=))

import qualified Data.Text.IO as TIO
import qualified Toml
```

`tomland` is mostly designed for qualified imports and intended to be imported
`tomland` is designed for qualified imports and intended to be imported
as follows:

```haskell ignore
Expand All @@ -59,9 +66,39 @@ import qualified Toml

## Data type: parsing and printing

We're going to parse TOML configuration from [`examples/readme.toml`](examples/readme.toml) file.
We're going to parse TOML configuration from
[`examples/readme.toml`](examples/readme.toml) file. The configuration
contains the following description of our data:

```toml
server.port = 8080
server.codes = [ 5, 10, 42 ]
server.description = """
This is production server.
Don't touch it!
"""

This static configuration is captured by the following Haskell data type:
[mail]
host = "smtp.gmail.com"
send-if-inactive = false

[[user]]
guestId = 42

[[user]]
guestId = 114

[[user]]
login = "Foo Bar"
createdAt = 2020-05-19
```

The above static configuration describes `Settings` for some
server. It has several top-level fields, a table with the name `mail`
and an array of tables with the name `user` that stores list of
different types of users.

We can model such TOML using the following Haskell data types:

```haskell
data Settings = Settings
Expand All @@ -78,25 +115,29 @@ data Mail = Mail
}

data User
= Admin !Integer -- id of admin
| Client !Text -- name of the client
deriving stock (Show)
= Guest !Integer -- id of guest
| Registered !RegisteredUser -- login and createdAt of registered user

data RegisteredUser = RegisteredUser
{ registeredUserLogin :: !Text
, registeredUserCreatedAt :: !Day
}

newtype Port = Port Int
newtype Host = Host Text
```

Using `tomland` library, you can write bidirectional converters for these types
using the following guidelines and helper functions:
Using the `tomland` library, you can write bidirectional converters for these types
with the following guidelines and helper functions:

1. If your fields are some simple basic types like `Int` or `Text` you can just
1. If your fields are some simple primitive types like `Int` or `Text` you can just
use standard codecs like `Toml.int` and `Toml.text`.
2. If you want to parse `newtype`s, use `Toml.diwrap` to wrap parsers for
underlying `newtype` representation.
3. For parsing nested data types, use `Toml.table`. But this requires to specify
this data type as TOML table in `.toml` file.
3. For parsing nested data types, use `Toml.table`. But it requires to specify
this data type as TOML table in the `.toml` file.
4. If you have lists of custom data types, use `Toml.list`. Such lists are
represented as array of tables in TOML. If you have lists of primitive types
represented as _array of tables_ in TOML. If you have lists of the primitive types
like `Int`, `Bool`, `Double`, `Text` or time types, that you can use
`Toml.arrayOf` and parse arrays of values.
5. If you have sets of custom data types, use `Toml.set` or `Toml.HashSet`. Such
Expand All @@ -123,39 +164,48 @@ mailCodec = Mail
<$> Toml.diwrap (Toml.text "host") .= mailHost
<*> Toml.bool "send-if-inactive" .= mailSendIfInactive

_Admin :: TomlBiMap User Integer
_Admin = Toml.prism Admin $ \case
Admin i -> Right i
other -> Toml.wrongConstructor "Admin" other
matchGuest :: User -> Maybe Integer
matchGuest = \case
Guest i -> Just i
_ -> Nothing

_Client :: TomlBiMap User Text
_Client = Toml.prism Client $ \case
Client n -> Right n
other -> Toml.wrongConstructor "Client" other
matchRegistered :: User -> Maybe RegisteredUser
matchRegistered = \case
Registered u -> Just u
_ -> Nothing

userCodec :: TomlCodec User
userCodec =
Toml.match (_Admin >>> Toml._Integer) "id"
<|> Toml.match (_Client >>> Toml._Text) "name"
Toml.dimatch matchGuest Guest (Toml.integer "guestId")
<|> Toml.dimatch matchRegistered Registered registeredUserCodec

registeredUserCodec :: TomlCodec RegisteredUser
registeredUserCodec = RegisteredUser
<$> Toml.text "login" .= registeredUserLogin
<*> Toml.day "createdAt" .= registeredUserCreatedAt
```

And now we're ready to parse our TOML and print the result back to see whether
And now we are ready to parse our TOML and print the result back to see whether
everything is okay.

```haskell
main :: IO ()
main = do
tomlRes <- Toml.decodeFileEither settingsCodec "examples/readme.toml"
case tomlRes of
Left err -> print err
Left errs -> TIO.putStrLn $ Toml.prettyTomlDecodeErrors errs
Right settings -> TIO.putStrLn $ Toml.encode settingsCodec settings
```

## Benchmarks and comparison with other libraries

`tomland` is [compared](https://github.com/kowainik/toml-benchmarks) with other libraries. Since it uses 2-step approach with
converting text to intermediate AST and only then decoding Haskell type from
this AST, benchmarks are also implemented in a way to reflect this difference.
You can find benchmarks of the `tomland` library in the following repository:

* [kowainik/toml-benchmarks](https://github.com/kowainik/toml-benchmarks)

Since `tomland` uses 2-step approach with converting text to
intermediate AST and only then decoding Haskell type from this AST,
benchmarks are also implemented in a way to reflect this difference.

| Library | parse :: Text -> AST | transform :: AST -> Haskell |
|--------------------|----------------------|-----------------------------|
Expand All @@ -164,21 +214,22 @@ this AST, benchmarks are also implemented in a way to reflect this difference.
| `htoml-megaparsec` | `295.0 μs` | `33.62 μs` |
| `toml-parser` | `164.6 μs` | `1.101 μs` |

You may see that `tomland` is not the fastest one (though still very fast). But
performance hasn’t been optimized so far and:

1. `toml-parser` doesn’t support the array of tables and because of that it’s
hardly possible to specify the list of custom data types in TOML with this
library.
2. `tomland` supports latest TOML spec while `htoml` and `htoml-megaparsec`
don’t have support for all types, values and formats.
3. `tomland` is the only library that has pretty-printing.
4. `toml-parser` doesn’t have ways to convert TOML AST to custom Haskell types
and `htoml*` libraries use typeclasses-based approach via `aeson` library.
5. `tomland` is bidirectional :slightly_smiling_face:
In addition to the above numbers, `tomland` has several features that
make it unique:

1. `tomland` is the only Haskell library that has pretty-printing.
2. `tomland` is compatible with the latest TOML spec while other libraries are not.
3. `tomland` is bidirectional, which means that your encoding and
decoding are consistent with each other by construction.
4. `tomland` provides abilities for `Generic` and `DerivingVia`
deriving out-of-the-box.
5. Despite being the fastest, `toml-parser` doesn’t support the array
of tables and because of that it’s hardly possible to specify the list
of custom data types in TOML with this library. In addition,
`toml-parser` doesn’t have ways to convert TOML AST to custom
Haskell types and `htoml*` libraries use typeclasses-based approach
via `aeson` library.

## Acknowledgement

Icons made by [Freepik](http://www.freepik.com) from
[www.flaticon.com](https://www.flaticon.com/) is licensed by
[CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/).
Icons made by [Freepik](http://www.freepik.com) from [www.flaticon.com](https://www.flaticon.com/) is licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/).
8 changes: 6 additions & 2 deletions examples/readme.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ Don't touch it!
send-if-inactive = false

[[user]]
id = 42
guestId = 42

[[user]]
name = "Foo Bar"
guestId = 114

[[user]]
login = "Foo Bar"
createdAt = 2020-05-19
5 changes: 3 additions & 2 deletions tomland.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,9 @@ executable readme
if os(windows)
buildable: False
main-is: README.lhs
build-depends: text
, tomland
build-depends: tomland
, text
, time

build-tool-depends: markdown-unlit:markdown-unlit
ghc-options: -pgmL markdown-unlit
Expand Down

0 comments on commit 316c893

Please sign in to comment.