Skip to content

Commit b6a4537

Browse files
committed
de bruijn & co...
1 parent 405c3fc commit b6a4537

File tree

5 files changed

+150
-18
lines changed

5 files changed

+150
-18
lines changed

app/Main.hs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import System.IO (hSetEncoding, stdin, stdout, utf8)
1515
import HhiReducer
1616
import Kiselyov
1717
import System.TimeIt
18-
import Text.RawString.QQ
18+
import Text.RawString.QQ
19+
import qualified Data.Bifunctor
1920

2021

2122
printGraph :: ST s (STRef s (Graph s)) -> ST s String
@@ -34,7 +35,7 @@ main = do
3435
hSetEncoding stdout utf8 -- this is required to handle UTF-8 characters like λ
3536

3637
--let testSource = "main = (\\x y -> + x x) 3 4"
37-
mapM_ showCompilations [sqr] --, factorial, fibonacci, ackermann, tak]
38+
mapM_ showCompilations [sqr, factorial] --, fibonacci, ackermann, tak]
3839
--demo
3940

4041
type SourceCode = String
@@ -51,7 +52,7 @@ tak = [r|
5152
main = tak 7 4 2 --18 6 3
5253
|]
5354

54-
ackermann :: SourceCode
55+
ackermann :: SourceCode
5556
ackermann = [r|
5657
ack = y(λf n m. if (is0 n) (+ m 1) (if (is0 m) (f (sub1 n) 1) (f (sub1 n) (f n (sub1 m)))))
5758
main = ack 2 2
@@ -75,6 +76,12 @@ showCompilations source = do
7576
putStrLn "The parsed environment of named lambda expressions:"
7677
mapM_ print env
7778
putStrLn ""
79+
putStrLn "The expressions in de Bruijn notation:"
80+
mapM_ (print . Data.Bifunctor.second deBruijn) env
81+
82+
putStrLn "applying plain compilation:"
83+
print $ compilePlain env
84+
putStrLn ""
7885

7986
let expr = compile env abstractToSKI
8087
putStrLn "The main expression compiled to SICKBY combinator expressions:"

kiselyov.md

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ My implementation closely follows Ben Lynn's implementation of Kiselyov's algori
3737
My parser can parse programs of a very rudimentary language that is basically just pure λ-calculus plus integers. Here is an example:
3838

3939
```haskell
40-
sqr = \x -> * x x
40+
sqr = λx. * x x
4141
main = sqr 3
4242
```
4343

@@ -64,6 +64,90 @@ type Environment = [(String, Expr)]
6464

6565
Now we can define a compiler that translates such λ-expressions to combinator terms.
6666

67+
Our journey begins by translating λ-expressions to a data type `DB` which is quite similar to the λ-calculus terms but uses indices instead of variable names. This is done by the function `deBruijn`:
68+
69+
```haskell
70+
data Peano = Succ Peano | Zero deriving Show
71+
data DB = N Peano | L DB | A DB DB | Free String | IN Integer deriving Show
72+
73+
deBruijn :: Expr -> DB
74+
deBruijn = go [] where
75+
go binds = \case
76+
Var x -> maybe (Free x) N $ index x binds
77+
Lam x t -> L $ go (x:binds) t
78+
App t u -> A (go binds t) (go binds u)
79+
Int i -> IN i
80+
81+
index :: Eq a => a -> [a] -> Maybe Peano
82+
index x xs = lookup x $ zip xs $ iterate Succ Zero
83+
```
84+
85+
Lets see how this works on our example `sqr` and `main` functions:
86+
87+
```haskell
88+
let source = [r|
89+
sqr = \x. * x x
90+
main = sqr 3
91+
|]
92+
let env = parseEnvironment source
93+
putStrLn "The parsed environment of named lambda expressions:"
94+
mapM_ print env
95+
putStrLn ""
96+
putStrLn "The expressions in de Bruijn notation:"
97+
mapM_ (print . Data.Bifunctor.second deBruijn) env
98+
```
99+
This will produce the following output:
100+
101+
```haskell
102+
The parsed environment of named lambda expressions:
103+
("sqr", Lam "x" (App (App (Var "*") (Var "x")) (Var "x")))
104+
("main", App (Var "sqr") (Int 3))
105+
106+
The expressions in de Bruijn notation:
107+
("sqr", L (A (A (Free "*") (N Zero)) (N Zero)))
108+
("main", A (Free "sqr") (IN 3))
109+
```
110+
111+
It's easy to see that the de Bruijn notation is just a different representation of the λ-calculus terms. The only difference is that the variable names are replaced by indices.
112+
This is quite helpful as it allows to systematically adress variables by their respective position without having to deal with arbitrary variable names.
113+
114+
But why are we using Peano numbers for the indices? Why not just use integers?
115+
Well it's definitely possible to [use integers instead of Peano numbers](https://crypto.stanford.edu/~blynn/lambda/cl.html).
116+
But there is a good reason to use Peano numbers in our case:
117+
In the subsequent compilation steps we want to be able to do pattern matching on the indices. This is not possible with integers but it is possible with Peano numbers, because they are defined as an algebraic data type:
118+
119+
```haskell
120+
data Peano = Succ Peano | Zero
121+
```
122+
123+
Now we'll take a look at the next step in the compilation process. The function `convert` translates the de Bruijn notation to a data type `CL` which represents the combinator terms.
124+
125+
126+
127+
```haskell
128+
convert :: ((Int, CL) -> (Int, CL) -> CL) -> [(String, Expr)] -> DB -> (Int, CL)
129+
convert (#) env = \case
130+
N Zero -> (1, Com I)
131+
N (Succ e) -> (n + 1, (0, Com K) # t) where t@(n, _) = rec $ N e
132+
L e -> case rec e of
133+
(0, d) -> (0, Com K :@ d)
134+
(n, d) -> (n - 1, d)
135+
A e1 e2 -> (max n1 n2, t1 # t2) where
136+
t1@(n1, _) = rec e1
137+
t2@(n2, _) = rec e2
138+
IN i -> (0, INT i)
139+
Free s -> convertVar (#) env s
140+
where rec = convert (#) env
141+
```
142+
143+
xxx
144+
145+
146+
147+
148+
149+
150+
67151
Combinator terms are defined as follows:
68152

69153
```haskell
@@ -75,6 +159,8 @@ data Combinator = I | K | S | B | C | Y | R | B' | C' | S' | T |
75159
```
76160

77161

162+
163+
78164
## to be continued...
79165

80166
## performance comparison

src/CLTerm.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module CLTerm
1414
import Parser (Expr(..))
1515

1616
data CL = Com Combinator | INT Integer | CL :@ CL
17+
1718
instance Show CL where
1819
showsPrec :: Int -> CL -> ShowS
1920
showsPrec p = \case

src/Kiselyov.hs

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module Kiselyov
33
(
44
deBruijn,
55
bulkOpt,
6+
compilePlain,
67
compileBulk,
78
compileEta,
89
compileBulkLinear,
@@ -22,20 +23,54 @@ I've only added minor changes to fit it into my codebase.
2223
E.g. I've added access to the environment of named lambda expressions for free variables.
2324
--}
2425

25-
data Peano = Su Peano | Z deriving Show
26+
data Peano = Succ Peano | Zero deriving Show
2627
data DB = N Peano | L DB | A DB DB | Free String | IN Integer deriving Show
2728

28-
index :: Eq a => a -> [a] -> Maybe Peano
29-
index x xs = lookup x $ zip xs $ iterate Su Z
30-
3129
deBruijn :: Expr -> DB
3230
deBruijn = go [] where
3331
go binds = \case
3432
Var x -> maybe (Free x) N $ index x binds
3533
Lam x t -> L $ go (x:binds) t
36-
t `App` u -> A (go binds t) (go binds u)
34+
App t u -> A (go binds t) (go binds u)
3735
Int i -> IN i
3836

37+
index :: Eq a => a -> [a] -> Maybe Peano
38+
index x xs = lookup x $ zip xs $ iterate Succ Zero
39+
40+
convert :: ((Int, CL) -> (Int, CL) -> CL) -> [(String, Expr)] -> DB -> (Int, CL)
41+
convert (#) env = \case
42+
N Zero -> (1, Com I)
43+
N (Succ e) -> (n + 1, (0, Com K) # t) where t@(n, _) = rec $ N e
44+
L e -> case rec e of
45+
(0, d) -> (0, Com K :@ d)
46+
(n, d) -> (n - 1, d)
47+
A e1 e2 -> (max n1 n2, t1 # t2) where
48+
t1@(n1, _) = rec e1
49+
t2@(n2, _) = rec e2
50+
IN i -> (0, INT i)
51+
Free s -> convertVar (#) env s
52+
where rec = convert (#) env
53+
54+
-- | convert a free variable to a combinator.
55+
-- first we try to find a definition in the environment.
56+
-- if that fails, we assume it is a SICKBY combinator.
57+
convertVar :: ((Int, CL) -> (Int, CL) -> CL) -> [(String, Expr)] -> String -> (Int, CL)
58+
convertVar (#) env s
59+
| Just t <- lookup s env = convert (#) env (deBruijn t)
60+
| otherwise = (0, Com (fromString s))
61+
62+
plain :: Environment -> DB -> (Int, CL)
63+
plain = convert (#) where
64+
(0 , d1) # (0 , d2) = d1 :@ d2
65+
(0 , d1) # (n , d2) = (0, Com B :@ d1) # (n - 1, d2)
66+
(n , d1) # (0 , d2) = (0, Com R :@ d2) # (n - 1, d1)
67+
(n1, d1) # (n2, d2) = (n1 - 1, (0, Com S) # (n1 - 1, d1)) # (n2 - 1, d2)
68+
69+
compilePlain :: Environment -> CL
70+
compilePlain env = case lookup "main" env of
71+
Nothing -> error "main function missing"
72+
Just main -> snd $ plain env (deBruijn main)
73+
3974
bulk :: Combinator -> Int -> CL
4075
bulk c 1 = Com c
4176
bulk c n = Com $ BulkCom (show c) n
@@ -53,17 +88,17 @@ compileBulk env = case lookup "main" env of
5388
compileBulkLinear :: Environment -> CL
5489
compileBulkLinear env = case lookup "main" env of
5590
Nothing -> error "main function missing"
56-
Just main -> snd $ bulkOpt breakBulkLinear env (deBruijn main)
91+
Just main -> snd $ bulkOpt breakBulkLinear env (deBruijn main)
5792

5893
compileBulkLog :: Environment -> CL
5994
compileBulkLog env = case lookup "main" env of
6095
Nothing -> error "main function missing"
61-
Just main -> snd $ bulkOpt breakBulkLog env (deBruijn main)
96+
Just main -> snd $ bulkOpt breakBulkLog env (deBruijn main)
6297

6398
convertBool :: (([Bool], CL) -> ([Bool], CL) -> CL) -> Environment -> DB -> ([Bool], CL)
6499
convertBool (#) env = \case
65-
N Z -> ([True], Com I)
66-
N (Su e) -> (False:g, d) where (g, d) = rec env (N e)
100+
N Zero -> ([True], Com I)
101+
N (Succ e) -> (False:g, d) where (g, d) = rec env (N e)
67102
L e -> case rec env e of
68103
([], d) -> ([], Com K :@ d)
69104
(False:g, d) -> (g, ([], Com K) # (g, d))
@@ -75,6 +110,9 @@ convertBool (#) env = \case
75110
IN i -> ([False], INT i)
76111
where rec = convertBool (#)
77112

113+
-- | convert a free variable to a combinator.
114+
-- first we try to find a definition in the environment.
115+
-- if that fails, we assume it is a SICKBY combinator.
78116
convertFree :: (([Bool], CL) -> ([Bool], CL) -> CL) -> [(String, Expr)] -> String -> ([Bool], CL)
79117
convertFree (#) env s
80118
| Just t <- lookup s env = convertBool (#) env (deBruijn t)
@@ -120,8 +158,8 @@ bulkLookup s env bulkFun = case lookup s env of
120158

121159
bulkOpt :: (Combinator -> Int -> CL) -> Environment -> DB -> ([Bool], CL)
122160
bulkOpt bulkFun env = \case
123-
N Z -> ([True], Com I)
124-
N (Su e) -> first (False:) $ rec env $ N e
161+
N Zero -> ([True], Com I)
162+
N (Succ e) -> first (False:) $ rec env $ N e
125163
L e -> case rec env e of
126164
([], d) -> ([], Com K :@ d)
127165
(False:g, d) -> ([], Com K) ## (g, d)

src/Parser.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ data Expr
1919
| Var String
2020
| Int Integer
2121
| Lam String Expr
22-
deriving (Eq) --, Show)
22+
deriving (Eq, Show)
2323

24-
instance Show Expr where
25-
show = toString
24+
-- instance Show Expr where
25+
-- show = toString
2626

2727

2828
toString :: Expr -> String

0 commit comments

Comments
 (0)