Safe Haskell | None |
---|---|
Language | Haskell2010 |
Type families for defining functions that are polymorphic over predicates.
Since: 0.1.0.0
Synopsis
- type family Implies q p where ...
- type (:=>) p q = Implies p q
- type family ImpliesBool q p :: Bool where ...
- type family ErrIfFalse (p :: ErrorMessage) (b :: Bool) where ...
- type PredNotFound p q = (('Text "Desired predicate " ':<>: 'ShowType q) ':<>: 'Text " was not implied by ") ':<>: 'ShowType p
Type Families
The Implies
type family lets us write functions that are polymorphic
over predicates, i.e., we can define functions that require some predicate
p
, which can then be used by any refined term, as long as the term
satisfies p
. This means we can write APIs that are both safe and
flexible.
type family Implies q p where ... Source #
raises a type error if Implies
q pq
does not logically imply
p
. See ImpliesBool
for when q
implies p
.
Examples
>>>
:{
let safeDiv :: (Implies p NonZero, Integral n) => n -> Refined p n -> n safeDiv x d = x `div` unrefine d -- -- term does not have to exactly match Refined '[NonZero] n d :: Refined (NonZero && Positive) Int d = $$(refineTH 7) in safeDiv 14 d :} 2
>>>
:{
-- total wrapper over Data.Text's partial splitOn. let safeSplitOn :: Implies p NonEmpty => Refined p Text -> Text -> [Text] safeSplitOn needle haystack = unrefine needle `splitOn` haystack -- splitter = $$(refineTH @NonEmpty @Text ",") in safeSplitOn splitter "some,text," :} ["some","text",""]
Since: 0.1.0.0
Implies q p = ErrIfFalse (PredNotFound q p) (ImpliesBool q p) |
type (:=>) p q = Implies p q infixr 1 Source #
Infix operator for Implies
.
Examples
>>>
:{
safeDiv2 :: (p :=> NonZero, Integral n) => n -> Refined p n -> n safeDiv2 x d = x `div` unrefine d :}
Since: 0.1.0.0
type family ImpliesBool q p :: Bool where ... Source #
ImpliesBool q p
returns 'True
if q
logically implies p
,
i.e., whenever q
is true, p
is also true.
NB. For a (possibly exclusive) disjunction to imply p
, both clauses
must individually imply p
, since we cannot know which one is satisfied.
That is,
\[ q_1 \vee q_2 \implies p \quad \iff \quad (q_1 \implies p) \wedge (q_2 \implies p) \]
When determining whether q
implies p
we are unfortunately limited by
searching for syntactic, not semantic, equality. In particular,
((A && B) && C) /=> (A && (B && C)) (A || (B && Not B) || C) /=> A || C
Thus it is advisable to write function constraints as simply as possible.
-- not this foo :: Implies p (A && B) => Refined p a -> ... -- prefer this bar :: (Implies p A, Implies p B) => Refined p a -> ...
Of course this transformation cannot be performed universally
(e.g. A || B
), and we do have
(A && B) == (B && A), (A || B) == (B || A)
for convenience.
Examples
>>>
:kind! ImpliesBool (Not A) B
ImpliesBool (Not A) B :: Bool = False
>>>
:kind! ImpliesBool (Not (Not A)) A
ImpliesBool (Not (Not A)) A :: Bool = True
>>>
:kind! ImpliesBool (A || B) A
ImpliesBool (A || B) A :: Bool = False
>>>
:kind! ImpliesBool (A || (A && B)) A
ImpliesBool (A || (A && B)) A :: Bool = True
Since: 0.1.0.0
ImpliesBool p p = 'True | |
ImpliesBool q p = ImpliesCNF (ToCNF q) (ToCNF p) |
Errors
type family ErrIfFalse (p :: ErrorMessage) (b :: Bool) where ... Source #
ErrIfFalse err 'False = TypeError err :: Constraint | |
ErrIfFalse _1 _2 = () |