constrained-generators-0.2.0.0: examples/Constrained/Examples/ManualExamples.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}
module Constrained.Examples.ManualExamples where
import Constrained.API
import Data.Set (Set)
import GHC.Generics
import GHC.Natural
import Test.QuickCheck hiding (forAll)
import qualified Test.QuickCheck as QuickCheck
{- Generating from Specifications, and checking against Specifications -}
prop1 :: Gen Property
prop1 = do
(w, x, y, z) <- arbitrary :: Gen (Int, Int, Int, Int)
pure $ (w < x && x < y && y < z) ==> property (w < z)
spec1 :: Specification (Int, Int, Int, Int)
spec1 = constrained' $ \w x y z -> [w <. x, x <. y, y <. z]
prop2 :: Gen Property
prop2 = do
(w, x, y, z) <- genFromSpec spec1
pure $ (w < x && x < y && y < z) ==> property (w < z)
prop3 :: Gen Property
prop3 = do
(w, x, y, z) <- frequency [(9, genFromSpec spec1), (1, arbitrary)]
pure $
if (w < x && x < y && y < z)
then property (w < z)
else expectFailure $ property (w < z)
leqPair :: Specification (Int, Int)
leqPair = constrained $ \p ->
match p $ \x y ->
assert (x <=. (y +. lit 2))
sumPair :: Specification (Int, Int)
sumPair = constrained $ \p ->
match p $ \x y ->
[ assert $ x <=. y
, assert $ y >=. 20
, assert $ x + y ==. 25
]
ex1 :: Specification Int
ex1 = constrained $ \_x -> True
ex2 :: Specification Int
ex2 = constrained $ \x -> x ==. lit 3
ex3 :: Specification Int
ex3 = constrained $ \x -> [x <=. lit 2, x >=. lit 0]
ex4 :: Specification Int
ex4 = constrained $ \x -> assert $ x ==. lit 9
{- From Term to Pred
1. `assert`
-}
-- assert :: IsPred p => p -> Pred
ex5 :: Specification [Int]
ex5 = constrained $ \xs -> assert $ elem_ 7 xs
{- For all elements in a container type (List, Set, Map)
1. `forAll`
-}
-- forAll :: (Forallable t a, HasSpec t, HasSpec a, IsPred p) =>
-- Term t -> (Term a -> p) -> Pred
-- class Forallable t e | t -> e where
-- instance Ord k => Forallable (Map k v) (k, v)
-- instance Ord a => Forallable (Set a) a
-- instance Forallable [a] a
ex6 :: Specification [Int]
ex6 = constrained $ \xs ->
forAll xs $ \x -> [x <=. 10, x >. 1]
{- Reification
1. `reifies`
2. `reify`
3. `assertRefified`
-}
-- reifies :: (HasSpec a, HasSpec b) => Term b -> Term a -> (a -> b) -> Pred
ex7 :: Specification (Int, [Int])
ex7 = constrained $ \pair ->
match pair $ \n xs ->
reifies n xs sum
-- reify :: (HasSpec a, HasSpec b, IsPred p) => Term a -> (a -> b) -> (Term b -> p) -> Pred
ex8 :: Specification ([Int], [Int])
ex8 = constrained $ \pair ->
match pair $ \xs1 xs2 ->
[ assert $ sizeOf_ xs1 <=. 5
, forAll xs1 $ \x -> x <=. 10
, reify xs1 reverse $ \t -> xs2 ==. t
]
-- assertReified :: (HasSpec Bool, HasSpec a) => Term a -> (a -> Bool) -> Pred
ex9 :: Specification Int
ex9 = constrained $ \x ->
[ assert $ x <=. 10
, assertReified x (<= 10)
]
{- Disjunction, choosing between multiple things with the same type
1. `CaseOn`, `branch`, `branchW`
2. `chooseSpec`
-}
{-
caseOn
:: (HasSpec a, HasSpec (SimpleRep a), HasSimpleRep a,
TypeSpec a ~ TypeSpec (SimpleRep a),
SimpleRep a
~ Constrained.Generic.SumOver
(Constrained.Spec.SumProd.Cases (SimpleRep a)),
TypeList (Constrained.Spec.SumProd.Cases (SimpleRep a))) =>
Term a
-> FunTy
(MapList
(Weighted Binder) (Constrained.Spec.SumProd.Cases (SimpleRep a)))
Pred
-}
data Three = One Int | Two Bool | Three Int deriving (Ord, Eq, Show, Generic)
instance HasSimpleRep Three
instance HasSpec Three
ex10 :: Specification Three
ex10 = constrained $ \three ->
caseOn
three
(branch $ \i -> i ==. 1) -- One
(branch $ \b -> assert (not_ b)) -- Two
(branch $ \j -> j ==. 3) -- Three
ex11 :: Specification Three
ex11 = constrained $ \three ->
caseOn
three
(branchW 1 $ \i -> i <. 0) -- One, weight 1
(branchW 2 $ \b -> assert b) -- Two, weight 2
(branchW 3 $ \j -> j >. 0) -- Three, weight 3
-- chooseSpec:: HasSpec a => (Int, Specification a) -> (Int, Specification a) -> Specification a
ex12 :: Specification (Int, [Int])
ex12 =
chooseSpec
( 5
, constrained $ \pair ->
match pair $ \tot xs -> [tot >. lit 10, sum_ xs ==. tot, sizeOf_ xs ==. lit 3]
)
( 3
, constrained $ \pair ->
match pair $ \tot xs -> [tot <. lit 10, sum_ xs ==. tot, sizeOf_ xs ==. lit 6]
)
{- Primed library functions which are compositions with match
1. `forAll'`
2. `constrained'`
3. `reify'`
-}
ex13a :: Specification [(Int, Int)]
ex13a = constrained $ \xs ->
forAll xs $ \x -> match x $ \a b -> a ==. negate b
ex13b :: Specification [(Int, Int)]
ex13b = constrained $ \xs ->
forAll' xs $ \a b -> a ==. negate b
ex14a :: Specification (Int, Int, Int)
ex14a = constrained $ \triple ->
match triple $ \a b c -> [b ==. a + lit 1, c ==. b + lit 1]
ex14b :: Specification (Int, Int, Int)
ex14b = constrained' $ \a b c -> [b ==. a + lit 1, c ==. b + lit 1]
ex15a :: Specification (Int, Int, Int)
ex15a = constrained $ \triple ->
match triple $ \x1 x2 x3 ->
reify x1 (\a -> (a + 1, a + 2)) $ \t ->
match t $ \b c -> [x2 ==. b, x3 ==. c]
ex15b :: Specification (Int, Int, Int)
ex15b = constrained $ \triple ->
match triple $ \x1 x2 x3 ->
reify' x1 (\a -> (a + 1, a + 2)) $ \b c -> [x2 ==. b, x3 ==. c]
ex15c :: Specification (Int, Int, Int)
ex15c = constrained' $ \x1 x2 x3 ->
reify' x1 (\a -> (a + 1, a + 2)) $ \b c -> [x2 ==. b, x3 ==. c]
{- Construtors and Selectors
1. `onCon`
2. `sel`
4. `isJust`
-}
ex16 :: Specification Three
ex16 = constrained $ \three ->
caseOn
three
(branchW 1 $ \i -> i ==. lit 1) -- One, weight 1
(branchW 2 $ \b -> assert (not_ b)) -- Two, weight 2
(branchW 3 $ \j -> j ==. 3) -- Three, weight 3
ex17 :: Specification Three
ex17 = constrained $ \three ->
[ onCon @"One" three (\x -> x ==. lit 1)
, onCon @"Two" three (\x -> not_ x)
, onCon @"Three" three (\x -> x ==. lit 3)
]
ex18 :: Specification Three
ex18 = constrained $ \three -> onCon @"Three" three (\x -> x ==. lit 3)
ex19 :: Specification (Maybe Bool)
ex19 = constrained $ \mb -> onCon @"Just" mb (\x -> x ==. lit False)
data Dimensions where
Dimensions ::
{ length :: Int
, width :: Int
, depth :: Int
} ->
Dimensions
deriving (Ord, Eq, Show, Generic)
instance HasSimpleRep Dimensions
instance HasSpec Dimensions
ex20a :: Specification Dimensions
ex20a = constrained $ \d ->
match d $ \l w dp -> [l >. lit 10, w ==. lit 5, dp <. lit 20]
ex20b :: Specification Dimensions
ex20b = constrained $ \d ->
[ sel @0 d >. lit 10
, sel @1 d ==. lit 5
, sel @2 d <. lit 20
]
width_ :: Term Dimensions -> Term Int
width_ d = sel @1 d
ex21 :: Specification Dimensions
ex21 = constrained $ \d -> width_ d ==. lit 1
{- Naming introduced lambda bound Term variables
1. [var|name|]
-}
ex22a :: Specification (Int, Int)
ex22a = constrained $ \pair ->
match pair $ \left right -> [left ==. right, left ==. right + lit 1]
ex22b :: Specification (Int, Int)
ex22b = constrained $ \ [var|pair|] ->
match pair $ \ [var|left|] [var|right|] -> [left ==. right, left ==. right + lit 1]
{- Existential quantifiers
1. `exists`
2. `unsafeExists`
-}
ex24 :: Specification Int
ex24 = constrained $ \ [var|oddx|] ->
unsafeExists
(\ [var|y|] -> [assert $ oddx ==. y + y + 1])
ex25 :: Specification Int
ex25 = explainSpec ["odd via (y+y+1)"] $
constrained $ \ [var|oddx|] ->
exists
(\eval -> pure (div (eval oddx - 1) 2))
(\ [var|y|] -> [assert $ oddx ==. y + y + 1])
{- Conditionals
1. `whenTrue`
2. `ifElse`
-}
data Rectangle = Rectangle {wid :: Int, len :: Int, square :: Bool}
deriving (Show, Eq, Generic)
instance HasSimpleRep Rectangle
instance HasSpec Rectangle
ex26 :: Specification Rectangle
ex26 = constrained' $ \w l sq ->
[ assert $ w >=. lit 0
, assert $ l >=. lit 0
, whenTrue sq (assert $ w ==. l)
]
ex27 :: Specification Rectangle
ex27 = constrained' $ \w l sq ->
ifElse
sq
(assert $ w ==. l)
[ assert $ w >=. lit 0
, assert $ l >=. lit 0
]
{- `Explanantions`
1. `assertExplain`
2. `explanation`
3. `ExplainSpec`
-}
ex28a :: Specification (Set Int)
ex28a = constrained $ \s ->
[ assert $ member_ (lit 5) s
, forAll s $ \x -> [x >. lit 6, x <. lit 20]
]
ex28b :: Specification (Set Int)
ex28b = explainSpec ["5 must be in the set"] $
constrained $ \s ->
[ assert $ member_ (lit 5) s
, forAll s $ \x -> [x >. lit 6, x <. lit 20]
]
{- Operations to define and use Specifications
1. `satisfies`
2. `equalSpec`
3. `notEqualSpec`
4. `notMemberSpec`
5. `leqSpec`
6. `ltSpec`
7. `geqSpec`
8. `gtSpec`
5. `cardinality`
-}
ex29 :: Specification Int
ex29 = constrained $ \x ->
[ assert $ x >=. lit 0
, assert $ x <=. lit 5
, satisfies x (notMemberSpec [2, 3])
]
{- Utility functions
1. `simplifyTerm`
2. `simplifySpec`
3. `genFromSpecT`
4. `genFromSpec`
5. `genFromSpecWithSeed`
6. `debugSpec`
-}
{- Escape Hatch to QuickCheck Gen monad
1. `monitor`
-}
ex30 :: Specification (Int, Int)
ex30 = constrained $ \ [var|p|] ->
match p $ \ [var|x|] [var|y|] ->
[ assert $ x /=. 0
, -- You can use `monitor` to add QuickCheck property modifiers for
-- monitoring distribution, like classify, label, and cover, to your
-- specification
monitor $ \eval ->
QuickCheck.classify (eval y > 0) "positive y"
. QuickCheck.classify (eval x > 0) "positive x"
]
prop31 :: QuickCheck.Property
prop31 = forAllSpec ex30 $ \_ -> True
ex32 :: IO ()
ex32 = QuickCheck.quickCheck $ prop31
ex11m :: Specification Three
ex11m = constrained $ \three ->
[ caseOn
three
(branchW 1 $ \i -> i <. 0) -- One, weight 1
(branchW 2 $ \b -> assert b) -- Two, weight 2
(branchW 3 $ \j -> j >. 0) -- Three, weight 3
, monitor $ \eval ->
case (eval three) of
One _ -> QuickCheck.classify True "One should be about 1/6"
Two _ -> QuickCheck.classify True "Two should be about 2/6"
Three _ -> QuickCheck.classify True "Three should be about 3/6"
]
propex11 :: QuickCheck.Property
propex11 = forAllSpec ex11m $ \_ -> True
ex33 :: IO ()
ex33 = QuickCheck.quickCheck $ propex11
{- Strategy for constraining a large type with many nested sub-components -}
data Nested = Nested Three Rectangle [Int]
deriving (Show, Eq, Generic)
instance HasSimpleRep Nested
instance HasSpec Nested
{-
Problem using TruePred, not monomorphic enough
skeleton1 :: Specification Nested
skeleton1 = constrained $ \ [var|nest|] ->
match nest $ \ [var|three|] [var|rect|] [var|line|] ->
[ (caseOn (three :: Term Three))
(branch $ \ _i -> TruePred) -- One,
(branch $ \ _k -> TruePred) -- Two,
(branch $ \ _j -> TruePred) -- Three,
, match rect $ \ [var|_wid|] [var|_len|] [var|_square|] -> TruePred
, forAll line $ \ [var|_point|] -> TruePred
]
-}
-- By type applying match, branch, and forAll to @Pred , makes it monomorphic
-- Note type Pred = PredD Deps , so it fixes the type argument of PredD
skeleton2 :: Specification Nested
skeleton2 = constrained $ \ [var|nest|] ->
match nest $ \ [var|three|] [var|rect|] [var|line|] ->
[ (caseOn (three :: Term Three))
(branch @Pred $ \_i -> truePred) -- One,
(branch @Pred $ \_k -> truePred) -- Two,
(branch @Pred $ \_j -> truePred) -- Three,
, match @Pred rect $ \ [var|_wid|] [var|_len|] [var|_square|] -> truePred
, forAll @Pred line $ \ [var|_point|] -> truePred
]
-- We can do a similar thing by introducing `truePred` with the monomorphic type.
truePred :: Pred
truePred = mempty
skeleton :: Specification Nested
skeleton = constrained $ \ [var|nest|] ->
match nest $ \ [var|three|] [var|rect|] [var|line|] ->
[ (caseOn (three :: Term Three))
(branch $ \_i -> truePred) -- One,
(branch $ \_k -> truePred) -- Two,
(branch $ \_j -> truePred) -- Three,
, match rect $ \ [var|_wid|] [var|_len|] [var|_square|] -> [truePred]
, forAll line $ \ [var|_point|] -> truePred
]
-- ======================================================================
newtype Coin = Coin {unCoin :: Integer} deriving (Eq, Show)
instance HasSimpleRep Coin where
type SimpleRep Coin = Natural
toSimpleRep (Coin i) = case integerToNatural i of
Nothing -> error $ "The impossible happened in toSimpleRep for (Coin " ++ show i ++ ")"
Just w -> w
fromSimpleRep = naturalToCoin
instance HasSpec Coin
integerToNatural :: Integer -> Maybe Natural
integerToNatural c
| c < 0 = Nothing
| otherwise = Just $ fromIntegral c
naturalToCoin :: Natural -> Coin
naturalToCoin = Coin . fromIntegral
ex34 :: Specification Coin
ex34 = constrained $ \coin ->
match coin $ \nat -> [nat >=. lit 100, nat <=. lit 200]