• Stars
    star
    122
  • Rank 292,031 (Top 6 %)
  • Language
    Haskell
  • License
    Mozilla Public Li...
  • Created over 6 years ago
  • Updated 5 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

๐Ÿ Bidirectional TOML serialization

tomland

palm

GitHub CI Hackage MPL-2.0 license

โ€œ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

tomland is a Haskell library for Bidirectional TOML Serialization. It provides the composable interface for implementing 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.

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

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

cabal run readme

Preamble: imports and language extensions

Since this is a literate haskell file, we need to specify all our language extensions and imports up front.

{-# OPTIONS -Wno-unused-top-binds #-}

{-# LANGUAGE LambdaCase        #-}
{-# LANGUAGE OverloadedStrings #-}

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

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

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

import Toml (TomlCodec, (.=))  -- add 'TomlBiMap' and 'Key' here optionally
import qualified Toml

Data type: parsing and printing

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

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

[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:

data Settings = Settings
    { settingsPort        :: !Port
    , settingsDescription :: !Text
    , settingsCodes       :: [Int]
    , settingsMail        :: !Mail
    , settingsUsers       :: ![User]
    }

data Mail = Mail
    { mailHost           :: !Host
    , mailSendIfInactive :: !Bool
    }

data User
    = 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 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 primitive types like Int or Text you can just use standard codecs like Toml.int and Toml.text.
  2. If you want to parse newtypes, use Toml.diwrap to wrap parsers for underlying newtype representation.
  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 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 sets are represented as array of tables in TOML.
  6. For parsing sum types, use Toml.dimatch. This requires writing matching functions for the constructors of the sum type.
  7. tomland separates conversion between Haskell types and TOML values from matching values by keys. Converters between types and values have type TomlBiMap and are named with capital letter started with underscore. Main type for TOML codecs is called TomlCodec. To lift TomlBiMap to TomlCodec you need to use Toml.match function.
settingsCodec :: TomlCodec Settings
settingsCodec = Settings
    <$> Toml.diwrap (Toml.int  "server.port")       .= settingsPort
    <*> Toml.text              "server.description" .= settingsDescription
    <*> Toml.arrayOf Toml._Int "server.codes"       .= settingsCodes
    <*> Toml.table mailCodec   "mail"               .= settingsMail
    <*> Toml.list  userCodec   "user"               .= settingsUsers

mailCodec :: TomlCodec Mail
mailCodec = Mail
    <$> Toml.diwrap (Toml.text "host") .= mailHost
    <*> Toml.bool "send-if-inactive"   .= mailSendIfInactive

matchGuest :: User -> Maybe Integer
matchGuest = \case
   Guest i -> Just i
   _ -> Nothing

matchRegistered :: User -> Maybe RegisteredUser
matchRegistered = \case
   Registered u -> Just u
   _ -> Nothing

userCodec :: TomlCodec User
userCodec =
        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 are ready to parse our TOML and print the result back to see whether everything is okay.

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

Benchmarks and comparison with other libraries

You can find benchmarks of the tomland library in the following repository:

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
tomland 305.5 ฮผs 1.280 ฮผs
htoml 852.8 ฮผs 33.37 ฮผs
htoml-megaparsec 295.0 ฮผs 33.62 ฮผs
toml-parser 164.6 ฮผs 1.101 ฮผs

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 from www.flaticon.com is licensed by CC 3.0 BY.

More Repositories

1

learn4haskell

๐Ÿ‘ฉโ€๐Ÿซ ๐Ÿ‘จโ€๐Ÿซ Learn Haskell basics in 4 pull requests
Haskell
992
star
2

relude

๐ŸŒ€ Safe, performant, user-friendly and lightweight Haskell standard library
Haskell
698
star
3

summoner

๐Ÿ”ฎ ๐Ÿ”ง Tool for scaffolding batteries-included production-level Haskell projects
Haskell
695
star
4

stan

๐Ÿ•ต๏ธ Haskell STatic ANalyser
Haskell
565
star
5

cake-slayer

๐Ÿฐ๐Ÿ”ช Architecture of Haskell backend applications
Haskell
131
star
6

awesome-cabal

๐Ÿ’ซ A curated list of awesome resources for the Haskell Cabal build tool.
118
star
7

typerep-map

โšก๏ธEfficient implementation of Map with types as keys
Haskell
100
star
8

hit-on

:octocat: Kowainik Git Workflow Helper Tool
Haskell
76
star
9

prolens

๐Ÿ‘“ Profunctor based lightweight implementation of Lenses
Haskell
74
star
10

smuggler

๐Ÿšฃ Smuggle all imports
Haskell
71
star
11

shellmet

๐Ÿš Out of the shell solution for scripting in Haskell
Haskell
70
star
12

policeman

๐Ÿ‘ฎ Haskell PVP adviser
Haskell
69
star
13

validation-selective

๐Ÿ’‚โ€โ™‚๏ธ Lightweight pure validation based on Applicative and Selective functors
Haskell
66
star
14

colourista

โ€Ž๏ธโ€๐ŸŒˆ Convenient interface for printing colourful messages
Haskell
66
star
15

treap

๐Ÿƒ ๐ŸŒณ ๐Ÿ‚ Efficient implementation of the implicit treap data structure
Haskell
63
star
16

membrain

๐Ÿง  Type-safe memory units
Haskell
61
star
17

issue-wanted

๐Ÿท Web application to help beginners to start contributing into Haskell projects
Haskell
59
star
18

eio

๐ŸŽฏ IO with Exceptions tracked on the type-level
Haskell
58
star
19

type-errors-pretty

๐Ÿ’„๐Ÿž Combinators for writing pretty type errors easily
Haskell
55
star
20

extensions

๐Ÿ‘… Parse Haskell Language Extensions
Haskell
48
star
21

awesome-haskell-sponsorship

๐Ÿ’ Haskell profiles to sponsor
47
star
22

slist

โ™พ๏ธ Sized list
Haskell
46
star
23

autopack

๐Ÿ“ฆ Custom Setup to automate package modules discovery
Haskell
32
star
24

containers-backpack

๐ŸŽ’ Backpack interface for containers
Haskell
32
star
25

kowainik.github.io

๐ŸŽ‚ Kowainik web page
HTML
30
star
26

hintman

๐Ÿ”ซ GitHub application to suggest hints
Haskell
27
star
27

unlift

๐Ÿ›— Typeclass for monads that can be unlifted to arbitrary base monads
Haskell
25
star
28

idris-patricia

๐ŸŒ‹ Idris implementation of patricia tree
Idris
22
star
29

org

๐Ÿ“œ ๐Ÿ“’ Place for organization guidelines and workflows
Mustache
21
star
30

life-sync

๐Ÿ”„ Synchronize personal configs across multiple machines
Haskell
21
star
31

trial

โš–๏ธ Trial Data Type
Haskell
20
star
32

piece-of-cake-slayer

๐Ÿฐ๐ŸดTemplate project based on the cake-slayer architecture library
Haskell
19
star
33

first-class-patterns

First class patterns and pattern matching, using type families
Haskell
17
star
34

hakyll-shortcut-links

โœ‚๏ธ Hakyll shortcut-links in markdown files
Haskell
11
star
35

crocodealer

๐ŸŠ Manage GitHub organization files, labels, issues
Haskell
9
star
36

amicabal

๐Ÿฅฐ Friendly Haskell config format (cabal) helper and linter
Haskell
9
star
37

shortcut-links

๐Ÿ–‡๏ธ Link shortcuts for use in text markup
Haskell
8
star
38

github-graphql

๐Ÿ•ธ๏ธ GraphQL bindings to GitHub API
Haskell
7
star
39

.github

๐Ÿ’Š Default health files
7
star
40

seaweed

๐ŸŒŠ Create your fancy CV in different formats
Haskell
5
star
41

toml-benchmarks

๐Ÿ“Š Benchmarks for Haskell TOML decoding and encoding libraries
Haskell
4
star
42

outdator

Haskell outdated dependencies bot
Haskell
3
star
43

stan-action

GitHub Action for Stan โ€“โ€“ Haskell Static Analysis tool
Dockerfile
3
star
44

hintman-target

Target dummy for hintman
Haskell
3
star
45

hash-store

Hash as cache
Haskell
2
star
46

treasure-keeper

๐Ÿ’ฐ Accounting tool
Haskell
2
star
47

ppa

Ubuntu PPAs for Kowainik tools
Makefile
1
star
48

stack-full

See README for more info
Haskell
1
star
49

tomlerone

๐Ÿ—ป Tomland Online: TOML format online checker based on tomland library
Haskell
1
star
50

mysql-not-so-simple

MySQL not so simple
Haskell
1
star
51

cabal-full

See README for more info
Haskell
1
star
52

app-version

Get your application version
Haskell
1
star