Skip to content

Commit a2be607

Browse files
author
Thomas Mahler
committed
generalize compiler wrt Y and Fix
1 parent 470d1be commit a2be607

File tree

2 files changed

+60
-14
lines changed

2 files changed

+60
-14
lines changed

src/CCC/Compiler.hs

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ compileIntExpr env expr = do
7979
-- \x. e | curry (absCCC (\(x,y). e)) | Lam p body -> SFun (\v -> compile body[p:=v])
8080
-- f g | apply . (f' △ g') | applySVal (compile f) (compile g) at meta-level
8181
-- a ⊕ b | op . (a' △ b') | Comp op (fanC (compile a) (compile b))
82-
-- y (\f a1...an. b) | Fix step . (v1 △ ... △ vn) | compileYGeneric -> Fix with context c = (f, input)
82+
-- fixOp (\f a1...an. b) | Fix step . (v1 △ ... △ vn) | compileFixGeneric -> Fix with context c = (f, input)
8383

8484
compileExpr :: Environment -> [(String, SVal)] -> Expr -> Either String SVal
8585
compileExpr env localEnv = \case
@@ -95,8 +95,10 @@ compileExpr env localEnv = \case
9595
case lookup name env of
9696
Just expr -> compileExpr env localEnv expr
9797
Nothing -> compileBuiltin name
98-
-- y is compiled structurally to Fix rather than treated as a generic function
99-
App (Var "y") stepExpr -> compileY env localEnv stepExpr
98+
-- Structural recursion is detected by a fixpoint operator head (y/fix/alias)
99+
-- and compiled to CatExpr Fix instead of being interpreted as a regular function.
100+
App fixHead stepExpr
101+
| isFixOperator env localEnv fixHead -> compileFix env localEnv stepExpr
100102
-- λx. e ↦ Haskell closure; beta-reduction is deferred to apply time (NBE)
101103
-- absCCC (λx. λy. e) = curry (absCCC (λ(x,y). e))
102104
Lam param body -> Right $ SFun $ \argVal -> compileExpr env ((param, argVal) : localEnv) body
@@ -133,40 +135,40 @@ data IntArgs input where
133135
data SomeIntArgs where
134136
SomeIntArgs :: IntArgs input -> SomeIntArgs
135137

136-
compileY :: Environment -> [(String, SVal)] -> Expr -> Either String SVal
137-
compileY env outerLocal = \case
138+
compileFix :: Environment -> [(String, SVal)] -> Expr -> Either String SVal
139+
compileFix env outerLocal = \case
138140
Lam fName stepExpr ->
139141
case collectLams stepExpr of
140142
(params, body) ->
141143
case mkIntArgs (length params) of
142-
Just (SomeIntArgs args) -> compileYGeneric env outerLocal args fName params body
143-
Nothing -> Left "y expects at least one integer argument"
144-
_ -> Left "y expects a lambda step function"
144+
Just (SomeIntArgs args) -> compileFixGeneric env outerLocal args fName params body
145+
Nothing -> Left "fix expects at least one integer argument"
146+
_ -> Left "fix expects a lambda step function"
145147

146-
compileYGeneric ::
148+
compileFixGeneric ::
147149
Environment ->
148150
[(String, SVal)] ->
149151
IntArgs input ->
150152
String ->
151153
[String] ->
152154
Expr ->
153155
Either String SVal
154-
compileYGeneric env outerLocal args fName params body = buildCurried args []
156+
compileFixGeneric env outerLocal args fName params body = buildCurried args []
155157
where
156158
buildCurried :: IntArgs remaining -> [Closed Integer] -> Either String SVal
157159
buildCurried OneArg acc =
158160
Right $ SFun $ \case
159161
SInt arg -> do
160162
applied <- applyFix (acc ++ [arg])
161163
Right (SInt applied)
162-
_ -> Left "y expects Integer argument"
164+
_ -> Left "fix expects Integer argument"
163165
buildCurried (MoreArgs rest) acc =
164166
Right $ SFun $ \case
165167
SInt arg -> buildCurried rest (acc ++ [arg])
166-
_ -> Left "y expects Integer argument"
168+
_ -> Left "fix expects Integer argument"
167169

168170
-- Encodes the Fix rule:
169-
-- y (λf a₁…aₙ. body) at inputs (v₁,…,vₙ)
171+
-- fix (λf a₁…aₙ. body) at inputs (v₁,…,vₙ)
170172
-- = Comp (Fix stepBody) (v₁ △ … △ vₙ)
171173
-- Context for stepBody is c = (CatExpr input Integer, input):
172174
-- f ↦ buildRecFun → Apply ∘ fanC Fst (a₁ △ … △ aₙ)
@@ -182,6 +184,26 @@ compileYGeneric env outerLocal args fName params body = buildCurried args []
182184
stepBody <- compileRecExpr env outerLocal local body >>= expectRInt "Recursive body must compile to Integer"
183185
Right (Closed (Comp (Fix stepBody) paramTuple))
184186

187+
-- Detects whether an expression head denotes the structural fixpoint operator.
188+
-- Recognized forms:
189+
-- - direct builtins: y, fix
190+
-- - environment aliases that resolve to those names
191+
-- Local bindings shadow fixpoint names.
192+
isFixOperator :: Environment -> [(String, SVal)] -> Expr -> Bool
193+
isFixOperator env localEnv = go []
194+
where
195+
go _ (Int _) = False
196+
go _ (Lam _ _) = False
197+
go _ (App _ _) = False
198+
go seen (Var name)
199+
| name `elem` map fst localEnv = False
200+
| name == "y" || name == "fix" = True
201+
| name `elem` seen = False
202+
| otherwise =
203+
case lookup name env of
204+
Just expr -> go (name : seen) expr
205+
Nothing -> False
206+
185207
compileRecExpr :: Environment -> [(String, SVal)] -> [(String, RVal c)] -> Expr -> Either String (RVal c)
186208
compileRecExpr env outerLocal local = \case
187209
-- n ↦ IntConst n
@@ -348,7 +370,8 @@ compileBuiltin "geq" = Right (sIntCompare Geq)
348370
compileBuiltin "if" = Right sIfFun
349371
compileBuiltin "true" = Right (SBool (Closed T))
350372
compileBuiltin "false" = Right (SBool (Closed F))
351-
compileBuiltin "y" = Left "Recursion via y is not yet structurally compiled"
373+
compileBuiltin "y" = Left "Recursion via fixpoint operators is structurally compiled"
374+
compileBuiltin "fix" = Left "Recursion via fixpoint operators is structurally compiled"
352375
compileBuiltin "/" = Left "Division is not currently supported in CatExpr compilation"
353376
compileBuiltin name = Left ("Unbound variable: " ++ name)
354377

test/CCCCompilerSpec.hs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,29 @@ spec = do
5858
show morph `shouldSatisfy` isInfixOf "Fix"
5959
interp morph () `shouldBe` 3
6060

61+
it "compiles recursion structurally via fix alias" $ do
62+
let expr = App (App (Var "fix") (Lam "f" (Lam "n"
63+
(App (App (App (Var "if") (App (Var "is0") (Var "n")))
64+
(Int 1))
65+
(App (App (Var "*") (Var "n"))
66+
(App (Var "f") (App (Var "sub1") (Var "n"))))))))
67+
(Int 5)
68+
morph = compileNumExpr [] expr :: CatExpr () Integer
69+
show morph `shouldSatisfy` isInfixOf "Fix"
70+
interp morph () `shouldBe` 120
71+
72+
it "compiles recursion structurally via environment alias" $ do
73+
let expr = App (App (Var "myFix") (Lam "f" (Lam "n"
74+
(App (App (App (Var "if") (App (Var "is0") (Var "n")))
75+
(Int 1))
76+
(App (App (Var "*") (Var "n"))
77+
(App (Var "f") (App (Var "sub1") (Var "n"))))))))
78+
(Int 5)
79+
env = [("myFix", Var "y")]
80+
morph = compileNumExpr env expr :: CatExpr () Integer
81+
show morph `shouldSatisfy` isInfixOf "Fix"
82+
interp morph () `shouldBe` 120
83+
6184
describe "CCC.Compiler environment helpers" $ do
6285
it "returns Right for numeric bindings" $ do
6386
case tryCompileVar [("k", Int 11)] "k" of

0 commit comments

Comments
 (0)