tomland
 
 
 
 
 

“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:
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
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 Control.Category ((>>>))
import Data.Text (Text)
import Toml (TomlBiMap, TomlCodec, (.=))
import qualified Data.Text.IO as TIO
import qualified Toml
tomland is mostly 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.
This static configuration is captured by the following Haskell data type:
data Settings = Settings
    { settingsPort        :: !Port
    , settingsDescription :: !Text
    , settingsCodes       :: [Int]
    , settingsMail        :: !Mail
    , settingsUsers       :: ![User]
    }
data Mail = Mail
    { mailHost           :: !Host
    , mailSendIfInactive :: !Bool
    }
data User
    = Admin  Integer  -- id of admin
    | Client Text     -- name of the client
    deriving (Show)
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:
- If your fields are some simple basic types like IntorTextyou can just
use standard codecs likeToml.intandToml.text.
- If you want to parse newtypes, useToml.diwrapto wrap parsers for
underlyingnewtyperepresentation.
- For parsing nested data types, use Toml.table. But this requires to specify
this data type as TOML table in.tomlfile.
- 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
likeInt,Bool,Double,Textor time types, that you can useToml.arrayOfand parse arrays of values.
- tomlandseparates conversion between Haskell types and TOML values from
matching values by keys. Converters between types and values have type- TomlBiMapand are named with capital letter started with underscore. Main
type for TOML codecs is called- TomlCodec. To lift- TomlBiMapto- TomlCodecyou need to use- Toml.matchfunction.
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
_Admin :: TomlBiMap User Integer
_Admin = Toml.prism Admin $ \case
    Admin i -> Right i
    other   -> Toml.wrongConstructor "Admin" other
_Client :: TomlBiMap User Text
_Client = Toml.prism Client $ \case
    Client n -> Right n
    other    -> Toml.wrongConstructor "Client" other
userCodec :: TomlCodec User
userCodec =
        Toml.match (_Admin >>> Toml._Integer) "id"
    <|> Toml.match (_Client >>> Toml._Text) "name"
And now we're ready to parse our TOML and print the result back to see whether
everything is okay.
main :: IO ()
main = do
    tomlExample <- TIO.readFile "examples/readme.toml"
    let res = Toml.decode settingsCodec tomlExample
    case res of
        Left err -> print err
        Right settings -> TIO.putStrLn $ Toml.encode settingsCodec settings
Benchmarks and comparison with other libraries
tomland is compared 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.
| Library | parse :: Text -> AST | transform :: AST -> Haskell | 
| tomland | 387.5 μs | 1.313 μs | 
| htoml | 801.2 μs | 32.54 μs | 
| htoml-megaparsec | 318.7 μs | 34.74 μs | 
| toml-parser | 157.2 μs | 1.156 μs | 
You may see that tomland is not the fastest one (though still very fast). But
performance hasn’t been optimized so far and:
- toml-parserdoesn’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.
- tomlandsupports latest TOML spec while- htomland- htoml-megaparsecdon’t have support for all types, values and formats.
- tomlandis the only library that has pretty-printing.
- toml-parserdoesn’t have ways to convert TOML AST to custom Haskell types
and- htoml*libraries use typeclasses-based approach via- aesonlibrary.
- tomlandis bidirectional 🙂
Acknowledgement
Icons made by Freepik from
www.flaticon.com is licensed by
CC 3.0 BY.