[英]removing explicit recursion by replacing catamorphism
我有這個 AST 數據結構
data AST = Integer Int
| Let String AST AST
| Plus AST AST
| Minus AST AST
| Times AST AST
| Variable String
| Boolean Bool
| If AST AST AST
| Lambda String AST Type Type
| Application AST AST
| And AST AST
| Or AST AST
| Quot AST AST
| Rem AST AST
| Negate AST
| Eq AST AST
| Leq AST AST
| Geq AST AST
| Neq AST AST
| Lt AST AST
| Gt AST AST
這個評估代碼:
eval :: AST -> AST
eval = cata go where
go :: ASTF (AST) -> AST
go (LetF var e e') = eval $ substVar (var, e) e'
go (PlusF (Integer n) (Integer m)) = Integer (n + m)
go (MinusF (Integer n) (Integer m)) = Integer (n - m)
go (TimesF (Integer n) (Integer m)) = Integer (n * m)
go (QuotF (Integer n) (Integer m)) = Integer (quot n m)
go (RemF (Integer n) (Integer m)) = Integer (rem n m)
go (IfF (Boolean b) e e') = if b then e else e'
go (ApplicationF (Lambda var e _ _) e') = eval $ substVar (var, e') e
go (AndF (Boolean b) (Boolean b')) = Boolean (b && b')
go (OrF (Boolean b) (Boolean b')) = Boolean (b || b')
go (NegateF (Boolean b)) = Boolean (not b)
go (EqF e e') = Boolean (e == e')
go (NeqF e e') = Boolean (e /= e')
go (LeqF (Integer n) (Integer m)) = Boolean (n <= m)
go (GeqF (Integer n) (Integer m)) = Boolean (n >= m)
go (LtF (Integer n) (Integer m)) = Boolean (n < m)
go (GtF (Integer n) (Integer m)) = Boolean (n > m)
go astf = embed astf
我覺得應該刪除'let'和應用程序的顯式遞歸,但我不確定我應該使用哪種遞歸方案。 在這種情況下和類似情況下,可以使用哪種遞歸方案來消除遞歸,您有什么好的方法來識別所述遞歸方案適用的情況嗎?
eval
不能直接表示為變態,因為eval (Let xe e')
將eval
應用於subst (x, eval e) e'
,它不是Let xe e'
( e
或e'
)的子項。 相反,請考慮eval
和替換的組成。 如果將替換subst
概括為用substs
一次替換多個變量,則可以得到以下等式:
(eval . substs s) (Let x e e')
= eval (Let x (substs s e) (substs s e'))
= (eval . subst (x, (eval . substs s) e) . substs s) e'
-- by (subst xe . substs s) = substs (xe : s)
= (eval . substs ((x, (eval . substs s) e) : s)) e'
我們確實有一個形式為(eval. substs _)
的 function 應用於子項e
和e'
。 為了考慮substs
的參數,您可以將cata
與 F-algebra 一起使用,其中載體是 function ASTF (Sub -> AST) -> Sub -> AST
,然后您可以將不同的替換Sub
傳遞給每個子項。
{-# LANGUAGE TemplateHaskell, TypeFamilies #-}
import Data.Functor.Foldable (cata)
import Data.Functor.Foldable.TH (makeBaseFunctor)
import Data.Maybe (fromJust)
type Id = String
data AST
= Let Id AST AST
| Var Id
| Int Int
| Plus AST AST
type Sub = [(Id, AST)]
makeBaseFunctor ''AST
evalSubsts :: AST -> Sub -> AST
evalSubsts = cata go where
go (LetF x e e') s = e' ((x, e s) : s)
go (VarF x) s = fromJust (lookup x s)
go (IntF n) _ = Int n
go (PlusF e e') s =
let (Int n, Int m) = (e s, e' s) in
Int (n + m)
-- Or this:
-- go (PlusF e e') = liftA2 plus e e'
-- where plus (Int n) (Int m) = Int (n + m)
eval :: AST -> AST
eval e = evalSubsts e []
另一種看待evalSubsts
的方式是作為使用環境的評估器,將變量映射到值。 然后綁定一個變量,而不是進行替換,只是將其值插入環境中,一旦到達Var
節點就會查找該值。
繼續 @li-yao-xia 的evalSubsts
方法,讓我們嘗試將 lambda 和應用程序添加到我們的語言中。 我們從擴展AST
開始:
data AST
...
| Lam Id AST
| App AST AST
但是,我們如何在evalSubst
中編寫我們的LamF
案例?
go (LamF x e) s = ???
我們希望我們的 lambda 是靜態(詞法)范圍的,所以我們需要保留環境s
,但我們也不能將環境應用於e
,因為我們不知道x
的值應該是什么。 我們陷入困境!
這里的解決方案是認識到,雖然AST
是我們輸入的一個很好的表示,但它不是output 的一個很好的表示。 實際上,輸入Int
和 output Int
碰巧共享相同的結構,這有點巧合,而對於 lambda,也許它們不應該。 因此,我們可以制作一個新的 output 表示:
data Val
= IntV Int
| LamV (Val -> Val)
type Sub = [(Id, Val)]
這里的關鍵是LamV
是一個function,而不是簡單的一些數據。 這樣,我們就可以完成對evalSubsts
的定義:
evalSubsts :: AST -> Sub -> Val
evalSubsts = cata go where
go (LetF x e e') s = e' ((x, e s) : s)
go (VarF x) s = fromJust (lookup x s)
go (IntF n) _ = IntV n
go (LamF x e) s = LamV $ \xVal -> e ((x, xVal) : s)
go (AppF lam e) s =
let (LamV f) = lam s in f (e s)
go (PlusF e e') s =
let (IntV n, IntV m) = (e s, e' s) in
IntV (n + m)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.