packages feed

rerefined 0.3.0 → 0.4.0

raw patch · 17 files changed

+128/−133 lines, 17 filesdep +QuickCheck

Dependencies added: QuickCheck

Files

CHANGELOG.md view
@@ -1,3 +1,8 @@+## 0.4.0 (2024-05-11)+* add `Eq`, `Ord`, `Arbitrary` instances+* simplify modules (fewer)+* add `reifyPredicate`, `reifyPredicate1` for reifying predicates to `a -> Bool`+ ## 0.3.0 (2024-05-07) * refactor predicate names: now handled with a sort of type-level `Show`. no   `Typeable`, lots of custom prettiness (infix operators!)
rerefined.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           rerefined-version:        0.3.0+version:        0.4.0 synopsis:       Refinement types, again description:    Please see README.md. category:       Types, Data@@ -53,8 +53,6 @@       Rerefined.Predicates       Rerefined.Refine       Rerefined.Refine.TH-      Rerefined.Refine.Unsafe-      Rerefined.Refined   other-modules:       Paths_rerefined   hs-source-dirs:@@ -72,7 +70,8 @@       MagicHash   ghc-options: -Wall -Wno-unticked-promoted-constructors   build-depends:-      base >=4.17 && <5+      QuickCheck >=2.14 && <2.16+    , base >=4.17 && <5     , mono-traversable >=1.0.17.0 && <1.1     , template-haskell >=2.19.0.0 && <2.22     , text >=2.0 && <2.2
src/Rerefined.hs view
@@ -1,7 +1,9 @@ module Rerefined   ( module Rerefined.Refine+  , module Rerefined.Refine.TH   , Predicate, Refine, Refine1   ) where  import Rerefined.Refine+import Rerefined.Refine.TH import Rerefined.Predicate
src/Rerefined/Predicate.hs view
@@ -7,6 +7,7 @@   , Refine1(validate1)   , RefineFailure(..)   , Predicate(..)+  , KnownPredicateName   , predicateName   ) where @@ -21,22 +22,19 @@     -- Predicate names should aim to communicate the meaning of the predicate as     -- clearly and concisely as possible.     ---    -- Consider using @type-level-show@ to build this. However, note that GHC-    -- cannot figure out 'KnownSymbol' when there are type families in play, so-    -- you may need to put 'KnownSymbol' constraints in instance contexts.+    -- Consider using the package @type-level-show@ to build this.     type PredicateName (d :: Natural) p :: Symbol         -- ^ TODO d: the operator precedence of the enclosing context (a number         --   from 0 to 11). Function application has precedence 10. -{- TODO-stuffing the KnownSymbol constraint into a Predicate superclass is handy, but-then we have to handle it in combinator predicates. probably _not_ doing so is-better, so I'm trying that first.--}+-- | Constraint for reifying a predicate name.+type KnownPredicateName p = KnownSymbol (PredicateName 0 p) --- | Reify predicate name.-predicateName-    :: forall p. (Predicate p, KnownSymbol (PredicateName 0 p)) => String+-- | Reify predicate name to a 'String'.+--+-- Using this regrettably necessitates @UndecidableInstances@, due to the type+-- family in the constraint. It sucks, but that's life.+predicateName :: forall p. KnownPredicateName p => String predicateName = symbolVal' (proxy# @(PredicateName 0 p))  -- | Refine @a@ with predicate @p@.
src/Rerefined/Predicate/Common.hs view
@@ -8,20 +8,14 @@     module Rerefined.Predicate   , proxy#   , TBL.Builder-  , IsString -- TODO remove    -- * Predicate validation   , validateFail, validateBool-  , KnownPredicateName   ) where  import Rerefined.Predicate import GHC.Exts ( Proxy#, proxy#, IsString(fromString) )-import GHC.TypeLits ( KnownSymbol ) import Data.Text.Builder.Linear qualified as TBL---- TODO maybe move to main 'Rerefined.Predicate' module-type KnownPredicateName p = KnownSymbol (PredicateName 0 p)  -- | Shortcut for returning a predicate validation failure. validateFail
src/Rerefined/Predicate/Logical/And.hs view
@@ -5,13 +5,12 @@  import Rerefined.Predicate.Common.Binary import Rerefined.Predicate.Common-import Rerefined.Refine.Unsafe-import Rerefined.Refined+import Rerefined.Refine  -- | Logical conjunction. Also AND logic gate. data And l r --- | Precendence of 3 (matching 'Data.Bool.&&').+-- | Precedence of 3 (matching 'Data.Bool.&&'). instance (Predicate l, Predicate r) => Predicate (And l r) where     type PredicateName d (And l r) = PredicateNameBOp " ∧ " 3 d l r 
src/Rerefined/Predicate/Logical/If.hs view
@@ -9,7 +9,7 @@ -- | Logical implication. "If l then r". data If l r --- | Precendence of 4 (matching '==').+-- | Precedence of 4 (matching '=='). instance (Predicate l, Predicate r) => Predicate (If l r) where     -- TODO double arrow? idk     type PredicateName d (If l r) = PredicateNameBOp " → " 4 d l r
src/Rerefined/Predicate/Logical/Iff.hs view
@@ -10,7 +10,7 @@ --   equivalence (loosely). data Iff l r --- | Precendence of 4 (matching '==').+-- | Precedence of 4 (matching '=='). instance (Predicate l, Predicate r) => Predicate (Iff l r) where     type PredicateName d (Iff l r) = PredicateNameBOp " ↔ " 4 d l r 
src/Rerefined/Predicate/Logical/Nand.hs view
@@ -9,7 +9,7 @@ -- | NAND logic gate. Also called the Sheffer stroke, or non-conjunction. data Nand l r --- | Precendence of 3 (matching 'Data.Bool.&&').+-- | Precedence of 3 (matching 'Data.Bool.&&'). instance (Predicate l, Predicate r) => Predicate (Nand l r) where     type PredicateName d (Nand l r) = PredicateNameBOp " ⊼ " 3 d l r 
src/Rerefined/Predicate/Logical/Nor.hs view
@@ -9,7 +9,7 @@ -- | NOR logic gate. Also called non-disjunction, or joint denial. data Nor l r --- | Precendence of 2 (matching 'Data.Bool.||').+-- | Precedence of 2 (matching 'Data.Bool.||'). instance (Predicate l, Predicate r) => Predicate (Nor l r) where     type PredicateName d (Nor l r) = PredicateNameBOp " ⊽ " 2 d l r 
src/Rerefined/Predicate/Logical/Not.hs view
@@ -9,7 +9,7 @@ -- | Logical negation. Also NOT logic gate, or logical complement. data Not p --- | Precendence of 9 (one below function application).+-- | Precedence of 9 (one below function application). instance Predicate p => Predicate (Not p) where     type PredicateName d (Not p) = ShowParen (d > 9)         ("¬ " ++ PredicateName 10 p)
src/Rerefined/Predicate/Logical/Or.hs view
@@ -9,7 +9,7 @@ -- | Logical disjunction. Also OR logic gate. data Or l r --- | Precendence of 2 (matching 'Data.Bool.||').+-- | Precedence of 2 (matching 'Data.Bool.||'). instance (Predicate l, Predicate r) => Predicate (Or l r) where     type PredicateName d (Or l r) = PredicateNameBOp " ∨ " 2 d l r 
src/Rerefined/Predicate/Logical/Xor.hs view
@@ -9,7 +9,7 @@ -- | Logical exclusive disjunction. Also XOR logic gate. data Xor l r --- | Precendence of 4 (matching '==').+-- | Precedence of 4 (matching '=='). instance (Predicate l, Predicate r) => Predicate (Xor l r) where     type PredicateName d (Xor l r) = PredicateNameBOp " ⊕ " 4 d l r 
src/Rerefined/Predicate/Relational/Length.hs view
@@ -9,7 +9,7 @@ import Data.MonoTraversable ( MonoFoldable(olength) ) import GHC.Exts ( Proxy# ) -import Rerefined.Refine.Unsafe+import Rerefined.Refine import GHC.TypeError import Data.Kind ( type Constraint ) import TypeLevelShow.Utils
src/Rerefined/Refine.hs view
@@ -1,4 +1,6 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE AllowAmbiguousTypes #-} -- for predicate reification+{-# LANGUAGE UndecidableInstances #-} -- for KnownPredicateName in Arbitrary  module Rerefined.Refine   (@@ -6,11 +8,18 @@     type Refined   , refine   , unrefine+  , reifyPredicate+  , unsafeRefine+  , unsafeRerefine    -- * @Refined1@   , type Refined1   , refine1   , unrefine1+  , reifyPredicate1+  , squashRefined1+  , unsafeRefine1+  , unsafeRerefine1    -- * Errors   , type RefineFailure@@ -18,12 +27,45 @@   , prettyRefineFailure'   ) where -import Rerefined.Refined import Rerefined.Predicate+import Language.Haskell.TH.Syntax ( Lift ) import GHC.Exts ( proxy# ) import Data.Text ( Text ) import Data.Text.Builder.Linear qualified as TBL +import Test.QuickCheck ( Arbitrary(arbitrary) )+import Test.QuickCheck.Gen qualified as QC++-- | @a@ refined with predicate @p@.+newtype Refined p a = Refined a+    deriving stock (Lift, Eq, Ord, Show)++-- | Strip the refinement from a 'Refined'.+--+-- This is kept as a separate function for prettier @'Show' 'Refined'@ output.+unrefine :: Refined p a -> a+unrefine (Refined a) = a++-- | @f a@ refined with predicate @p@.+--+-- We may derive legal 'Functor', 'Traversable' instances for this as+-- 'Rerefined.Predicate.Refine1' guarantees that the predicate only applies to+-- the functor structure. That is, you _may_ alter a 'Refined1' without+-- re-asserting its predicate, provided your changes are made without altering+-- the structure/shape of @f@ (e.g. 'fmap', 'traverse').+newtype Refined1 p f a = Refined1 (f a)+    deriving stock (Functor, Foldable, Traversable, Lift, Eq, Ord, Show)++-- | Strip the refinement from a 'Refined1'.+--+-- This is kept as a separate function for prettier @'Show' 'Refined1'@ output.+unrefine1 :: Refined1 p f a -> f a+unrefine1 (Refined1 fa) = fa++-- | Squash a 'Refined1' into a 'Refined'. Essentially forget the @f@.+squashRefined1 :: Refined1 p f a -> Refined p (f a)+squashRefined1 = Refined . unrefine1+ -- | Refine @a@ with predicate @p@. refine     :: forall p a. Refine p a@@ -33,9 +75,22 @@       Nothing -> Right (Refined a)       Just e  -> Left e --- reifyPredicate is just a weaker version of validate without proxy.--- Maybe the latter is useful, though...?+-- | Construct a 'Refined' without validating the predicate @p@.+--+-- Unsafe. Use only when you can manually prove that the predicate holds.+unsafeRefine :: a -> Refined p a+unsafeRefine = Refined +-- | Replace a 'Refined''s predicate without validating the new prdicate @pNew@.+--+-- Unsafe. Use only when you can manually prove that the new predicate holds.+unsafeRerefine :: forall pNew pOld a. Refined pOld a -> Refined pNew a+unsafeRerefine = Refined . unrefine++-- | Reify a predicate.+reifyPredicate :: forall p a. Refine p a => a -> Bool+reifyPredicate a = case refine @p a of Right{} -> True; Left{} -> False+ -- | Refine @f a@ with functor predicate @p@. refine1     :: forall p f a. Refine1 p f@@ -45,6 +100,23 @@       Nothing -> Right (Refined1 fa)       Just e  -> Left e +-- | Construct a 'Refined1' without validating the predicate @p@.+--+-- Unsafe. Use only when you can manually prove that the predicate holds.+unsafeRefine1 :: f a -> Refined1 p f a+unsafeRefine1 = Refined1++-- | Replace a 'Refined1''s predicate without validating the new prdicate+--   @pNew@.+--+-- Unsafe. Use only when you can manually prove that the new predicate holds.+unsafeRerefine1 :: forall pNew pOld f a. Refined1 pOld f a -> Refined1 pNew f a+unsafeRerefine1 = Refined1 . unrefine1++-- | Reify a functor predicate.+reifyPredicate1 :: forall p f a. Refine1 p f => f a -> Bool+reifyPredicate1 fa = case refine1 @p fa of Right{} -> True; Left{} -> False+ {- TODO * got an extra \n at start oops * make it look better lol@@ -84,3 +156,27 @@     idk n rs = \case       []   -> rs       l:ls -> idk n ((n, l):rs) ls++-- | Generate a refined term by generating an unrefined term and asserting the+--   predicate.+--+-- Will runtime error if it fails to find a satisfying term (based on size).+instance (Arbitrary a, Refine p a, KnownPredicateName p)+  => Arbitrary (Refined p a) where+    arbitrary = QC.suchThatMaybe arbitrary (reifyPredicate @p) >>= \case+      Just a  -> pure $ Refined a+      Nothing -> error $+           "rerefined: couldn't generate a value satisfying predicate: "+        <> predicateName @p++-- | Generate a refined term by generating an unrefined term and asserting the+--   functor predicate.+--+-- Will runtime error if it fails to find a satisfying term (based on size).+instance (Arbitrary (f a), Refine1 p f, KnownPredicateName p)+  => Arbitrary (Refined1 p f a) where+    arbitrary = QC.suchThatMaybe arbitrary (reifyPredicate1 @p) >>= \case+      Just fa -> pure $ Refined1 fa+      Nothing -> error $+           "rerefined: couldn't generate a value satisfying predicate: "+        <> predicateName @p
− src/Rerefined/Refine/Unsafe.hs
@@ -1,50 +0,0 @@-{- | Unsafe refining.--Sometimes, you know that your value satisfies some predicate before validating.-For those cases, we permit skipping validation and obtaining a refined value-"for free".--You should be certain that your value cannot possibly fail the predicate you are-skipping. A good practice is to annotate all call sites with an explanation of-why the usage is safe.--}--module Rerefined.Refine.Unsafe-  (-  -- * @Refined@-    type Refined-  , unsafeRefine-  , unsafeRerefine--  -- * @Refined1@-  , type Refined1-  , unsafeRefine1-  , unsafeRerefine1-  ) where--import Rerefined.Refined---- | Construct a 'Refined' without validating the predicate @p@.------ Unsafe. Use only when you can manually prove that the predicate holds.-unsafeRefine :: a -> Refined p a-unsafeRefine = Refined---- | Replace a 'Refined''s predicate without validating the new prdicate @pNew@.------ Unsafe. Use only when you can manually prove that the new predicate holds.-unsafeRerefine :: forall pNew pOld a. Refined pOld a -> Refined pNew a-unsafeRerefine = Refined . unrefine---- | Construct a 'Refined1' without validating the predicate @p@.------ Unsafe. Use only when you can manually prove that the predicate holds.-unsafeRefine1 :: f a -> Refined1 p f a-unsafeRefine1 = Refined1---- | Replace a 'Refined1''s predicate without validating the new prdicate---   @pNew@.------ Unsafe. Use only when you can manually prove that the new predicate holds.-unsafeRerefine1 :: forall pNew pOld f a. Refined1 pOld f a -> Refined1 pNew f a-unsafeRerefine1 = Refined1 . unrefine1
− src/Rerefined/Refined.hs
@@ -1,48 +0,0 @@--- | 'Refined' and 'Refined1' definitions for refined values.------ Not intended for external use. For unsafe refining, use--- 'Rerefined.Refine.Unsafe'.--module Rerefined.Refined-  (-  -- * @Refined@-    Refined(..)-  , unrefine--  -- * @Refined1@-  , Refined1(..)-  , unrefine1-  , squashRefined1-  ) where--import Language.Haskell.TH.Syntax ( Lift )---- | @a@ refined with predicate @p@.-newtype Refined p a = Refined a-    deriving stock (Lift, Show)---- | Strip the refinement from a 'Refined'.------ This is kept as a separate function for prettier @'Show' 'Refined'@ output.-unrefine :: Refined p a -> a-unrefine (Refined a) = a---- | @f a@ refined with predicate @p@.------ We may derive legal 'Functor', 'Traversable' instances for this as--- 'Rerefined.Predicate.Refine1' guarantees that the predicate only applies to--- the functor structure. That is, you _may_ alter a 'Refined1' without--- re-asserting its predicate, provided your changes are made without altering--- the structure/shape of @f@ (e.g. 'fmap', 'traverse').-newtype Refined1 p f a = Refined1 (f a)-    deriving stock (Functor, Foldable, Traversable, Lift, Show)---- | Strip the refinement from a 'Refined1'.------ This is kept as a separate function for prettier @'Show' 'Refined1'@ output.-unrefine1 :: Refined1 p f a -> f a-unrefine1 (Refined1 fa) = fa---- | Squash a 'Refined1' into a 'Refined'. Essentially forget the @f@.-squashRefined1 :: Refined1 p f a -> Refined p (f a)-squashRefined1 = Refined . unrefine1