packages feed

verdict-0.0.0.0: src/Verdict/Val.hs

{-# 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