# trial [![GitHub CI](https://github.com/kowainik/trial/workflows/CI/badge.svg)](https://github.com/kowainik/trial/actions) [![Hackage](https://img.shields.io/hackage/v/trial.svg?logo=haskell)](https://hackage.haskell.org/package/trial) [![MPL-2.0 license](https://img.shields.io/badge/license-MPL--2.0-blue.svg)](LICENSE) The `Trial` Data Structure is a `Either`-like structure that keeps events history inside. The data type allows to keep track of the `Fatality` level of each such event entry (`Warning` or `Error`). ## Project Structure This is a multi-package project that has the following packages inside: | Package | Description | |------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------| | `trial` | The main package that contains the `Trial` data structure, instances and useful functions to work with the structure. | | `trial-optparse-applicative` | `Trial` structure integration with the [`optparse-applicative`](https://hackage.haskell.org/package/optparse-applicative) library for Command Line Interface. | | `trial-tomland` | `Trial` structure integration with the [`tomland`](https://hackage.haskell.org/package/tomland) library for TOML configurations. | | `trial-example` | Example project with the usage example of the `Trial` data structure. | ## How to use `trial` `trial` is compatible with the latest GHC versions starting from `8.6.5`. In order to start using `trial` in your project, you will need to set it up with the three easy steps: 1. Add the dependency on `trial` in your project's `.cabal` file. For this, you should modify the `build-depends` section by adding the name of this library. After the adjustment, this section could look like this: ```haskell build-depends: base ^>= 4.14 , trial ^>= 0.0 ``` 2. In the module where you plan to use `Trial`, you should add the import: ```haskell import Trial (Trial (..), fiasco, prettyPrintTrial) ``` 3. Now you can use the types and functions from the library: ```haskell main :: IO () main = putStrLn $ prettyPrintTrial $ fiasco "This is fiasco, bro!" ``` ## Trial Data Structure Let's have a closer look at the `Trial` data structure. `Trial` is a sum type that has two constructors: - `Fiasco` — represents the unsuccessful state similar to the `Left` constructor of `Either`. However, unlike `Left`, `Fiasco` holds a list of all `error`-like items that happened along the way. Each such item has a notion of `Fatality` (the severity of the error). The following cases cover `Fatality`: + `Error` — fatal error that led to the final fatal `Fiasco`. + `Warning` — non-essential error, which didn't affect the result. - `Result` — represents the successful state similar to the `Right` constructor of `Either`. However, unlike `Right`, `Result` keeps the list of all `error`-like items that happened along the way. All error items are warnings as the final result was found anyway. Schematically, `Trial` has the following internal representation: ```haskell data Trial e a │ │ │ ╰╴Resulting type │ ╰╴An error item type -- | Unsuccessful case = Fiasco (DList (Fatality, e)) │ │ │ │ │ ╰╴One error item │ │ │ ╰╴Level of damage │ ╰╴Efficient list-container for error type items -- | Successful case | Result (DList e) a │ │ │ │ │ ╰╴Result │ │ │ ╰╴One warning item │ ╰╴Efficient list-container for warning type items ``` ### `Trial` instances In order to follow the basis idea of the data type, `Trial` uses smart constructors and different instances to make the structure work the way it works. Here are the main points: * All `Fiasco`s can be created only with the `Error` `Fatality` level. * The `Fatality` level can be eased only through the `Semigroup` appends of different `Trial`s. * All error items in `Result` should have only `Warning` `Fatality` level. This is guaranteed by the `Trial` `Semigroup` and `Applicative` instances. * `Semigroup` is responsible for the correct collection of history events, their `Fatality` level and the final result decision. * `Semigroup` chooses the latest 'Result' and combines all events. * Think of `Semigroup` instance as of high-level combinator of your result. * `Applicative` is responsible for the correct combination of `Trial`s. * `Applicative` returns `Fiasco`, if at least one value if `Fiasco`, combine all events. * Think of `Applicative` instance as of low-level combinator of your result on the record fields level. * `Alternative` instance could help when you want to stop on the first `Result` and get the history of all failures before it. * `Alternative`: return first `Result`, also combine all events for all `Trial`s before this `Result`. ## Tagged `Trial` Additionally, there is a `Trial`-like data type that has a notion of the `tag` inside. The main difference from `Trial` is that the resulting type contains additional information of the tag (or source it came from). The type looks like this: ```haskell type TaggedTrial tag a = Trial tag (tag, a) ``` Due to the described instances implementation, the tag will always be aligned with the final source it came from. The library provides different ways to add the tag: * Manual with the `withTag` function * Using `OverloadedLabels`and the provided `IsLabel` instance for `TaggedTrial`. You can choose the one that is more suitable for your use-case. ## Usage Examples One of the use cases when one could consider using `Trial` is the configurations in the application. If you need to collect configurations from different places, combine the results into a single configuration, you can find the `Trial` data structure quite handy. With `trial` you can get the event history for free and also you can keep track of where the final result for each component of your configurations type comes from (by using `tag` functionality). The complete example in the `trial-example` package. It combines CLI, TOML configuration and the default options provided in the source code. | Executable | Description | |------------|-------------| | [`trial-example`](https://github.com/kowainik/trial/blob/master/trial-example/app/Main.hs) | The basic example of config problem with the usage of `TaggedTrial` | | [`trial-example-advanced`](https://github.com/kowainik/trial/blob/master/trial-example/app-advanced/Main.hs) | The basic example of config problem with the usage of `TaggedTrial` with the `Phase` based approach. | To run it you can use the following command: ```shell $ cabal run trial-example $ cabal run trial-example-advanced ``` For the successful result you can use the CLI and provide necessary information in order to have the complete configurations: ```shell $ cabal run trial-example -- --host="abc" $ cabal run trial-example-advanced -- --host="abc" ```