verdict (empty) → 0.0.0.0
raw patch · 12 files changed
+734/−0 lines, 12 filesdep +basedep +hspecdep +markdown-unlitsetup-changed
Dependencies added: base, hspec, markdown-unlit, mtl, text, transformers, verdict
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- examples/Tutorial.lhs +148/−0
- src/Verdict.hs +40/−0
- src/Verdict/Class.hs +103/−0
- src/Verdict/Failure.hs +42/−0
- src/Verdict/Logic.hs +27/−0
- src/Verdict/Types.hs +51/−0
- src/Verdict/Val.hs +122/−0
- test/Spec.hs +1/−0
- test/VerdictSpec.hs +96/−0
- verdict.cabal +72/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2015, Julian K. Arni++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Julian K. Arni nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ examples/Tutorial.lhs view
@@ -0,0 +1,148 @@+# Verdict++*Very experimental!*++~~~ {.haskell}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE OverloadedStrings #-}+import GHC.TypeLits+import Data.Proxy+import Data.Monoid+import qualified Data.Text as Text+import Verdict+~~~++## A very general smart constructor++Sometimes you want to provide extra guarantees about a type that are best+expressed in terms of a smart constructor with an opaque type:+++~~~ {.haskell}+newtype NonEmptyList' a = NonEmptyList' { getList :: [a] }+makeNonEmptyList :: [a] -> Maybe (NonEmptyList' a)+makeNonEmptyList x | null x = Nothing+ | otherwise = Just (NonEmptyList' x)+~~~++Where the `NonEmptyList` constructor is not exported.++This works, but it has quite a few downsides. You can no longer derive things+like `Read` and `FromJSON` without losing the invariants you set up. If you+have a `NonEmptyList` that is also of maximum length 100, you need to either+have a newtype to again wrap your `NonEmptyList`, or a newtype that expresses+both those constraints. Either way, all the functions you wrote for+`NonEmptyList`, and which ideally would work with `NonEmptyMax100List`, won't+do so (without some newtype unwrapping or coercing).++Instead, with `verdict` you can do this:++~~~ {.haskell}+type NonEmptyList a = Validated (Not (Length 0)) [a]+~~~++The smart constructor comes for free as `validate`. Specialized for clarity:++~~~ {.haskell}+mkNonEmpty :: [a] -> Either ErrorTree (NonEmptyList a)+mkNonEmpty = validate+~~~++The `Read` instance also comes for free, and checks for validity:++~~~ {.haskell}+testRead :: NonEmptyList Int+testRead = read "[]"+~~~++`testRead` will throw a descriptive error message.++## With some of the power of refinement types++Writing functions that express their precise assumptions can then be done with+`Implies`:++~~~ {.haskell}+safeHead :: (c `Implies` (Not (Length 0))) => Validated c [a] -> a+safeHead = head . getVal+~~~++Now, if we have another type:++~~~ {.haskell}+type ReasonableList a = Validated (MinLength 1 :&& MaxLength 100) [a]+~~~++Our function `safeHead` will typecheck when given either a `ReasonableList` or+a `NonEmptyList` (or anything else with constraints that imply the length is+not zero).++The library provides some terms that can be used in the little constraint DSL.+Additionally, you may want to create your own. Doing so is as easy as declaring+an empty datatype, and a `HaskVerdict` instance for it. Let us for example+define a predicate `InCircle cx cy r`, which is true of a `Point` if the point+is inside a circle of radius `r` centered on coordinates `(cx, cy)`:++~~~ {.haskell}+data InCircle centerX centerY radius+data Point = Point { x :: Integer, y :: Integer } deriving (Eq, Show)++instance (KnownNat cx, KnownNat cy, KnownNat r)+ => HaskVerdict (InCircle cx cy r) Point where+ haskVerdict _+ = check (\p -> (x p - centerX) ^ 2 + (y p - centerY) ^ 2 < radius ^ 2) err+ where centerX = natVal (Proxy :: Proxy cx)+ centerY = natVal (Proxy :: Proxy cy)+ radius = natVal (Proxy :: Proxy r)+ err = "Point not inside circle: " <> (Text.pack $ show ((centerX, centerY), radius))+~~~++Additionally, you may also want to declare type instances for `Implies'` which+describe what things imply or are implied by your new constraint.++~~~ {.haskell}+type instance Implies' (InCircle cx cy r) (InCircle cx cy r') = r <= r'+~~~++## And opt-in validation capabilities++Sometimes you may not want to or be able to change the types of records. You+can still validate them:++~~~ {.haskell}+mkUpperRightQuad :: ApplicativeError ErrorTree m+ => Integer -> Integer -> m Point+mkUpperRightQuad x' y' = Point <$> x' `checkWith` (Proxy :: Proxy (Minimum 0))+ <*> y' `checkWith` (Proxy :: Proxy (Minimum 0))+~~~++If we instantiate `ApplicativeError` to a short-circuiting applicative (like+`Either`), only the first result will be returned. If we use something like+`Failure`, all of them will:++~~~ {.haskell}+main :: IO ()+main = do+ print (mkUpperRightQuad 5 3 :: Either ErrorTree Point)+ print (mkUpperRightQuad (-2) 1 :: Either ErrorTree Point)+ print (mkUpperRightQuad (-2) (- 1) :: Either ErrorTree Point)+ print (mkUpperRightQuad (-2) (- 1) :: Failure ErrorTree Point)+ print (mkUpperRightQuad 0 (- 1) :: Either ErrorTree Point)+ print testRead+~~~++The results:+++ Right (Point {x = 5, y = 3})+ Left (Leaf "Should be more than 0")+ Left (Leaf "Should be more than 0")+ Failure (And (Leaf "Should be more than 0") (Leaf "Should be more than 0"))+ Left (Leaf "Should be more than 0")+ tutorial: Leaf "this still needs to be figured out"
+ src/Verdict.hs view
@@ -0,0 +1,40 @@+module Verdict+ (++ -- * Essentials+ Validated+ , validate+ , getVal+ , unsafeCoerceVal+ , check+ , checkWith+ , HaskVerdict(..)+ , Implies+ , Implies'+ , KnownVal(..)+ , (|.)++ -- * Verdict Terms+ , (:&&)+ , (:||)+ , Not+ , Minimum+ , Maximum+ , MaxLength+ , MinLength+ , Length+ , MultipleOf+ , HasElem++ -- * Errors+ , ErrorTree+ , ErrorTree'(..)+ , Failure(..)+ , ApplicativeError(..)+ ) where++import Verdict.Class+import Verdict.Failure+import Verdict.Logic+import Verdict.Types+import Verdict.Val
+ src/Verdict/Class.hs view
@@ -0,0 +1,103 @@+{-# LANGUAGE OverloadedStrings #-}+module Verdict.Class where++import Control.Monad+import Data.Monoid+import Data.Proxy+import qualified Data.Text as Text+import GHC.TypeLits+import Verdict.Types++------------------------------------------------------------------------------+-- * HaskVerdict+------------------------------------------------------------------------------+class HaskVerdict a b where+ haskVerdict :: Proxy a -> b -> Maybe ErrorTree+++------------------------------------------------------------------------------+-- * Logical Base Terms+------------------------------------------------------------------------------+instance (HaskVerdict a r, HaskVerdict b r) => HaskVerdict (a :&& b) r where+ haskVerdict _ x = And <$> haskVerdict pa x <*> haskVerdict pb x+ where pa = Proxy :: Proxy a+ pb = Proxy :: Proxy b++instance (HaskVerdict a r, HaskVerdict b r) => HaskVerdict (a :|| b) r where+ haskVerdict _ x = Or <$> haskVerdict pa x <*> haskVerdict pb x+ where pa = Proxy :: Proxy a+ pb = Proxy :: Proxy b++instance (HaskVerdict c a) => HaskVerdict (Not c) a where+ haskVerdict _ x = case haskVerdict p x of+ Just _ -> Nothing+ Nothing -> Just (Leaf "this still needs to be figured out")+ where p = Proxy :: Proxy c++instance HaskVerdict 'True a where+ haskVerdict _ _ = Nothing++------------------------------------------------------------------------------+-- * Datatype Representation Terms+------------------------------------------------------------------------------++{-instance ( G.Generic a ) => HaskVerdict (a :* b) r where-}+ {-haskVerdict _ x = foldM (G.hcollapse . G.hap r $ G.from x)-}+ {-where pa = Proxy :: Proxy a-}+ {-pb = Proxy :: Proxy b-}+ {-v1 = G.Fn $ \(G.I n) -> G.K (haskVerdict pa n)-}+ {-v2 = G.Fn $ \(G.I n) -> G.K (haskVerdict pb n)-}+ {-r = (v1 G.:* v2 G.:* G.Nil) G.:* G.Nil-}++------------------------------------------------------------------------------+-- * Other Base Terms+------------------------------------------------------------------------------+instance HaskVerdict () a where+ haskVerdict _ = const Nothing++instance (Ord b, Show b, KnownVal a b) => HaskVerdict (Maximum a) b where+ haskVerdict _ = check (<= p) ("Should be less than " <> showT p)+ where p = knownVal (Proxy :: Proxy a)++instance (Ord b, Show b, KnownVal a b) => HaskVerdict (Minimum a) b where+ haskVerdict _ = check (>= p) ("Should be more than " <> showT p)+ where p = knownVal (Proxy :: Proxy a)++instance (Foldable f, Show (f b), KnownNat a)+ => HaskVerdict (MaxLength a) (f b) where+ haskVerdict _ = check ((<= p) . length)+ ("Should be of length less than " <> showT p)+ where p = fromInteger $ natVal (Proxy :: Proxy a)++instance (Foldable f, Show (f b), KnownNat a)+ => HaskVerdict (MinLength a) (f b) where+ haskVerdict _ = check ((>= p) . length)+ ("Should be of length more than " <> showT p)+ where p = fromInteger $ knownVal (Proxy :: Proxy a)++instance (Foldable t, KnownNat a) => HaskVerdict (Length a) (t b) where+ haskVerdict _ = check ((== p) . length) ("Should be of length " <> showT p)+ where p = fromInteger $ natVal (Proxy :: Proxy a)++instance (Foldable t, Show b, Eq b, KnownVal a b)+ => HaskVerdict (HasElem a) (t b) where+ haskVerdict _ = check (elem p) ("Should contain " <> showT p)+ where p = knownVal (Proxy :: Proxy a)++instance (KnownNat n, Integral a) => HaskVerdict (MultipleOf n) a where+ haskVerdict _ = check (\x -> (toInteger x `rem` p) == 0) ("Not a multiple of " <> showT p)+ where p = natVal (Proxy :: Proxy n)++showT :: Show a => a -> Text.Text+showT = Text.pack . show++check :: (x -> Bool) -> err -> x -> Maybe (ErrorTree' err)+check pred' err x = guard (not $ pred' x) >> pure (Leaf err)++------------------------------------------------------------------------------+-- Known Val+class KnownVal a b | a -> b where+ knownVal :: Proxy a -> b++instance KnownNat n => KnownVal n Integer where+ knownVal = natVal
+ src/Verdict/Failure.hs view
@@ -0,0 +1,42 @@+module Verdict.Failure+ ( Failure(..)+ , ApplicativeError(..)+ ) where++import Control.Exception (IOException, catch)+import qualified Control.Monad.Trans.Except as ExceptT+import Data.Monoid+import Data.Typeable (Typeable)+import GHC.Generics (Generic)++data Failure e a = Failure e | Success a+ deriving (Eq, Show, Functor, Generic, Typeable)++class Applicative m => ApplicativeError e m | m -> e where+ throwError :: e -> m a+ catchError :: m a -> (e -> m a) -> m a++instance Monoid e => Applicative (Failure e) where+ pure = Success+ Failure msgs <*> Failure msgs' = Failure (msgs <> msgs')+ Success _ <*> Failure msgs' = Failure msgs'+ Failure msgs' <*> Success _ = Failure msgs'+ Success f <*> Success x = Success (f x)++instance Monoid e => ApplicativeError e (Failure e) where+ throwError = Failure+ catchError (Failure e) f = f e+ catchError s _ = s++instance ApplicativeError e (Either e) where+ throwError = Left+ catchError (Left e) f = f e+ catchError s _ = s++instance ApplicativeError IOException IO where+ throwError = ioError+ catchError = catch++instance Monad m => ApplicativeError e (ExceptT.ExceptT e m) where+ throwError = ExceptT.throwE+ catchError = ExceptT.catchE
+ src/Verdict/Logic.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE UndecidableInstances #-}+module Verdict.Logic where++import GHC.Exts (Constraint)+import GHC.TypeLits+import Verdict.Types+++type family Or (a :: Constraint) (b :: Constraint) :: Constraint where+ Or () x = ()+ Or x () = ()++type family Implies (a :: k) (b :: k) :: Constraint where+ Implies (a :&& b) c = (a `Implies` c) `Or` (b `Implies` c)+ Implies a (b :|| c) = (a `Implies` b) `Or` (a `Implies` c)+ Implies a (Not a) = ('True ~ 'False)+ Implies (Not a) a = ('True ~ 'False)+ Implies a (Not (Not a)) = ()+ Implies a 'True = ()+ Implies a b = Implies' a b++type family Implies' a b :: Constraint+type instance Implies' (Length a) (MaxLength b) = a <= b+type instance Implies' (Length a) (MinLength b) = b <= b+type instance Implies' (MaxLength a) (MaxLength b) = a <= b+type instance Implies' (MinLength a) (MinLength b) = b <= a
+ src/Verdict/Types.hs view
@@ -0,0 +1,51 @@+{-# LANGUAGE DeriveFunctor #-}+module Verdict.Types where++import qualified Data.Text as Text++------------------------------------------------------------------------------+-- * Logical Base Terms+------------------------------------------------------------------------------+data a :&& b = a :&& b+infixr 3 :&&+data a :|| b = a :|| b+infixr 2 :||+data Not a++------------------------------------------------------------------------------+-- * Datatype Representation Terms+------------------------------------------------------------------------------++data a :* b+infixr 5 :*++------------------------------------------------------------------------------+-- * Other Base Terms+------------------------------------------------------------------------------++data Minimum a+data Maximum a+data MaxLength a+data MinLength a+data Length a+data MultipleOf a++data HasElem a++------------------------------------------------------------------------------+-- * Other Types+------------------------------------------------------------------------------++-- | Error representation+data ErrorTree' e+ = Leaf e+ | Or (ErrorTree' e) (ErrorTree' e)+ | And (ErrorTree' e) (ErrorTree' e)+ | Sum (ErrorTree' e) (ErrorTree' e)+ deriving (Eq, Show, Functor)++type ErrorTree = ErrorTree' Text.Text++instance Monoid e => Monoid (ErrorTree' e) where+ mempty = Leaf mempty+ mappend = And
+ src/Verdict/Val.hs view
@@ -0,0 +1,122 @@+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Verdict.Val where++import Control.Monad.Fix+import Control.Monad.Zip+import Data.Coerce (Coercible, coerce)+import Data.Foldable+import Data.Proxy+import Data.String (IsString (..))+import Text.Read++import Verdict.Class+import Verdict.Failure+import Verdict.Logic+import Verdict.Types++------------------------------------------------------------------------------+-- * Validated+------------------------------------------------------------------------------+-- | A generalization of smart constructors with opaque types.+-- Construct a @Validated@ with 'validate'.+newtype Validated constraint a = Validated { getVal :: a }+ deriving (Show, Eq, Ord)++-- * Validated ()++-- @Validated ()@ is the same as 'Data.Functor.Identity'; we use the same+-- instances.+validateEmpty :: a -> Validated () a+validateEmpty = coerce++instance Functor (Validated ()) where+ fmap = coerce++instance Applicative (Validated ()) where+ pure = Validated+ (<*>) = coerce++instance Monad (Validated ()) where+ return = Validated+ m >>= k = k (getVal m)++instance MonadFix (Validated ()) where+ mfix f = Validated (fix (getVal . f))++instance MonadZip (Validated ()) where+ mzipWith = coerce+ munzip = coerce++instance Monoid m => Monoid (Validated () m) where+ mempty = Validated mempty+ mappend a b = Validated $ mappend (getVal a) (getVal b)++instance Foldable (Validated ()) where+ foldMap = coerce+ elem = (. getVal) #. (==)+ foldl = coerce+ foldl' = coerce+ foldl1 _ = getVal+ foldr f z (Validated x) = f x z+ foldr' = foldr+ foldr1 _ = getVal+ length _ = 1+ maximum = getVal+ minimum = getVal+ null _ = False+ product = getVal+ sum = getVal+ toList (Validated x) = [x]++instance (HaskVerdict c v, Read v) => Read (Validated c v) where+ readPrec = force . validate <$> readPrec+ where force = either (error . show) id++instance (HaskVerdict c v, IsString v) => IsString (Validated c v) where+ fromString = force . validate . fromString+ where force = either (error . show) id+++-- | Constructs a @Validated c a@ from an @a@ if @a@ matches the constraints;+-- throws an error with a description of precise constraints not satisfied+-- otherwise.+validate :: forall c a m . (HaskVerdict c a, ApplicativeError ErrorTree m)+ => a -> m (Validated c a)+validate a = case haskVerdict (Proxy :: Proxy c) a of+ Nothing -> pure $ Validated a+ Just err -> throwError err++-- | Coerce a 'Validated' to another set of constraints. This is safe with+-- respect to memory corruption, but loses the guarantee that the values+-- satisfy the predicates.+unsafeCoerceVal :: Validated c a -> Validated c' a+unsafeCoerceVal = coerce++protect :: ( ApplicativeError (String, ErrorTree) m+ , HaskVerdict c a+ ) => Proxy c -> String -> (a -> b) -> a -> m b+protect p name fn a = case haskVerdict p a of+ Nothing -> pure $ fn a+ Just e -> throwError (name, e)++-- | Checks a non-'Validated' value against a set of constraints given by a+-- 'Proxy'.+checkWith :: forall m c a . (ApplicativeError ErrorTree m, HaskVerdict c a)+ => a -> Proxy c -> m a+checkWith v _ = getVal <$> v'+ where v' = validate v :: ApplicativeError ErrorTree m => m (Validated c a)++-- | Function composition. Typechecks if the result of applying the first+-- function has a constraint that implies the constraint of the argument of the+-- second function.+(|.) :: (cb' `Implies` cb)+ => (Validated cb b -> Validated cc c)+ -> (Validated ca a -> Validated cb' b)+ -> Validated ca a -> Validated cc c+f |. g = f . coerce . g+infixr 8 |.++-- * Internal+(#.) :: Coercible b c => (b -> c) -> (a -> b) -> (a -> c)+(#.) _f = coerce
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ test/VerdictSpec.hs view
@@ -0,0 +1,96 @@+module VerdictSpec (spec) where++import Data.Either+import Test.Hspec+import Verdict++spec :: Spec+spec = describe "Verdict" $ do+ maximumSpec+ minimumSpec+ maxLengthSpec+ minLengthSpec+ lengthSpec+ multipleOfSpec++maximumSpec :: Spec+maximumSpec = describe "Maximum" $ do++ it "rejects larger values" $ do+ (validate 5 :: Either ErrorTree (Validated (Maximum 3) Integer))+ `shouldSatisfy` isLeft++ it "accepts smaller values" $ do+ (validate 3 :: Either ErrorTree (Validated (Maximum 5) Integer))+ `shouldSatisfy` isRight++ it "accepts equal sized values" $ do+ (validate 3 :: Either ErrorTree (Validated (Maximum 3) Integer))+ `shouldSatisfy` isRight++minimumSpec :: Spec+minimumSpec = describe "Minimum" $ do++ it "rejects smaller values" $ do+ (validate 3 :: Either ErrorTree (Validated (Minimum 5) Integer))+ `shouldSatisfy` isLeft++ it "accepts larger values" $ do+ (validate 5 :: Either ErrorTree (Validated (Minimum 3) Integer))+ `shouldSatisfy` isRight++ it "accepts equal sized values" $ do+ (validate 3 :: Either ErrorTree (Validated (Minimum 3) Integer))+ `shouldSatisfy` isRight++maxLengthSpec :: Spec+maxLengthSpec = describe "MaxLength" $ do++ it "rejects longer values" $ do+ (validate [(),()] :: Either ErrorTree (Validated (MaxLength 1) [()]))+ `shouldSatisfy` isLeft++ it "accepts shorter values" $ do+ (validate [()] :: Either ErrorTree (Validated (MaxLength 3) [()]))+ `shouldSatisfy` isRight++ it "accepts equal sized values" $ do+ (validate [()] :: Either ErrorTree (Validated (MaxLength 1) [()]))+ `shouldSatisfy` isRight++minLengthSpec :: Spec+minLengthSpec = describe "MinLength" $ do++ it "rejects shorter values" $ do+ (validate [()] :: Either ErrorTree (Validated (MinLength 2) [()]))+ `shouldSatisfy` isLeft++ it "accepts shorter values" $ do+ (validate [(), ()] :: Either ErrorTree (Validated (MinLength 1) [()]))+ `shouldSatisfy` isRight++ it "accepts equal sized values" $ do+ (validate [()] :: Either ErrorTree (Validated (MinLength 1) [()]))+ `shouldSatisfy` isRight++lengthSpec :: Spec+lengthSpec = describe "Length" $ do++ it "rejects differing lengths" $ do+ (validate [()] :: Either ErrorTree (Validated (Length 2) [()]))+ `shouldSatisfy` isLeft++ it "accepts matching lengths" $ do+ (validate [()] :: Either ErrorTree (Validated (Length 1) [()]))+ `shouldSatisfy` isRight++multipleOfSpec :: Spec+multipleOfSpec = describe "MultipleOf" $ do++ it "rejects non-multiples" $ do+ (validate 5 :: Either ErrorTree (Validated (MultipleOf 2) Integer))+ `shouldSatisfy` isLeft++ it "accepts multiples" $ do+ (validate 4 :: Either ErrorTree (Validated (MultipleOf 2) Integer))+ `shouldSatisfy` isRight
+ verdict.cabal view
@@ -0,0 +1,72 @@+name: verdict+version: 0.0.0.0+synopsis: Validation framework+description:+ DO NOT USE! Unstable, not thoroughly tested.+license: BSD3+license-file: LICENSE+author: Julian K. Arni+maintainer: jkarni@gmail.com+copyright: (c) Julian K. Arni+build-type: Simple+cabal-version: >=1.10++library+ exposed-modules: Verdict+ other-modules: Verdict.Class+ , Verdict.Failure+ , Verdict.Types+ , Verdict.Logic+ , Verdict.Val+ default-extensions: TypeOperators+ , MultiParamTypeClasses+ , DataKinds+ , FunctionalDependencies+ , PolyKinds+ , ScopedTypeVariables+ , FlexibleInstances+ , FlexibleContexts+ , TypeFamilies+ , DeriveFunctor+ , DeriveGeneric+ , DeriveDataTypeable+ build-depends: base >=4.7 && <4.9+ , mtl+ , transformers+ , text+ hs-source-dirs: src+ ghc-options: -Wall+ default-language: Haskell2010++test-suite spec+ type: exitcode-stdio-1.0+ ghc-options: -Wall+ -fno-warn-unused-binds+ -fno-warn-type-defaults+ default-language: Haskell2010+ hs-source-dirs: test+ main-is: Spec.hs+ other-modules: VerdictSpec+ default-extensions: DeriveFunctor+ , TypeOperators+ , MultiParamTypeClasses+ , DataKinds+ , FunctionalDependencies+ , PolyKinds+ , ScopedTypeVariables+ , FlexibleInstances+ , FlexibleContexts+ , TypeFamilies+ build-depends: base == 4.*+ , verdict+ , hspec == 2.*++executable tutorial+ main-is: Tutorial.lhs+ ghc-options: -Wall -pgmL markdown-unlit+ hs-source-dirs: examples+ build-depends: base >=4.7 && <4.9+ , verdict+ , text+ , markdown-unlit+ default-language: Haskell2010