{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ScopedTypeVariables, TypeOperators, GADTs, FlexibleInstances, FlexibleContexts, MultiParamTypeClasses, RecordWildCards, TemplateHaskell, UndecidableInstances, DefaultSignatures, FunctionalDependencies #-} module QuickSpec.Haskell where import QuickSpec.Haskell.Resolve import QuickSpec.Type import QuickSpec.Prop import Test.QuickCheck hiding (total) import Data.Constraint import Data.Proxy import qualified Twee.Base as B import QuickSpec.Term import Data.Functor.Identity import Data.Maybe import Data.MemoUgly import Test.QuickCheck.Gen import Test.QuickCheck.Random import System.Random import Data.Char import Data.Ord import qualified QuickSpec.Testing.QuickCheck as QuickCheck import qualified QuickSpec.Pruning.Twee as Twee import qualified QuickSpec.Explore import QuickSpec.Explore.PartialApplication import QuickSpec.Pruning.Background(Background) import Control.Monad import Control.Monad.Trans.State.Strict import QuickSpec.Terminal import Text.Printf import Data.Reflection hiding (D) import QuickSpec.Utils import GHC.TypeLits import QuickSpec.Explore.Conditionals baseInstances :: Instances baseInstances = mconcat [ -- Generate tuple values (pairs and () are built into findInstance) inst $ \(x :: A) (y :: B) (z :: C) -> (x, y, z), inst $ \(x :: A) (y :: B) (z :: C) (w :: D) -> (x, y, z, w), inst $ \(x :: A) (y :: B) (z :: C) (w :: D) (v :: E) -> (x, y, z, w, v), -- Split conjunctions of typeclasses into individuals inst $ \() -> Dict :: Dict (), inst $ \(Dict :: Dict ClassA) (Dict :: Dict ClassB) -> Dict :: Dict (ClassA, ClassB), inst $ \(Dict :: Dict ClassA) (Dict :: Dict ClassB) (Dict :: Dict ClassC) -> Dict :: Dict (ClassA, ClassB, ClassC), inst $ \(Dict :: Dict ClassA) (Dict :: Dict ClassB) (Dict :: Dict ClassC) (Dict :: Dict ClassD) -> Dict :: Dict (ClassA, ClassB, ClassC, ClassD), inst $ \(Dict :: Dict ClassA) (Dict :: Dict ClassB) (Dict :: Dict ClassC) (Dict :: Dict ClassD) (Dict :: Dict ClassE) -> Dict :: Dict (ClassA, ClassB, ClassC, ClassD, ClassE), -- Derive typeclass instances using (:-) -- N.B. flip is there to resolve (:-) first to reduce backtracking inst $ flip $ \(Dict :: Dict ClassA) (Sub Dict :: ClassA :- ClassB) -> Dict :: Dict ClassB, -- Standard names inst $ \(Names names :: Names A) -> Names (map (++ "s") names) :: Names [A], inst (Names ["p", "q", "r"] :: Names (A -> Bool)), inst (Names ["f", "g", "h"] :: Names (A -> B)), inst (Names ["x", "y", "z", "w"] :: Names A), -- Standard instances baseType (Proxy :: Proxy ()), baseType (Proxy :: Proxy Int), baseType (Proxy :: Proxy Integer), baseType (Proxy :: Proxy Bool), baseType (Proxy :: Proxy Char), inst (Sub Dict :: () :- CoArbitrary ()), inst (Sub Dict :: () :- CoArbitrary Int), inst (Sub Dict :: () :- CoArbitrary Integer), inst (Sub Dict :: () :- CoArbitrary Bool), inst (Sub Dict :: () :- CoArbitrary Char), inst (Sub Dict :: Eq A :- Eq [A]), inst (Sub Dict :: Ord A :- Ord [A]), inst (Sub Dict :: Arbitrary A :- Arbitrary [A]), inst (Sub Dict :: CoArbitrary A :- CoArbitrary [A]), inst (Sub Dict :: Eq A :- Eq (Maybe A)), inst (Sub Dict :: Ord A :- Ord (Maybe A)), inst (Sub Dict :: Arbitrary A :- Arbitrary (Maybe A)), inst (Sub Dict :: CoArbitrary A :- CoArbitrary (Maybe A)), inst (Sub Dict :: (Eq A, Eq B) :- Eq (Either A B)), inst (Sub Dict :: (Ord A, Ord B) :- Ord (Either A B)), inst (Sub Dict :: (Arbitrary A, Arbitrary B) :- Arbitrary (Either A B)), inst (Sub Dict :: (CoArbitrary A, CoArbitrary B) :- CoArbitrary (Either A B)), inst (Sub Dict :: (Eq A, Eq B) :- Eq (A, B)), inst (Sub Dict :: (Ord A, Ord B) :- Ord (A, B)), inst (Sub Dict :: (Arbitrary A, Arbitrary B) :- Arbitrary (A, B)), inst (Sub Dict :: (CoArbitrary A, CoArbitrary B) :- CoArbitrary (A, B)), inst (Sub Dict :: (Eq A, Eq B, Eq C) :- Eq (A, B, C)), inst (Sub Dict :: (Ord A, Ord B, Ord C) :- Ord (A, B, C)), inst (Sub Dict :: (Arbitrary A, Arbitrary B, Arbitrary C) :- Arbitrary (A, B, C)), inst (Sub Dict :: (CoArbitrary A, CoArbitrary B, CoArbitrary C) :- CoArbitrary (A, B, C)), inst (Sub Dict :: (Eq A, Eq B, Eq C, Eq D) :- Eq (A, B, C, D)), inst (Sub Dict :: (Ord A, Ord B, Ord C, Ord D) :- Ord (A, B, C, D)), inst (Sub Dict :: (Arbitrary A, Arbitrary B, Arbitrary C, Arbitrary D) :- Arbitrary (A, B, C, D)), inst (Sub Dict :: (CoArbitrary A, CoArbitrary B, CoArbitrary C, CoArbitrary D) :- CoArbitrary (A, B, C, D)), inst (Sub Dict :: (CoArbitrary A, Arbitrary B) :- Arbitrary (A -> B)), inst (Sub Dict :: (Arbitrary A, CoArbitrary B) :- CoArbitrary (A -> B)), inst (Sub Dict :: Ord A :- Eq A), -- From Arbitrary to Gen inst $ \(Dict :: Dict (Arbitrary A)) -> arbitrary :: Gen A, inst $ \(dict :: Dict ClassA) -> return dict :: Gen (Dict ClassA), -- Observe inst (\(Dict :: Dict (Observe A B C)) -> Observe2 (do { env <- arbitrary; return (\x -> observe env (x :: C)) })), inst (Sub Dict :: (Arbitrary A, Observe B C D) :- Observe (A, B) C (A -> D)), inst (\(Dict :: Dict (Ord A)) -> Observe2 (return id) :: Observe2 A A), inst (\(Observe2 obsm :: Observe2 B C) (xm :: Gen A) -> Observe2 (do {x <- xm; obs <- obsm; return (\f -> obs (f x))}) :: Observe2 (A -> B) C), inst (\(obs :: Observe2 A B) -> Observe1 (toValue obs))] -- | A typeclass for types which support observational equality, typically used -- for types that have no `Ord` instance. -- -- An instance @Observe test outcome a@ declares that values of type @a@ can be -- /tested/ for equality by random testing. You supply a function -- @observe :: test -> outcome -> a@. Then, two values @x@ and @y@ are considered -- equal, if for many random values of type @test@, @observe test x == observe test y@. -- -- For an example of using observational equality, see @@. -- -- You must use `QuickSpec.inst` to add the @Observe@ instance to your signature. -- Note that `QuickSpec.monoType` requires an `Ord` instance, so this even applies for -- monomorphic types. Don't forget to add the `Arbitrary` instance too in that case. class (Arbitrary test, Ord outcome) => Observe test outcome a | a -> test outcome where -- | Make an observation on a value. Should satisfy the following law: if -- @x /= y@, then there exists a value of @test@ such that @observe test x /= observe test y@. observe :: test -> a -> outcome default observe :: (test ~ (), outcome ~ a) => test -> a -> outcome observe _ x = x instance (Arbitrary a, Observe test outcome b) => Observe (a, test) outcome (a -> b) where observe (x, obs) f = observe obs (f x) data Observe2 a b where Observe2 :: Ord b => Gen (a -> b) -> Observe2 a b deriving Typeable data Observe1 a = Observe1 (Value (Observe2 a)) deriving Typeable -- | Declare that values of a particular type should be compared by observational equality. -- -- See @examples/PrettyPrinting.hs@ for an example. -- -- XXX mention what instances must be in scope -- XXX remove constraints etc -- observe :: Ord res => Gen env -> (env -> val -> res) -> Observe val res -- observe gen f = -- Observe (do { env <- gen; return (\x -> f env x) }) -- data SomeObserve a = forall args res. (Ord res, Arbitrary args) => SomeObserve (a -> args -> res) deriving Typeable baseType :: forall proxy a. (Ord a, Arbitrary a, Typeable a) => proxy a -> Instances baseType _ = mconcat [ inst (Dict :: Dict (Ord a)), inst (Dict :: Dict (Arbitrary a))] -- | Declare what variable names you would like to use for values of a particular type. See also `baseTypeNames`. newtype Names a = Names { getNames :: [String] } names :: Instances -> Type -> [String] names insts ty = case findInstance insts (skolemiseTypeVars ty) of (x:_) -> ofValue getNames x [] -> error "don't know how to name variables" arbitraryVal :: Type -> Instances -> Gen (Var -> Value Maybe, Value Identity -> Maybe (Value Ordy)) arbitraryVal def insts = MkGen $ \g n -> let (g1, g2) = split g in (memo $ \(V ty x) -> case genType ty of Nothing -> fromJust $ cast (defaultTo def ty) (toValue (Nothing :: Maybe A)) Just gen -> forValue gen $ \gen -> Just (unGen (coarbitrary x gen) g1 n), ordyVal g2 n) where genType :: Type -> Maybe (Value Gen) genType = memo $ \ty -> case findInstance insts (defaultTo def ty) of [] -> Nothing (gen:_) -> Just (mapValue (coarbitrary ty) gen) ordyVal :: QCGen -> Int -> Value Identity -> Maybe (Value Ordy) ordyVal g n x = let ty = defaultTo def (typ x) in case ordyTy ty of Nothing -> Nothing Just f -> Just (unGen f g n x) ordyTy :: Type -> Maybe (Gen (Value Identity -> Value Ordy)) ordyTy = memo $ \ty -> case findInstance insts ty :: [Value Observe1] of [] -> Nothing (val:_) -> case unwrap val of Observe1 val `In` w1 -> case unwrap val of Observe2 obs `In` w2 -> Just $ MkGen $ \g n -> let observe = unGen obs g n in \x -> wrap w2 (Ordy (observe (runIdentity (reunwrap w1 x)))) data Ordy a where Ordy :: Ord a => a -> Ordy a instance Eq (Value Ordy) where x == y = compare x y == EQ instance Ord (Value Ordy) where compare x y = compare (typ x) (typ y) `mappend` case unwrap x of Ordy xv `In` w -> let Ordy yv = reunwrap w y in compare xv yv evalHaskell :: (Given Type, Typed f, PrettyTerm f, Eval f (Value Maybe)) => (Var -> Value Maybe, Value Identity -> Maybe (Value Ordy)) -> Term f -> Either (Value Ordy) (Term f) evalHaskell (env, obs) t = case unwrap (eval env t) of Nothing `In` _ -> Right t Just val `In` w -> case obs (wrap w (Identity val)) of Nothing -> Right t Just ordy -> Left ordy data Constant = Constant { con_name :: String, con_style :: TermStyle, con_pretty_arity :: Int, con_value :: Value Identity, con_size :: Int, con_classify :: Classification Constant } instance Eq Constant where x == y = con_name x == con_name y && typ (con_value x) == typ (con_value y) instance Ord Constant where compare = comparing $ \con -> (con_name con, twiddle (arity con), typ con) where -- This trick comes from Prover9 and improves the ordering somewhat twiddle 1 = 2 twiddle 2 = 1 twiddle x = x instance Background Constant con :: Typeable a => String -> a -> Constant con name val = constant' name (toValue (Identity val)) constant' :: String -> Value Identity -> Constant constant' name val = Constant { con_name = name, con_style = case () of _ | name == "()" -> curried | take 1 name == "," -> fixedArity (length name+1) tupleStyle | take 2 name == "(," -> fixedArity (length name-1) tupleStyle | isOp name && typeArity (typ val) >= 2 -> infixStyle 5 | isOp name -> prefix | otherwise -> curried, con_pretty_arity = case () of _ | isOp name && typeArity (typ val) >= 2 -> 2 | isOp name -> 1 | otherwise -> typeArity (typ val), con_value = val, con_size = 1, con_classify = Function } isOp :: String -> Bool isOp "[]" = False isOp ('"':_) = False isOp xs | all (== '.') xs = True isOp xs = not (all isIdent xs) where isIdent x = isAlphaNum x || x == '\'' || x == '_' || x == '.' instance Typed Constant where typ = typ . con_value otherTypesDL con = case con_classify con of Predicate{..} -> -- Don't call typesDL on clas_selectors because it in turn -- contains a reference to the predicate typesDL (map con_value clas_selectors) `mplus` typesDL clas_test_case `mplus` typesDL clas_true Selector{..} -> typesDL clas_pred `mplus` typesDL clas_test_case Function -> mzero typeSubst_ sub con = con { con_value = typeSubst_ sub (con_value con), con_classify = fmap (typeSubst_ sub) (con_classify con) } instance Pretty Constant where pPrint = text . con_name instance PrettyTerm Constant where termStyle = con_style instance PrettyArity Constant where prettyArity = con_pretty_arity instance Sized Constant where size = con_size instance Arity Constant where arity = typeArity . typ instance Predicate Constant where classify = con_classify instance (Given Type, Applicative f) => Eval Constant (Value f) where eval _ = mapValue (pure . runIdentity) . con_value class Predicateable a where uncrry :: a -> TestCase a -> Bool instance Predicateable Bool where uncrry = const instance forall a b. (Predicateable b, Typeable a, TestCase (a -> b) ~ (a, TestCase b)) => Predicateable (a -> b) where uncrry f (a, b) = uncrry (f a) b -- Foldr over functions type family (Foldr f b fun) :: * where Foldr f def (a -> b) = f a (Foldr f def b) Foldr f def b = def -- A test case for predicates of type a -- if `a ~ A -> B -> C -> Bool` we get `TestCase a ~ (A, (B, (C, ())))` -- -- Some speedup should be possible by using unboxed tuples instead... type TestCase a = Foldr (,) () a data TestCaseWrapped (t :: Symbol) a = TestCaseWrapped { unTestCaseWrapped :: a } -- A `suchThat` generator for a predicate genSuchThat :: (Predicateable a, Arbitrary (TestCase a)) => a -> Gen (TestCaseWrapped x (TestCase a)) genSuchThat p = TestCaseWrapped <$> arbitrary `suchThat` uncrry p data PredRep = PredRep { predInstances :: Instances , predCon :: Constant , predCons :: [Constant] } true :: Constant true = con "True" True trueTerm :: Term (PartiallyApplied Constant) trueTerm = App (total true) [] -- | Declare a predicate with a given name and value. -- The predicate should have type @... -> Bool@. predicate :: forall a. ( Predicateable a , Typeable a , Typeable (TestCase a)) => String -> a -> PredRep predicate name pred = case someSymbolVal name of SomeSymbol (_ :: Proxy sym) -> let instances = inst (\(dict :: Dict (Arbitrary (TestCase a))) -> (withDict dict genSuchThat) pred :: Gen (TestCaseWrapped sym (TestCase a))) `mappend` inst (Names [name ++ "_var"] :: Names (TestCaseWrapped sym (TestCase a))) conPred = (con name pred) { con_classify = Predicate conSels ty (App true []) } conSels = [ (constant' (name ++ "_" ++ show i) (select i)) { con_classify = Selector i conPred ty, con_size = 0 } | i <- [0..typeArity (typeOf pred)-1] ] select i = fromJust (cast (arrowType [ty] (typeArgs (typeOf pred) !! i)) (unPoly (compose (sel i) unwrapV))) where compose f g = apply (apply cmpV f) g sel 0 = fstV sel n = compose (sel (n-1)) sndV fstV = toPolyValue (fst :: (A, B) -> A) sndV = toPolyValue (snd :: (A, B) -> B) cmpV = toPolyValue ((.) :: (B -> C) -> (A -> B) -> A -> C) unwrapV = toPolyValue (unTestCaseWrapped :: TestCaseWrapped SymA A -> A) ty = typeRep (Proxy :: Proxy (TestCaseWrapped sym (TestCase a))) in PredRep instances conPred (conPred:conSels) data Config = Config { cfg_quickCheck :: QuickCheck.Config, cfg_twee :: Twee.Config, cfg_max_size :: Int, cfg_instances :: Instances, cfg_constants :: [[Constant]], cfg_predicates :: [[PredRep]], cfg_default_to :: Type } makeLensAs ''Config [("cfg_quickCheck", "lens_quickCheck"), ("cfg_twee", "lens_twee"), ("cfg_max_size", "lens_max_size"), ("cfg_instances", "lens_instances"), ("cfg_constants", "lens_constants"), ("cfg_predicates", "lens_predicates"), ("cfg_default_to", "lens_default_to")] defaultConfig :: Config defaultConfig = Config { cfg_quickCheck = QuickCheck.Config { QuickCheck.cfg_num_tests = 1000, QuickCheck.cfg_max_test_size = 20, QuickCheck.cfg_fixed_seed = Nothing }, cfg_twee = Twee.Config { Twee.cfg_max_term_size = minBound, Twee.cfg_max_cp_depth = maxBound }, cfg_max_size = 7, cfg_instances = mempty, cfg_constants = [], cfg_predicates = [], cfg_default_to = typeRep (Proxy :: Proxy Int) } quickSpec :: Config -> IO () quickSpec Config{..} = give cfg_default_to $ do let constantsOf f = true:f cfg_constants ++ f (map (concatMap predCons) cfg_predicates) constants = constantsOf concat univ = conditionalsUniverse constants instances = mconcat (cfg_instances:map predInstances (concat cfg_predicates) ++ [baseInstances]) present prop = do n :: Int <- get put (n+1) putLine (printf "%3d. %s" n (show (prettyProp (names instances) (conditionalise prop) <+> maybeType prop))) -- Add a type signature when printing the equation x = y. maybeType (_ :=>: x@(Var _) :=: Var _) = text "::" <+> pPrintType (typ x) maybeType _ = pPrintEmpty mainOf f g = do printConstants (f cfg_constants ++ f (map (map predCon) cfg_predicates)) putLine "" putLine "== Laws ==" QuickSpec.Explore.quickSpec present measure (flip evalHaskell) cfg_max_size univ [ Partial fun 0 | fun <- constantsOf g ] putLine "" main = mapM_ round [1..rounds] where round n = mainOf (concat . take 1 . drop (rounds-n)) (concat . drop (rounds-n)) rounds = max (length cfg_constants) (length cfg_predicates) join $ fmap withStdioTerminal $ generate $ QuickCheck.run cfg_quickCheck (arbitraryVal cfg_default_to instances) evalHaskell $ Twee.run cfg_twee { Twee.cfg_max_term_size = Twee.cfg_max_term_size cfg_twee `max` cfg_max_size } $ runConditionals (map total constants) $ flip evalStateT 1 $ main printConstants :: MonadTerminal m => [Constant] -> m () printConstants cs = do putLine "== Functions ==" let decls = [ (show (pPrint (App (Partial c 0) [])), pPrintType (typ c)) | c <- cs ] maxWidth = maximum (0:map (length . fst) decls) pad xs = replicate (maxWidth - length xs) ' ' ++ xs pPrintDecl (name, ty) = hang (text (pad name) <+> text "::") 2 ty mapM_ (putLine . show . pPrintDecl) decls