{-# LANGUAGE MultiParamTypeClasses, DeriveGeneric, DeriveDataTypeable #-}
module TestSubstBind where
import Test.Tasty
import Unbound.Generics.LocallyNameless
import Data.Typeable (Typeable)
import GHC.Generics (Generic)
import Test.QuickCheck
import Test.Tasty.QuickCheck (testProperty)
import Unbound.Generics.LocallyNameless.Unsafe (unsafeUnbind)
import AlphaProperties
type Var = Name Expr
data Expr = V Var | Lam (Bind Var Expr) | I Int | App Expr Expr
deriving (Generic, Typeable, Show)
instance Alpha Expr
instance Subst Expr Expr where
isvar (V x) = Just (SubstName x)
isvar _ = Nothing
instance Arbitrary Expr where
arbitrary = sized arbitrarySizedExpr
shrink (I i) = I <$> shrink i
shrink (Lam bndExp) =
(substBind bndExp <$> [I 0, V x, V y, V z]) -- does the problem persist with the binder removed?
++ (Lam <$> underBinder shrink bndExp) -- shrink under the binder
shrink (f `App` a) = [f, a] ++ (App <$> shrink f <*> shrink a)
shrink _ = []
underBinder :: (Monad m) => (Expr -> m Expr) -> Bind Var Expr -> m (Bind Var Expr)
underBinder op bndExp = let
(name, bod) = unsafeUnbind bndExp -- Should be safe since no freshnames are invoked
in do
bod' <- op bod
pure $ bind name bod'
arbitrarySizedExpr :: Int -> Gen Expr
arbitrarySizedExpr i | i < 1 = do
n <- arbitrary
var <- elements [x,y,z]
elements [I n, V $ var]
arbitrarySizedExpr i = do
rest <- arbitrarySizedExpr (i - 1)
var <- elements [x,y,z]
n <- arbitrary
f <- arbitrarySizedExpr (i `div` 2) -- TODO: reorganize better
a <- arbitrarySizedExpr (i `div` 2)
elements [Lam $ bind var rest, f `App` a, I n, V $ var]
x,y,z :: Var
x = s2n "x"
y = s2n "y"
z = s2n "z"
smallStep :: Expr -> Maybe Expr
smallStep ((Lam bndBod) `App` a) = Just $ substBind bndBod a
smallStep (f `App` a) =
case (smallStep f, smallStep a) of
(Nothing, Nothing) -> Nothing
(Just f', _) -> Just $ f' `App` a
(Nothing, Just a') -> Just $ f `App` a'
smallStep (Lam bndBod) = Lam <$> underBinder smallStep bndBod
smallStep _ = Nothing -- no step
smallStep' :: (Fresh m) => Expr -> m (Maybe Expr)
smallStep' ((Lam bndBod) `App` a) = do
(name, bod) <- unbind bndBod
pure $ Just $ subst name a bod
smallStep' (f `App` a) = do
mf' <- smallStep' f
ma' <- smallStep' a
case (mf', ma') of
(Nothing, Nothing) -> pure $ Nothing
(Just f', _) -> pure $ Just $ f' `App` a
(Nothing, Just a') -> pure $ Just $ f `App` a'
smallStep' (Lam bndBod) = do
(name, bod) <- unbind bndBod
mbod' <- smallStep' bod
case mbod' of
Nothing -> pure $ Nothing
Just bod' -> pure $ Just $ Lam $ bind name bod'
smallStep' _ = pure $ Nothing -- no step
expbindProp :: Expr -> Property
expbindProp e = smallStep e =~= runFreshM (smallStep' e)
test_substBind :: TestTree
test_substBind = testGroup "substBind"
[testProperty "substBind matches unbind subst" $ expbindProp]