data Deb = Zero | Succ Deb | Lam Deb | App Deb Deb deriving Show
infixl 5 :#
data Com = Com :# Com | S | I | C | K | B | Sn Int | Bn Int | Cn Int
ski :: Deb -> (Int, Com)
ski deb = case deb of
Zero -> (1, I)
Succ d | x@(n, _) <- ski d -> (n + 1, f (0, K) x)
App d1 d2 | x@(a, _) <- ski d1
, y@(b, _) <- ski d2 -> (max a b, f x y)
Lam d | (n, e) <- ski d -> case n of
0 -> (0, K :# e)
_ -> (n - 1, e)
where
f (a, x) (b, y) = case (a, b) of
(0, 0) -> x :# y
(0, n) -> Bn n :# x :# y
(n, 0) -> Cn n :# x :# y
(n, m) | n == m -> Sn n :# x :# y
| n < m -> Bn (m - n) :# (Sn n :# x) :# y
| otherwise -> Cn (n - m) :# (Bn (n - m) :# Sn m :# x) :# y
λ to SKI, logarithmically
Please see my newer notes instead. This page is kept for historical reasons.
It relies on memoizing the bulk variants of the B, C, and S combinators:
In particular, \(B_1, C_1, S_1\) are the standard \(B, C, S\) combinators. Linear complexity is implied by:
linBulk :: Com -> Com linBulk b = case b of Bn n -> iterate ((B:# B):#) B !! (n - 1) Cn n -> iterate ((B:#(B:#C):#B):#) C !! (n - 1) Sn n -> iterate ((B:#(B:#S):#B):#) S !! (n - 1) x :# y -> linBulk x :# linBulk y _ -> b
Bulk discount
How about without memoization? Observe:
where
Hence we can build up the bulk combinators in a manner analogous to repeated squaring when exponentiating, resulting in a logarithmic factor in lieu of a linear one. In short: binary, not unary.
Define the following combinators:
To compute \(S_{50}\) for example, write 50 in binary: 11010. Then chain together \(b_0\) or \(b_1\) depending on the bits:
Similarly for the other bulk combinators.
logBulk :: Com -> Com logBulk b = case b of -- C' = \cfgx.c(fx) g = B(BC)B -- S' = \cfgx.c fx (gx) = B(BS)B Bn n -> go n (K:#I) :# B :# I Cn n -> go n (K:#(C:#I:#I)) :# (B:#(B:#C):#B) :# I Sn n -> go n (K:#(C:#I:#I)) :# (B:#(B:#S):#B) :# I x :# y -> logBulk x :# logBulk y _ -> b where go n base = foldr (:#) base $ ([b0, b1]!!) <$> bits [] n bits acc 0 = reverse acc bits acc n | (q, r) <- divMod n 2 = bits (r:acc) q b0 = C:#B:#(S:#B:#I) b1 = C:#(B:#S:#(B:#(B:#B):#(C:#B:#(S:#B:#I)))):#B
Therefore, if memoization is forbidden, we can easily transform a lambda term of length N to a combinatory logic term of length O(N log N).
Show Me
instance Show Com where
show S = "S"
show I = "I"
show C = "C"
show K = "K"
show B = "B"
show (l :# r) = (show l ++) $ case r of
_ :# _ -> "(" ++ show r ++ ")"
_ -> show r
show (Bn n) = "B_" ++ show n
show (Cn n) = "C_" ++ show n
show (Sn n) = "S_" ++ show n
For example:
print $ logBulk $ Sn 1234 print $ logBulk $ Bn 1234
Demo
jsEval "curl_module('../compiler/Charser.ob')"
import Charser
source = ws *> term [] <* eof where
term bnds = lam bnds <|> app bnds
lam bnds = do
vs <- lam0 *> many v <* lam1
t <- term $ reverse vs ++ bnds
pure $ iterate Lam t !! length vs
where lam0 = str "\\" <|> str "λ"
lam1 = str "->" <|> str "."
v = some alphaNumChar <* ws
app bnds = foldl1 App <$> some (ind bnds <$> v
<|> str "(" *> term bnds <* str ")")
ind bnds s = go bnds where
go = \case
[] -> error $ s ++ " is free"
h:t | h == s -> Zero
| otherwise -> Succ $ go t
str = (<* ws) . string
ws = many $ oneOf " \n" *> pure () <|> string "--" *> many (noneOf "\n") *> pure ()
demo = parse source "" <$> jsEval "input.value;" >>= \case
Left _ -> jsEval "lin.value = `parse error`; log.value = `parse error`;"
Right deb -> do
let v = snd $ ski deb
jsEval $ "lin.value = `" ++ show v ++ "`;"
jsEval $ "log.value = `" ++ show (logBulk v) ++ "`;"
jsEval [r|
skiB.addEventListener("click", (ev) => { repl.run("chat", ["Main"], "demo"); });
|]