{-# OPTIONS_GHC -Wall #-}
{-# OPTIONS_GHC -funbox-strict-fields #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE ExplicitNamespaces #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RoleAnnotations #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
module Refined.Internal
(
Refined(Refined)
, refine
, refine_
, refineThrow
, refineFail
, refineError
, refineTH
, refineTH_
, unrefine
, Predicate (validate)
, reifyPredicate
, Not(..)
, And(..)
, type (&&)
, Or(..)
, type (||)
, IdPred(..)
, LessThan(..)
, GreaterThan(..)
, From(..)
, To(..)
, FromTo(..)
, NegativeFromTo(..)
, EqualTo(..)
, NotEqualTo(..)
, Odd(..)
, Even(..)
, DivisibleBy(..)
, Positive
, NonPositive
, Negative
, NonNegative
, ZeroToOne
, NonZero
, SizeLessThan(..)
, SizeGreaterThan(..)
, SizeEqualTo(..)
, NonEmpty
, Ascending(..)
, Descending(..)
, Weaken (weaken)
, andLeft
, andRight
, leftOr
, rightOr
, strengthen
, strengthenM
, RefineException
( RefineNotException
, RefineAndException
, RefineOrException
, RefineOtherException
)
, displayRefineException
, RefineT, runRefineT, exceptRefine, mapRefineT
, RefineM, refineM, runRefineM
, throwRefine, catchRefine
, throwRefineOtherException
, (|>)
, (.>)
, PP.pretty
) where
import Prelude
(Num, fromIntegral, negate, undefined)
import Control.Applicative (Applicative (pure))
import Control.Exception (Exception (displayException))
import Control.Monad (Monad, unless, when)
import Data.Bool (Bool(True,False),(&&), otherwise)
import Data.Coerce (coerce)
import Data.Either
(Either (Left, Right), either, isRight)
import Data.Eq (Eq, (==), (/=))
import Data.Foldable (Foldable(length, foldl'))
import Data.Function (const, flip, ($), (.))
import Data.Functor (Functor, fmap)
import Data.Functor.Identity (Identity (runIdentity))
import Data.Monoid (mconcat)
import Data.Ord (Ord, (<), (<=), (>), (>=))
import Data.Proxy (Proxy (Proxy))
import Data.Semigroup (Semigroup((<>)))
import Data.Typeable (TypeRep, Typeable, typeOf)
import Data.Void (Void)
import Text.Read (Read (readsPrec), lex, readParen)
import Text.Show (Show (show))
import Control.Monad.Catch (MonadThrow)
import qualified Control.Monad.Catch as MonadThrow
import Control.Monad.Error.Class (MonadError)
import qualified Control.Monad.Error.Class as MonadError
import Control.Monad.Fail (MonadFail, fail)
import Control.Monad.Fix (MonadFix, fix)
import Control.Monad.Trans.Class (MonadTrans (lift))
import Control.Monad.Trans.Except (ExceptT)
import qualified Control.Monad.Trans.Except as ExceptT
import GHC.Generics (Generic, Generic1)
import GHC.TypeLits (type (<=), KnownNat, Nat, natVal)
import GHC.Real (Integral(mod), even, odd)
import Refined.These (These(This,That,These))
import qualified Data.Text.Prettyprint.Doc as PP
import qualified Language.Haskell.TH.Syntax as TH
infixl 0 |>
infixl 9 .>
(|>) :: a -> (a -> b) -> b
(|>) = flip ($)
{-# INLINE (|>) #-}
(.>) :: (a -> b) -> (b -> c) -> a -> c
f .> g = \x -> g (f x)
{-# INLINE (.>) #-}
data Ordered a = Empty | Decreasing a | Increasing a
inc :: Ordered a -> Bool
inc (Decreasing _) = False
inc _ = True
{-# INLINE inc #-}
dec :: Ordered a -> Bool
dec (Increasing _) = False
dec _ = True
{-# INLINE dec #-}
increasing :: (Foldable t, Ord a) => t a -> Bool
increasing = inc . foldl' go Empty where
go Empty y = Increasing y
go (Decreasing x) _ = Decreasing x
go (Increasing x) y
| x <= y = Increasing y
| otherwise = Decreasing y
{-# INLINABLE increasing #-}
decreasing :: (Foldable t, Ord a) => t a -> Bool
decreasing = dec . foldl' go Empty where
go Empty y = Decreasing y
go (Increasing x) _ = Increasing x
go (Decreasing x) y
| x >= y = Decreasing y
| otherwise = Increasing y
{-# INLINABLE decreasing #-}
newtype Refined p x = Refined x
deriving (Eq, Foldable , Ord, Show, Typeable)
type role Refined nominal nominal
instance (Read x, Predicate p x) => Read (Refined p x) where
readsPrec d = readParen (d > 10) $ \r1 -> do
("Refined", r2) <- lex r1
(raw, r3) <- readsPrec 11 r2
case refine raw of
Right val -> [(val, r3)]
Left _ -> []
instance (TH.Lift x) => TH.Lift (Refined p x) where
lift (Refined a) = [|Refined a|]
refine :: (Predicate p x) => x -> Either RefineException (Refined p x)
refine x = do
let predicateByResult :: RefineM (Refined p x) -> p
predicateByResult = const undefined
runRefineM $ fix $ \result -> do
validate (predicateByResult result) x
pure (Refined x)
{-# INLINABLE refine #-}
refine_ :: forall p x. (Predicate p x) => x -> Either RefineException x
refine_ = refine @p @x .> coerce
refineThrow :: (Predicate p x, MonadThrow m) => x -> m (Refined p x)
refineThrow = refine .> either MonadThrow.throwM pure
{-# INLINABLE refineThrow #-}
refineFail :: (Predicate p x, MonadFail m) => x -> m (Refined p x)
refineFail = refine .> either (displayException .> fail) pure
{-# INLINABLE refineFail #-}
refineError :: (Predicate p x, MonadError RefineException m)
=> x -> m (Refined p x)
refineError = refine .> either MonadError.throwError pure
{-# INLINABLE refineError #-}
refineTH :: (Predicate p x, TH.Lift x) => x -> TH.Q (TH.TExp (Refined p x))
refineTH =
let refineByResult :: (Predicate p x)
=> TH.Q (TH.TExp (Refined p x))
-> x
-> Either RefineException (Refined p x)
refineByResult = const refine
in fix $ \loop -> refineByResult (loop undefined)
.> either (show .> fail) TH.lift
.> fmap TH.TExp
refineTH_ :: forall p x. (Predicate p x, TH.Lift x)
=> x
-> TH.Q (TH.TExp x)
refineTH_ =
let refineByResult :: (Predicate p x)
=> TH.Q (TH.TExp x)
-> x
-> Either RefineException x
refineByResult = const (refine_ @p @x)
in fix $ \loop -> refineByResult (loop undefined)
.> either (show .> fail) TH.lift
.> fmap TH.TExp
{-# INLINE unrefine #-}
unrefine :: Refined p x -> x
unrefine = coerce
class (Typeable p) => Predicate p x where
{-# MINIMAL validate #-}
validate :: (Monad m) => p -> x -> RefineT m ()
reifyPredicate :: forall p a. Predicate p a => a -> Bool
reifyPredicate = refine @p @a .> isRight
data IdPred = IdPred
deriving (Generic)
instance Predicate IdPred x where
validate _ _ = pure ()
{-# INLINE validate #-}
data Not p = Not
deriving (Generic, Generic1)
instance (Predicate p x, Typeable p) => Predicate (Not p) x where
validate p x = do
result <- runRefineT (validate @p undefined x)
when (isRight result) $ do
throwRefine (RefineNotException (typeOf p))
data And l r = And
deriving (Generic, Generic1)
infixr 3 &&
type (&&) = And
instance ( Predicate l x, Predicate r x, Typeable l, Typeable r
) => Predicate (And l r) x where
validate p x = do
a <- lift $ runRefineT $ validate @l undefined x
b <- lift $ runRefineT $ validate @r undefined x
let throw err = throwRefine (RefineAndException (typeOf p) err)
case (a, b) of
(Left e, Left e1) -> throw (These e e1)
(Left e, _) -> throw (This e)
(Right _, Left e) -> throw (That e)
(Right _, Right _) -> pure ()
data Or l r = Or
deriving (Generic, Generic1)
infixr 2 ||
type (||) = Or
instance ( Predicate l x, Predicate r x, Typeable l, Typeable r
) => Predicate (Or l r) x where
validate p x = do
left <- lift $ runRefineT $ validate @l undefined x
right <- lift $ runRefineT $ validate @r undefined x
case (left, right) of
(Left l, Left r) -> throwRefine (RefineOrException (typeOf p) l r)
_ -> pure ()
data SizeLessThan (n :: Nat) = SizeLessThan
deriving (Generic)
instance (Foldable t, KnownNat n) => Predicate (SizeLessThan n) (t a) where
validate p x = do
let x' = natVal p
sz = length x
unless (sz < fromIntegral x') $ do
throwRefineOtherException (typeOf p)
( [ "Size of Foldable is not less than "
, PP.pretty x'
, newline
, twoSpaces
, "Size is: "
, PP.pretty sz
] |> mconcat
)
data SizeGreaterThan (n :: Nat) = SizeGreaterThan
deriving (Generic)
instance (Foldable t, KnownNat n) => Predicate (SizeGreaterThan n) (t a) where
validate p x = do
let x' = natVal p
sz = length x
unless (sz > fromIntegral x') $ do
throwRefineOtherException (typeOf p)
( [ "Size of Foldable is not greater than "
, PP.pretty x'
, newline
, twoSpaces
, "Size is: "
, PP.pretty sz
] |> mconcat
)
data SizeEqualTo (n :: Nat) = SizeEqualTo
deriving (Generic)
instance (Foldable t, KnownNat n) => Predicate (SizeEqualTo n) (t a) where
validate p x = do
let x' = natVal p
sz = length x
unless (sz == fromIntegral x') $ do
throwRefineOtherException (typeOf p)
( [ "Size of Foldable is not equal to "
, PP.pretty x'
, newline
, twoSpaces
, "Size is: "
, PP.pretty sz
] |> mconcat
)
data Ascending = Ascending
deriving (Generic)
instance (Foldable t, Ord a) => Predicate Ascending (t a) where
validate p x = do
unless (increasing x) $ do
throwRefineOtherException (typeOf p) ( "Foldable is not in ascending order." )
data Descending = Descending
deriving (Generic)
instance (Foldable t, Ord a) => Predicate Descending (t a) where
validate p x = do
unless (decreasing x) $ do
throwRefineOtherException (typeOf p) ( "Foldable is not in descending order." )
data LessThan (n :: Nat) = LessThan
deriving (Generic)
instance (Ord x, Num x, KnownNat n) => Predicate (LessThan n) x where
validate p x = do
let x' = natVal p
unless (x < fromIntegral x') $ do
throwRefineOtherException (typeOf p) ( "Value is not less than " <> PP.pretty x' )
data GreaterThan (n :: Nat) = GreaterThan
deriving (Generic)
instance (Ord x, Num x, KnownNat n) => Predicate (GreaterThan n) x where
validate p x = do
let x' = natVal p
unless (x > fromIntegral x') $ do
throwRefineOtherException (typeOf p) ( "Value is not greater than " <> PP.pretty x' )
data From (n :: Nat) = From
deriving (Generic)
instance (Ord x, Num x, KnownNat n) => Predicate (From n) x where
validate p x = do
let x' = natVal p
unless (x >= fromIntegral x') $ do
throwRefineOtherException (typeOf p) ( "Value is less than " <> PP.pretty x' )
data To (n :: Nat) = To
deriving (Generic)
instance (Ord x, Num x, KnownNat n) => Predicate (To n) x where
validate p x = do
let x' = natVal p
unless (x <= fromIntegral x') $ do
throwRefineOtherException (typeOf p) ( "Value is greater than " <> PP.pretty x' )
data FromTo (mn :: Nat) (mx :: Nat) = FromTo
deriving (Generic)
instance ( Ord x, Num x, KnownNat mn, KnownNat mx, mn <= mx
) => Predicate (FromTo mn mx) x where
validate p x = do
let mn' = natVal (Proxy @mn)
let mx' = natVal (Proxy @mx)
unless ((x >= fromIntegral mn') && (x <= fromIntegral mx')) $ do
let msg = [ "Value is out of range (minimum: "
, PP.pretty mn'
, ", maximum: "
, PP.pretty mx'
, ")"
] |> mconcat
throwRefineOtherException (typeOf p) msg
data EqualTo (n :: Nat) = EqualTo
deriving (Generic)
instance (Eq x, Num x, KnownNat n) => Predicate (EqualTo n) x where
validate p x = do
let x' = natVal p
unless (x == fromIntegral x') $ do
throwRefineOtherException (typeOf p) ("Value does not equal " <> PP.pretty x')
data NotEqualTo (n :: Nat) = NotEqualTo
deriving (Generic)
instance (Eq x, Num x, KnownNat n) => Predicate (NotEqualTo n) x where
validate p x = do
let x' = natVal p
unless (x /= fromIntegral x') $ do
throwRefineOtherException (typeOf p) ( "Value does equal " <> PP.pretty x' )
data NegativeFromTo (n :: Nat) (m :: Nat) = NegativeFromTo
deriving (Generic)
instance (Ord x, Num x, KnownNat n, KnownNat m) => Predicate (NegativeFromTo n m) x where
validate p x = do
let n' = natVal (Proxy @n)
m' = natVal (Proxy @m)
unless (x >= negate (fromIntegral n') && x <= fromIntegral m') $ do
let msg = [ "Value is out of range (minimum: "
, PP.pretty (negate n')
, ", maximum: "
, PP.pretty m'
, ")"
] |> mconcat
throwRefineOtherException (typeOf p) msg
data DivisibleBy (n :: Nat) = DivisibleBy
deriving (Generic)
instance (Integral x, KnownNat n) => Predicate (DivisibleBy n) x where
validate p x = unless (x `mod` (fromIntegral $ natVal p) == 0) $ do
throwRefineOtherException (typeOf p) $ "Value is not divisible by " <> PP.pretty (natVal p)
data Odd = Odd
deriving (Generic)
instance (Integral x) => Predicate Odd x where
validate p x = unless (odd x) $ do
throwRefineOtherException (typeOf p) $ "Value is not odd."
data Even = Even
deriving (Generic)
instance (Integral x) => Predicate Even x where
validate p x = unless (even x) $ do
throwRefineOtherException (typeOf p) $ "Value is not even."
type Positive = GreaterThan 0
type NonPositive = To 0
type Negative = LessThan 0
type NonNegative = From 0
type ZeroToOne = FromTo 0 1
type NonZero = NotEqualTo 0
type NonEmpty = SizeGreaterThan 0
class Weaken from to where
weaken :: Refined from x -> Refined to x
weaken = coerce
instance (n <= m) => Weaken (LessThan n) (LessThan m)
instance (n <= m) => Weaken (LessThan n) (To m)
instance (n <= m) => Weaken (To n) (To m)
instance (m <= n) => Weaken (GreaterThan n) (GreaterThan m)
instance (m <= n) => Weaken (GreaterThan n) (From m)
instance (m <= n) => Weaken (From n) (From m)
instance (p <= n, m <= q) => Weaken (FromTo n m) (FromTo p q)
instance (p <= n) => Weaken (FromTo n m) (From p)
instance (m <= q) => Weaken (FromTo n m) (To q)
andLeft :: Refined (And l r) x -> Refined l x
andLeft = coerce
andRight :: Refined (And l r) x -> Refined r x
andRight = coerce
leftOr :: Refined l x -> Refined (Or l r) x
leftOr = coerce
rightOr :: Refined r x -> Refined (Or l r) x
rightOr = coerce
strengthen :: forall p p' x. (Predicate p x, Predicate p' x)
=> Refined p x
-> Either RefineException (Refined (p && p') x)
strengthen r = refine @(p && p') (unrefine r)
{-# inlineable strengthen #-}
strengthenM :: forall p p' x m. (Predicate p x, Predicate p' x, Monad m)
=> Refined p x
-> RefineT m (Refined (p && p') x)
strengthenM r = exceptRefine (strengthen r)
data RefineException
=
RefineNotException
{ _RefineException_typeRep :: !TypeRep
}
|
RefineAndException
{ _RefineException_typeRep :: !TypeRep
, _RefineException_andChild :: !(These RefineException RefineException)
}
|
RefineOrException
{ _RefineException_typeRep :: !TypeRep
, _RefineException_orLChild :: !RefineException
, _RefineException_orRChild :: !RefineException
}
|
RefineOtherException
{ _RefineException_typeRep :: !TypeRep
, _RefineException_message :: !(PP.Doc Void)
}
deriving (Generic)
instance Show RefineException where
show = PP.pretty .> show
twoSpaces, newline :: PP.Doc ann
{-# INLINE twoSpaces #-}
{-# INLINE newline #-}
twoSpaces = " "
newline = "\n"
displayRefineException :: RefineException -> PP.Doc ann
displayRefineException = \case
RefineOtherException tr msg ->
[ "The predicate ("
, PP.pretty (show tr)
, ") does not hold: "
, newline
, twoSpaces
, PP.pretty (show msg)
] |> mconcat
RefineNotException tr ->
[ "The negation of the predicate ("
, PP.pretty (show tr)
, ") does not hold:"
, newline
, twoSpaces
] |> mconcat
RefineOrException tr orLChild orRChild ->
[ "Both subpredicates failed in: ("
, PP.pretty (show tr)
, "):"
, newline
, twoSpaces
, displayRefineException orLChild
, newline
, twoSpaces
, displayRefineException orRChild
, newline
, twoSpaces
] |> mconcat
RefineAndException tr andChild ->
(
[ "The predicate ("
, PP.pretty (show tr)
, ") does not hold:"
, newline
, twoSpaces
] |> mconcat
)
<> case andChild of
This a -> mconcat [ "The left subpredicate does not hold:", newline, twoSpaces, displayRefineException a, newline ]
That b -> mconcat [ "The right subpredicate does not hold:", newline, twoSpaces, displayRefineException b, newline ]
These a b -> mconcat [ twoSpaces, "Neither subpredicate holds: ", newline
, twoSpaces, displayRefineException a, newline
, twoSpaces, displayRefineException b, newline
]
instance PP.Pretty RefineException where
pretty = displayRefineException
instance Exception RefineException where
displayException = show
newtype RefineT m a
= RefineT (ExceptT RefineException m a)
deriving ( Functor, Applicative, Monad, MonadFix
, MonadError RefineException, MonadTrans
, Generic, Generic1
)
runRefineT
:: RefineT m a
-> m (Either RefineException a)
runRefineT = coerce .> ExceptT.runExceptT
mapRefineT
:: (m (Either RefineException a) -> n (Either RefineException b))
-> RefineT m a
-> RefineT n b
mapRefineT f = coerce .> ExceptT.mapExceptT f .> coerce
type RefineM a = RefineT Identity a
refineM
:: Either RefineException a
-> RefineM a
refineM = ExceptT.except .> (coerce :: ExceptT RefineException Identity a -> RefineM a)
runRefineM
:: RefineM a
-> Either RefineException a
runRefineM = runRefineT .> runIdentity
exceptRefine
:: (Monad m)
=> Either RefineException a
-> RefineT m a
exceptRefine = MonadError.liftEither
throwRefine
:: (Monad m)
=> RefineException
-> RefineT m a
throwRefine = MonadError.throwError
catchRefine
:: (Monad m)
=> RefineT m a
-> (RefineException -> RefineT m a)
-> RefineT m a
catchRefine = MonadError.catchError
throwRefineOtherException
:: (Monad m)
=> TypeRep
-> PP.Doc Void
-> RefineT m a
throwRefineOtherException rep
= RefineOtherException rep .> throwRefine