簡體   English   中英

通過替換變態來消除顯式遞歸

[英]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'ee' )的子項。 相反,請考慮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 應用於子項ee' 為了考慮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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM