- Notifications
You must be signed in to change notification settings - Fork9
kowainik/trial
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
TheTrial
Data Structure is aEither
-like structure that keeps events historyinside. The data type allows to keep track of theFatality
level of each suchevent entry (Warning
orError
).
This is a multi-package project that has the following packages inside:
Package | Description |
---|---|
trial | The main package that contains theTrial data structure, instances and useful functions to work with the structure. |
trial-optparse-applicative | Trial structure integration with theoptparse-applicative library for Command Line Interface. |
trial-tomland | Trial structure integration with thetomland library for TOML configurations. |
trial-example | Example project with the usage example of theTrial data structure. |
trial
is compatible with the latest GHC versions starting from8.6.5
.
In order to start usingtrial
in your project, you will need to set it up withthe three easy steps:
Add the dependency on
trial
in your project's.cabal
file. For this, youshould modify thebuild-depends
section by adding the name of this library.After the adjustment, this section could look like this:build-depends: base^>=4.14 , trial^>=0.0
In the module where you plan to use
Trial
, you should add the import:importTrial (Trial (..),fiasco,prettyPrintTrial)
Now you can use the types and functions from the library:
main::IO()main=putStrLn$ prettyPrintTrial$ fiasco"This is fiasco, bro!"
Let's have a closer look at theTrial
data structure.Trial
is a sum type that has two constructors:
Fiasco
— represents the unsuccessful state similar to theLeft
constructor ofEither
. However, unlikeLeft
,Fiasco
holds a list of allerror
-like items that happened along the way. Each such item has a notion ofFatality
(the severity of the error). The following cases coverFatality
:Error
— fatal error that led to the final fatalFiasco
.Warning
— non-essential error, which didn't affect the result.
Result
— represents the successful state similar to theRight
constructor ofEither
. However, unlikeRight
,Result
keeps the list ofallerror
-like items that happened along the way. All error items arewarnings as the final result was found anyway.
Schematically,Trial
has the following internal representation:
dataTrialea │ │ │ ╰╴Resultingtype│╰╴Anerroritemtype--| Unsuccessful case=Fiasco (DList (Fatality,e))│││││╰╴Oneerroritem│││╰╴Levelofdamage│╰╴Efficientlist-containerforerrortypeitems--| Successful case |Result (DListe)a│││││╰╴Result│││╰╴Onewarningitem│╰╴Efficientlist-containerforwarningtypeitems
In order to follow the basis idea of the data type,Trial
uses smartconstructors and different instances to make the structure work the way itworks.
Here are the main points:
- All
Fiasco
s can be created only with theError
Fatality
level. - The
Fatality
level can be eased only through theSemigroup
appends ofdifferentTrial
s. - All error items in
Result
should have onlyWarning
Fatality
level. Thisis guaranteed by theTrial
Semigroup
andApplicative
instances. Semigroup
is responsible for the correct collection of history events, theirFatality
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 ofTrial
s.Applicative
returnsFiasco
, if at least one value ifFiasco
, combine allevents.- Think of
Applicative
instance as of low-level combinator of your result on therecord fields level. Alternative
instance could help when you want to stop on the firstResult
and get the history of all failures before it.Alternative
: return firstResult
, also combine all events forallTrial
s before thisResult
.
Additionally, there is aTrial
-like data type that has a notion of thetag
inside.
The main difference fromTrial
is that the resulting type contains additionalinformation of the tag (or source it came from). The type looks like this:
typeTaggedTrialtaga=Trialtag (tag,a)
Due to the described instances implementation, the tag will always be alignedwith the final source it came from.
The library provides different ways to add the tag:
- Manual with the
withTag
function - Using
OverloadedLabels
and the providedIsLabel
instance forTaggedTrial
.
You can choose the one that is more suitable for your use-case.
One of the use cases when one could consider usingTrial
is the configurationsin the application.
If you need to collect configurations from different places, combine the resultsinto a single configuration, you can find theTrial
data structure quitehandy. Withtrial
you can get the event history for free and also you can keeptrack of where the final result for each component of your configurations typecomes from (by usingtag
functionality).
The complete example in thetrial-example
package. It combines CLI, TOMLconfiguration and the default options provided in the source code.
Executable | Description |
---|---|
trial-example | The basic example of config problem with the usage ofTaggedTrial |
trial-example-advanced | The basic example of config problem with the usage ofTaggedTrial with thePhase based approach. |
To run it you can use the following command:
$ cabal run trial-example$ cabal run trial-example-advanced
For the successful result you can use the CLI and provide necessary informationin order to have the complete configurations:
$ cabal run trial-example -- --host="abc"$ cabal run trial-example-advanced -- --host="abc"