structural-induction 0.2 → 0.2.0.1
raw patch · 8 files changed
+446/−10 lines, 8 files
Files
- changelog +3/−0
- structural-induction.cabal +2/−10
- test/Env.hs +129/−0
- test/EnvTypes.hs +164/−0
- test/Nat.hs +45/−0
- test/Trace.hs +26/−0
- test/Util.hs +15/−0
- test/Walk.hs +62/−0
changelog view
@@ -1,3 +1,6 @@+structural-induction 0.2.0.1 (released 2015-06-10)+ * Add missing test suite files to extra-source-files.+ structural-induction 0.2 (released 2015-04-10) * Support GHC 7.10
structural-induction.cabal view
@@ -1,6 +1,6 @@ name: structural-induction category: Theorem Provers, Logic-version: 0.2+version: 0.2.0.1 license: LGPL-3 license-file: LICENSE author: Dan Rosén@@ -12,20 +12,14 @@ tested-with: GHC == 7.4.2, GHC == 7.6.3, GHC == 7.8.3, GHC == 7.10.1 synopsis: Instantiate structural induction schemas for algebraic data types description: See documentation for Induction.Structural-extra-source-files: changelog+extra-source-files: changelog test/Env.hs test/EnvTypes.hs test/Walk.hs test/Util.hs test/Trace.hs test/Nat.hs source-repository head type: git location: git://github.com/danr/structural-induction.git -flag Werror- default: False- manual: True- library ghc-options: -Wall -fno-warn-unused-imports- if flag(Werror)- ghc-options: -Werror exposed-modules: Induction.Structural,@@ -50,8 +44,6 @@ main-is: Main.hs hs-source-dirs: test ghc-options: -Wall -fno-warn-unused-imports- if flag(Werror)- ghc-options: -Werror build-depends: structural-induction,
+ test/Env.hs view
@@ -0,0 +1,129 @@+{-# LANGUAGE GADTs, ConstraintKinds, ExplicitForAll, PatternGuards #-}+module Env where++import Test.QuickCheck+import Control.Applicative++import Induction.Structural++import EnvTypes+import Nat+import Util++type Oblig = Obligation Con' String Ty'+type Tm = Term Con' String+type P = Predicate Con' String+type Hyp = Hypothesis Con' String Ty'++-- | A small test environment. No exponentials for now.+testEnv :: Repr a -> Maybe [(Con a,[Arg Ty'])]+testEnv t = case t of+ Unit -> Just [(TT,[])]+ Nat -> Just [(Zero,[])+ ,(Succ,[Rec (Si Nat)])+ ]+ PInt -> Just [(Pos,[NonRec (Si Nat)])+ ,(Neg,[NonRec (Si Nat)])+ ]+ Bool -> Just [(Tru,[]),(Fls,[])]+ List a -> Just [(Nil a,[])+ ,(Cons,[NonRec (Si a),Rec (Si (List a))])+ ]+ Maybe a -> Just [(Ntng a,[])+ ,(Jst,[NonRec (Si a)])+ ]+ TupTy a b -> Just [(Tup,[NonRec (Si a),NonRec (Si b)])]+ TyVar -> Nothing++-- | Forgetting some type info+testEnv' :: TyEnv Con' Ty'+testEnv' (Si t) = case testEnv t of+ Just ksargs -> Just (map (uncurry forget) ksargs)+ Nothing -> Nothing+ where+ forget :: forall a . EqShow a => Con a -> [Arg Ty'] -> (Con',[Arg Ty'])+ forget k args = (Si k, args)++-- | Generates a random value of some type+arbFromType :: Repr a -> Gen a+arbFromType t = case t of+ Unit -> arbitrary+ Nat -> arbitrary+ PInt -> arbitrary+ Bool -> arbitrary+ List a -> halfSize $ \ s -> frequency+ [ (1, return [])+ , (s, (:) <$> arbFromType a <*> arbFromType (List a))+ ]+ Maybe a -> sized $ \ s -> frequency+ [ (1, return Nothing)+ , (s, Just <$> arbFromType a)+ ]+ TupTy a b -> (,) <$> arbFromType a <*> arbFromType b+ TyVar -> arbitrary++-- | Forget type information+arbFromType' :: Sigma Repr -> Gen (Exists Repr)+arbFromType' (Si r) = Val r <$> arbFromType r++startFromTypes :: [Ty'] -> Gen [Repr']+startFromTypes = mapM arbFromType'++-- Cannot make this well typed in a nice way+mkCon :: Con' -> [Repr'] -> Repr'+mkCon (Si c) as = case c of+ TT -> case as of { [] -> Val Unit () ; _ -> ill_typed }+ Zero -> case as of { [] -> Val Nat Z ; _ -> ill_typed }+ Succ -> case as of { [Val Nat n] -> Val Nat (S n) ; _ -> ill_typed }+ Pos -> case as of { [Val Nat n] -> Val PInt (P n) ; _ -> ill_typed }+ Neg -> case as of { [Val Nat n] -> Val PInt (N n) ; _ -> ill_typed }+ Fls -> case as of { [] -> Val Bool False ; _ -> ill_typed }+ Tru -> case as of { [] -> Val Bool True ; _ -> ill_typed }+ Ntng a -> case as of { [] -> Val (Maybe a) Nothing ; _ -> ill_typed }+ Jst -> case as of { [Val a x] -> Val (Maybe a) (Just x) ; _ -> ill_typed }+ Tup -> case as of { [Val a x,Val b y] -> Val (TupTy a b) (x,y) ; _ -> ill_typed }+ Nil a -> case as of { [] -> Val (List a) [] ; _ -> ill_typed }+ Cons -> case as of+ [Val a x,Val (List b) xs] -> case a ==? b of+ Just Refl -> Val (List a) (x:xs)+ Nothing -> error $ "mkCon: " ++ show as ++ " is heterogenous!"+ _ -> ill_typed+ where+ ill_typed :: a+ ill_typed = error $ "mkCon: illtyped: " ++ show (Si c) ++ " on " ++ show as++type VarMap = [(String,Repr')]++-- | Tries to match a representation to a term, returning bound variables+match :: Repr' -> Tm -> Maybe VarMap+match v tm0 = case tm0 of+ Var s -> Just [(s,v)]+ Con (Si c) tms -> case (v,c,tms) of+ (Val Unit () , TT , []) -> ok+ (Val Nat Z , Zero , []) -> ok+ (Val Nat (S x) , Succ , [tm]) -> match (Val Nat x) tm+ (Val PInt (P x) , Pos , [tm]) -> match (Val Nat x) tm+ (Val PInt (N x) , Neg , [tm]) -> match (Val Nat x) tm+ (Val Bool True , Tru , []) -> ok+ (Val Bool False , Fls , []) -> ok+ (Val (Maybe a) Nothing , Ntng b , []) | Just Refl <- a ==? b -> ok+ (Val (Maybe a) (Just x) , Jst , [tm]) -> match (Val a x) tm+ (Val (List a) [] , Nil b , []) | Just Refl <- a ==? b -> ok+ (Val (List a) (x:xs) , Cons , [t1,t2]) ->+ match (Val a x) t1 `comb` match (Val (List a) xs) t2+ (Val (TupTy a b) (x,y) , Tup , [t1,t2]) ->+ match (Val a x) t1 `comb` match (Val b y) t2+ _ -> Nothing+ Fun{} -> error "match: No support for exponentials!"+ where+ ok = Just []+ comb r s = (++) <$> r <*> s++-- | Show induction schema+showOblig :: [Oblig] -> String+showOblig = unlines . map ((++ ".") . render . linObligation style)++-- | The style for printing+style :: Style Con' String Ty'+style = Style (text . show) text (text . show)+
+ test/EnvTypes.hs view
@@ -0,0 +1,164 @@+{-# LANGUAGE GADTs, KindSignatures, DeriveDataTypeable,+ FlexibleInstances, ConstraintKinds,+ PatternGuards, TemplateHaskell #-}+module EnvTypes where++import Nat++import Data.Function (on)+import Data.Typeable (Typeable)++import Test.QuickCheck+import Test.Feat++type EqShow a = (Eq a,Show a,Arbitrary a)++data Sigma :: (* -> *) -> * where+ Si :: EqShow a => r a -> Sigma r++data Exists :: (* -> *) -> * where+ Val :: EqShow a => r a -> a -> Exists r++type Repr' = Exists Repr++type Ty' = Sigma Repr+++-- Representations of our types.+-- Putting Eq and Show here simplifies those instances for Repr a bit+data Repr a where+ Unit :: Repr ()+ Nat :: Repr Nat+ PInt :: Repr PInt+ Bool :: Repr Bool+ List :: EqShow a => Repr a -> Repr [a]+ Maybe :: EqShow a => Repr a -> Repr (Maybe a)+ TupTy :: (EqShow a,EqShow b) => Repr a -> Repr b -> Repr (a,b)+ TyVar :: Repr Int+ -- ^ Should this contain more information?+ deriving Typeable++-- The more number of units, the more boring this type is to test+data URepr+ = UUnit () () ()+ | UNat+ | UPInt+ | UBool () ()+ | UList URepr+ | UMaybe URepr ()+ | UTupTy URepr URepr ()+ | UTyVar () () ()+ deriving Typeable++deriveEnumerable ''URepr++toTy :: URepr -> Ty'+toTy u = case u of+ UUnit{} -> Si Unit+ UNat -> Si Nat+ UPInt -> Si PInt+ UBool{} -> Si Bool+ UList a | Si a' <- toTy a -> Si (List a')+ UMaybe a _ | Si a' <- toTy a -> Si (Maybe a')+ UTupTy a b _ | Si a' <- toTy a+ , Si b' <- toTy b -> Si (TupTy a' b')+ UTyVar{} -> Si TyVar++enumTy' :: Enumerate Ty'+enumTy' = fmap toTy enumerate++{-# ANN enumTy's "HLint: ignore Use camelCase" #-}+enumTy's :: Enumerate [Ty']+enumTy's = fmap (map toTy) enumerate++showRepr :: Repr a -> String+showRepr r = case r of+ Unit -> "Unit"+ Nat -> "Nat"+ PInt -> "PInt"+ Bool -> "Bool"+ List a -> "List " ++ showRepr a+ Maybe a -> "Maybe " ++ showRepr a+ TupTy a b -> "TupTy " ++ showRepr a ++ " " ++ showRepr b+ TyVar -> "TyVar"++showRepr' :: Repr' -> String+showRepr' (Val r x) = "Val " ++ showRepr r ++ " " ++ show x++instance Show Ty' where+ show (Si r) = case r of+ Unit -> "()"+ Nat -> "Nat"+ PInt -> "Int"+ Bool -> "Bool"+ List a -> "[" ++ show (Si a) ++ "]"+ Maybe a -> "Maybe " ++ show (Si a)+ TupTy a b -> "(" ++ show (Si a) ++ "," ++ show (Si b) ++ ")"+ TyVar -> "a"++type Con' = Sigma Con++data Con a where+ TT :: Con ()+ Zero :: Con Nat+ Succ :: Con Nat+ Pos :: Con PInt+ Neg :: Con PInt+ Tru :: Con Bool+ Fls :: Con Bool+ Nil :: EqShow a => Repr a -> Con [a]+ Cons :: Con [a]+ Jst :: Con (Maybe a)+ Ntng :: EqShow a => Repr a -> Con (Maybe a)+ Tup :: Con (a,b)++infoCon' :: Con' -> (Int,String)+infoCon' c = case c of+ Si TT -> (0,"()")+ Si Zero -> (1,"Zero")+ Si Succ -> (2,"Succ")+ Si Nil{} -> (3,"Nil")+ Si Cons -> (4,"Cons")+ Si Pos -> (5,"Pos")+ Si Neg -> (6,"Neg")+ Si Jst -> (7,"Just")+ Si Ntng{} -> (8,"Nothing")+ Si Tru -> (9,"True")+ Si Fls -> (10,"False")+ Si Tup -> (11,"Tup")++instance Eq Con' where+ (==) = (==) `on` (fst . infoCon')++instance Ord Con' where+ compare = compare `on` (fst . infoCon')++instance Show Con' where+ show = snd . infoCon'++instance Show (Exists a) where+ show (Val _ x) = show x++shrinkRepr' :: Repr' -> [Repr']+shrinkRepr' (Val r x) = map (Val r) (shrink x)++data Equal :: * -> * -> * where+ Refl :: Equal a a++(==?) :: Repr a -> Repr b -> Maybe (Equal a b)+Unit ==? Unit = Just Refl+Nat ==? Nat = Just Refl+PInt ==? PInt = Just Refl+Bool ==? Bool = Just Refl+TyVar ==? TyVar = Just Refl+Maybe a ==? Maybe b | Just Refl <- a ==? b = Just Refl+List a ==? List b | Just Refl <- a ==? b = Just Refl+TupTy a u ==? TupTy b v | Just Refl <- a ==? b+ , Just Refl <- u ==? v = Just Refl+_ ==? _ = Nothing++instance Eq (Exists Repr) where+ Val s u == Val t v = case s ==? t of+ Just Refl -> u == v+ Nothing -> False+
+ test/Nat.hs view
@@ -0,0 +1,45 @@+{-# OPTIONS_GHC -fno-warn-missing-methods #-}+{-# LANGUAGE ViewPatterns #-}+module Nat where++import Test.QuickCheck++-- A classic!+data Nat = Z | S Nat deriving (Show,Eq)++instance Arbitrary Nat where+ arbitrary = sized $ \ s -> do+ x <- choose (0,round (sqrt (toEnum s) :: Double))+ return (nats !! x)++ shrink (S n) = n : shrink n+ shrink Z = []++nats :: [Nat]+nats = iterate S Z++-- warning: partial instance+instance Num Nat where+ fromInteger n = nats !! fromEnum n++-- Another classic!+data PInt = P Nat | N Nat+ deriving (Show,Eq)++p :: PInt -> Nat+p (P n) = n+p (N n) = n++instance Arbitrary PInt where+ arbitrary = do+ (b,x) <- arbitrary+ return $ if b then P x else N x++ shrink (p -> n ) = map P (shrink n) ++ map N (shrink n)++-- warning: partial instance+instance Num PInt where+ fromInteger x+ | x < 0 = N (fromInteger (negate (x + 1)))+ | otherwise = P (fromInteger x)+
+ test/Trace.hs view
@@ -0,0 +1,26 @@+module Trace where++import Data.List (find)+import Control.Monad (mplus,msum)++data Trace a = Fork a [Trace a]++instance Show a => Show (Trace a) where+ show = unlines . go+ where+ go (Fork x ts) = show x:bars (map go ts)++ bar :: [String] -> [String]+ bar [] = []+ bar (s:xs) = ("┣┉" ++ s):map ("┃ " ++) xs++ bars :: [[String]] -> [String]+ bars [] = []+ bars [s:xs] = ("┗┉" ++ s):map (" " ++) xs+ bars (x:xs) = bar x ++ bars xs++loop :: Eq a => Trace a -> Maybe a+loop = go []+ where+ go vis (Fork x ts) = find (x ==) vis `mplus` msum (map (go (x:vis)) ts)+
+ test/Util.hs view
@@ -0,0 +1,15 @@+module Util where++import Test.QuickCheck++halfSize :: (Int -> Gen a) -> Gen a+halfSize m = sized $ \s -> resize (s `div` 2) (m s)++divSize :: Int -> (Int -> Gen a) -> Gen a+divSize d m = sized $ \s -> resize (s `div` d) (m s)++coordss :: Int -> Int -> [[Int]]+coordss n _ | n < 0 = [[]]+coordss 0 d = [replicate d 0]+coordss n d = concat [ map (++ replicate t n) (coordss (n-1) (d-t)) | t <- [0..d] ]+
+ test/Walk.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE GADTs, RankNTypes, TypeOperators, FlexibleInstances #-}+module Walk where++import Test.QuickCheck+import Data.List+import Control.Monad+import Control.Applicative++import Induction.Structural++import Trace+import EnvTypes+import Env+import Util++construct :: VarMap -> Hyp -> Gen [Repr']+construct vm (hyps,tms) = mapM (construct1 hyps vm) tms++construct1 :: [(String,Ty')] -> VarMap -> Tm -> Gen Repr'+construct1 hyps vm tm = case tm of+ Var s -> case lookup s vm of+ Just u -> return u+ Nothing -> case lookup s hyps of+ Just t -> arbFromType' t+ -- ^ if this is missing, generate a new one with arbitrary!!+ Nothing -> error $ show s ++ " missing!" ++ show vm ++ "," ++ show hyps+ Con s tms -> mkCon s <$> mapM (construct1 hyps vm) tms+ Fun{} -> error "exponentials not supported"++-- | We can only pick 1-2 hyps, otherwise we get crazy exponential behaviour+pickHyps :: [a] -> Gen [a]+pickHyps [] = return []+pickHyps hs = do+ let n = length hs - 1+ i <- choose (0,n)+ i' <- choose (0,n)+ two <- arbitrary+ return $ if two && i /= i' then [hs !! i,hs !! i'] else [hs !! i]++-- TODO: Can this be reorganized so we get an efficient unrolling?+makeTracer :: [Repr'] -> [Oblig] -> Gen (Trace [Repr'])+makeTracer args parts = go parts+ where+ go p = case p of+ Obligation _ hyps conc:is+ | length args == length conc -> case zipWithM match args conc of+ Nothing -> go is+ Just vms -> halfSize $ \ _ -> do+ -- Throw away some hypotheses+ hyps' <- pickHyps hyps+ argss' <- mapM (construct (concat vms)) hyps'+ forks <- mapM (`makeTracer` parts) argss'+ return (Fork args forks)+ | otherwise -> mk_error $ "Unequal lengths (conc=" +++ intercalate "," (map (render . linTerm style) conc) ++ ")"+ _ -> mk_error "No case"+ where+ mk_error msg = error $+ "makeTracer " ++ show args ++ " : " ++ msg ++ "!" +++ "\nDetails: args = (" ++ intercalate " , " (map showRepr' args) ++ ")" +++ "\nCheck Env.match and EnvTypes.==?, or case is missing from schema:" +++ "\n" ++ showOblig parts