add (custom) de bruijn-y conversion and alpha equivalence check
This commit is contained in:
parent
99a18df155
commit
dde7cfc7a4
5 changed files with 164 additions and 3 deletions
|
|
@ -13,7 +13,8 @@ executable lamb
|
||||||
import: warnings
|
import: warnings
|
||||||
main-is: Main.hs
|
main-is: Main.hs
|
||||||
other-modules: Parser,
|
other-modules: Parser,
|
||||||
Evaluation
|
Evaluation,
|
||||||
|
Bruijn
|
||||||
build-depends: base, containers, parsec
|
build-depends: base, containers, parsec
|
||||||
hs-source-dirs: src
|
hs-source-dirs: src
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
|
|
@ -25,7 +26,8 @@ test-suite lamb-tests
|
||||||
other-modules: Parser,
|
other-modules: Parser,
|
||||||
ParserSpec,
|
ParserSpec,
|
||||||
Evaluation,
|
Evaluation,
|
||||||
EvaluationSpec
|
EvaluationSpec,
|
||||||
|
BruijnSpec
|
||||||
build-depends: base, containers, hspec, parsec
|
build-depends: base, containers, hspec, parsec
|
||||||
build-tool-depends: hspec-discover:hspec-discover
|
build-tool-depends: hspec-discover:hspec-discover
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
|
|
|
||||||
15
src/Bruijn.hs
Normal file
15
src/Bruijn.hs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
module Bruijn where
|
||||||
|
|
||||||
|
import Data.Map (Map, empty, insert, lookup, size)
|
||||||
|
import Parser (Expr (Abstraction, Application, Variable))
|
||||||
|
|
||||||
|
-- | Convert to (unorthodox) De Bruijn form
|
||||||
|
toDeBruijn :: Expr -> Expr
|
||||||
|
toDeBruijn e = innerDB e empty
|
||||||
|
|
||||||
|
innerDB :: Expr -> Map String Int -> Expr
|
||||||
|
innerDB v@(Variable name) d = case Data.Map.lookup name d of
|
||||||
|
Just n -> Variable (show n)
|
||||||
|
Nothing -> v
|
||||||
|
innerDB (Abstraction binder body) d = Abstraction (show $ size d) (innerDB body (insert binder (size d) d))
|
||||||
|
innerDB (Application f arg) d = Application (innerDB f d) (innerDB arg d)
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
module Evaluation where
|
module Evaluation where
|
||||||
|
|
||||||
|
import Bruijn (toDeBruijn)
|
||||||
import Data.Set
|
import Data.Set
|
||||||
import Parser (Expr (Abstraction, Application, Variable), vName)
|
import Parser (Expr (Abstraction, Application, Variable), vName)
|
||||||
|
|
||||||
|
|
@ -52,3 +53,6 @@ run :: Expr -> Expr
|
||||||
run ex = case beta ex of
|
run ex = case beta ex of
|
||||||
Just ex' -> run ex'
|
Just ex' -> run ex'
|
||||||
Nothing -> ex
|
Nothing -> ex
|
||||||
|
|
||||||
|
alphaEquiv :: Expr -> Expr -> Bool
|
||||||
|
alphaEquiv e1 e2 = (toDeBruijn e1) == (toDeBruijn e2)
|
||||||
|
|
|
||||||
48
test/BruijnSpec.hs
Normal file
48
test/BruijnSpec.hs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
module BruijnSpec (spec) where
|
||||||
|
|
||||||
|
import Bruijn (toDeBruijn)
|
||||||
|
import Parser (Expr (Abstraction, Application, Variable))
|
||||||
|
import Test.Hspec
|
||||||
|
|
||||||
|
spec :: Spec
|
||||||
|
spec = do
|
||||||
|
describe "toDeBruijn" $ do
|
||||||
|
|
||||||
|
-- Free variables
|
||||||
|
it "leaves a lone free variable unchanged" $
|
||||||
|
toDeBruijn (Variable "x") `shouldBe` Variable "x"
|
||||||
|
it "leaves free variables in abstraction body unchanged" $
|
||||||
|
toDeBruijn (Abstraction "x" (Variable "y"))
|
||||||
|
`shouldBe` Abstraction "0" (Variable "y")
|
||||||
|
|
||||||
|
-- Single abstraction
|
||||||
|
it "converts bound variable in identity" $
|
||||||
|
toDeBruijn (Abstraction "x" (Variable "x"))
|
||||||
|
`shouldBe` Abstraction "0" (Variable "0")
|
||||||
|
it "leaves free variable in abstraction body unchanged" $
|
||||||
|
toDeBruijn (Abstraction "x" (Variable "z"))
|
||||||
|
`shouldBe` Abstraction "0" (Variable "z")
|
||||||
|
|
||||||
|
-- Nested abstractions
|
||||||
|
it "inner binder resolves to depth 1" $
|
||||||
|
toDeBruijn (Abstraction "x" (Abstraction "y" (Variable "y")))
|
||||||
|
`shouldBe` Abstraction "0" (Abstraction "1" (Variable "1"))
|
||||||
|
it "outer binder resolves to depth 0 from within nested scope" $
|
||||||
|
toDeBruijn (Abstraction "x" (Abstraction "y" (Variable "x")))
|
||||||
|
`shouldBe` Abstraction "0" (Abstraction "1" (Variable "0"))
|
||||||
|
it "outermost of three binders resolves correctly" $
|
||||||
|
toDeBruijn (Abstraction "x" (Abstraction "y" (Abstraction "z" (Variable "x"))))
|
||||||
|
`shouldBe` Abstraction "0" (Abstraction "1" (Abstraction "2" (Variable "0")))
|
||||||
|
|
||||||
|
-- Application
|
||||||
|
it "converts self-application" $
|
||||||
|
toDeBruijn (Abstraction "x" (Application (Variable "x") (Variable "x")))
|
||||||
|
`shouldBe` Abstraction "0" (Application (Variable "0") (Variable "0"))
|
||||||
|
it "converts independent scopes in application" $
|
||||||
|
toDeBruijn (Application (Abstraction "x" (Variable "x")) (Abstraction "y" (Variable "y")))
|
||||||
|
`shouldBe` Application (Abstraction "0" (Variable "0")) (Abstraction "0" (Variable "0"))
|
||||||
|
|
||||||
|
-- Alpha equivalence
|
||||||
|
it "alpha-equivalent terms produce identical output" $
|
||||||
|
toDeBruijn (Abstraction "x" (Variable "x"))
|
||||||
|
`shouldBe` toDeBruijn (Abstraction "y" (Variable "y"))
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
module EvaluationSpec (spec) where
|
module EvaluationSpec (spec) where
|
||||||
|
|
||||||
import Data.Set (Set, empty, fromList)
|
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 Parser (Expr (Abstraction, Application, Variable), parse)
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
|
|
||||||
|
|
@ -106,6 +106,98 @@ spec = do
|
||||||
it "bind with abstraction argument substitutes correctly" $
|
it "bind with abstraction argument substitutes correctly" $
|
||||||
-- (λx. x) applied to (λi. i) → (λi. i)
|
-- (λx. x) applied to (λi. i) → (λi. i)
|
||||||
bind absI absI `shouldBe` Just absI
|
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
|
describe "beta reduction" $ do
|
||||||
-- Normal forms (no redex)
|
-- Normal forms (no redex)
|
||||||
it "returns Nothing for a bare variable" $
|
it "returns Nothing for a bare variable" $
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue