add (custom) de bruijn-y conversion and alpha equivalence check

This commit is contained in:
jackjohn7 2026-05-16 17:49:12 -05:00
parent 99a18df155
commit dde7cfc7a4
5 changed files with 164 additions and 3 deletions

View file

@ -1,7 +1,7 @@
module EvaluationSpec (spec) where
import Data.Set (Set, empty, fromList)
import Evaluation (beta, bind, freeIn, freeVars, freshName, subst)
import Evaluation (alphaEquiv, beta, bind, freeIn, freeVars, freshName, subst)
import Parser (Expr (Abstraction, Application, Variable), parse)
import Test.Hspec
@ -106,6 +106,98 @@ spec = do
it "bind with abstraction argument substitutes correctly" $
-- (λx. x) applied to (λi. i) → (λi. i)
bind absI absI `shouldBe` Just absI
describe "alphaEquiv" $ do
-- Reflexivity
it "a variable is alpha-equivalent to itself" $
alphaEquiv (Variable "x") (Variable "x") `shouldBe` True
it "an abstraction is alpha-equivalent to itself" $
alphaEquiv (Abstraction "x" (Variable "x")) (Abstraction "x" (Variable "x")) `shouldBe` True
it "a nested term is alpha-equivalent to itself" $
alphaEquiv (Abstraction "x" (Abstraction "y" (Application (Variable "x") (Variable "y"))))
(Abstraction "x" (Abstraction "y" (Application (Variable "x") (Variable "y"))))
`shouldBe` True
-- Renaming bound variables (True)
it "identity with different binder names: λx. x ≡ λy. y" $
alphaEquiv (Abstraction "x" (Variable "x"))
(Abstraction "y" (Variable "y"))
`shouldBe` True
it "constant body with different binder names: λx. z ≡ λy. z" $
alphaEquiv (Abstraction "x" (Variable "z"))
(Abstraction "y" (Variable "z"))
`shouldBe` True
it "nested: λx. λy. x ≡ λa. λb. a (outer reference)" $
alphaEquiv (Abstraction "x" (Abstraction "y" (Variable "x")))
(Abstraction "a" (Abstraction "b" (Variable "a")))
`shouldBe` True
it "nested: λx. λy. y ≡ λa. λb. b (inner reference)" $
alphaEquiv (Abstraction "x" (Abstraction "y" (Variable "y")))
(Abstraction "a" (Abstraction "b" (Variable "b")))
`shouldBe` True
it "nested application body: λx. λy. x y ≡ λa. λb. a b" $
alphaEquiv (Abstraction "x" (Abstraction "y" (Application (Variable "x") (Variable "y"))))
(Abstraction "a" (Abstraction "b" (Application (Variable "a") (Variable "b"))))
`shouldBe` True
it "self-application body: λx. x x ≡ λy. y y" $
alphaEquiv (Abstraction "x" (Application (Variable "x") (Variable "x")))
(Abstraction "y" (Application (Variable "y") (Variable "y")))
`shouldBe` True
-- Symmetry
it "alpha equivalence is symmetric" $
alphaEquiv (Abstraction "x" (Variable "x"))
(Abstraction "y" (Variable "y"))
`shouldBe`
alphaEquiv (Abstraction "y" (Variable "y"))
(Abstraction "x" (Variable "x"))
-- Free variables are name-sensitive (False)
it "distinct free variables are not equivalent" $
alphaEquiv (Variable "x") (Variable "y") `shouldBe` False
it "different free variables in abstraction body: λx. y ≢ λx. z" $
alphaEquiv (Abstraction "x" (Variable "y"))
(Abstraction "x" (Variable "z"))
`shouldBe` False
it "bound body vs free body: λx. x ≢ λx. y" $
alphaEquiv (Abstraction "x" (Variable "x"))
(Abstraction "x" (Variable "y"))
`shouldBe` False
-- Structurally different terms (False)
it "abstraction vs bare variable are not equivalent" $
alphaEquiv (Variable "x")
(Abstraction "x" (Variable "x"))
`shouldBe` False
it "different depths: λx. x ≢ λx. λy. y" $
alphaEquiv (Abstraction "x" (Variable "x"))
(Abstraction "x" (Abstraction "y" (Variable "y")))
`shouldBe` False
it "argument order matters in application: x y ≢ y x" $
alphaEquiv (Application (Variable "x") (Variable "y"))
(Application (Variable "y") (Variable "x"))
`shouldBe` False
-- Shadowing / name reuse
it "consistent inner shadowing: λx. λx. x ≡ λy. λy. y" $
alphaEquiv (Abstraction "x" (Abstraction "x" (Variable "x")))
(Abstraction "y" (Abstraction "y" (Variable "y")))
`shouldBe` True
it "inner shadow differs from outer reference: λx. λx. x ≢ λx. λy. x" $
alphaEquiv (Abstraction "x" (Abstraction "x" (Variable "x")))
(Abstraction "x" (Abstraction "y" (Variable "x")))
`shouldBe` False
-- Free variables unchanged across binder rename
it "free variable in body is preserved: λx. y ≡ λz. y" $
alphaEquiv (Abstraction "x" (Variable "y"))
(Abstraction "z" (Variable "y"))
`shouldBe` True
it "free vs bound distinction preserved: λy. y ≢ λy. z" $
alphaEquiv (Abstraction "y" (Variable "y"))
(Abstraction "y" (Variable "z"))
`shouldBe` False
describe "beta reduction" $ do
-- Normal forms (no redex)
it "returns Nothing for a bare variable" $