基於文本替換的解譯器:加入continuation,重構解譯器

來源:互聯網
上載者:User

或許在加入continuation之前要先講講費這麼大勁做這個有什麼意義。 畢竟用不用continuation的計算結果都是一樣的。 不過,這是一個興趣使然的系列,學習這些知識應該完全出於好奇與好玩的想法。 所以我才不會告訴你們通過控制continuation可以實現call-with-current-continuation和異常處理等功能呢。

我先簡要描述一下加入continuation後解譯器是怎麼工作的。 加入continuation後的解譯器是以迭代的方式工作的。 迭代的狀態量有兩個,第一個是一個待求值的運算式或者求到的值,第二個是Continuation。 假設解譯器的輸入是$M$,那麼一開始的狀態表示為$\left<M, {mt}\right>_v$。 第一個狀態量是個運算式$M$,這時解譯器開始對$M$求值。 這個過程用記號$\rightarrow_{v}$表示。 這個過程可能會將一些“下一步要做的事”儲存到continuation。 求值到最後,狀態會變為$\left<V, \kappa\right>_c$(如果沒有無限迴圈)。 注意到下標變成c,$V$是一個值,所以不能再求值了,下一步要做的是從continuation $\kappa$中取出“下一步要做的事”執行。 這個過程也叫做“將continuation $\kappa$應用到值$V$”,用記號$\rightarrow_{c}$表示。 一個解譯器的執行過程就是$\rightarrow_{v}$和$\rightarrow_{c}$交替運行,就好像太極裡陰陽交替一樣。 解譯器執行到最後狀態會變成$\left<V, {mt}\right>_c$,已求得值$V$,又沒有“下一步要做的事”,也就是運行結束了,輸出$V$。 說了這麼多,本質上整個過程還是一句話:遞迴轉迭代。順便一提,加入continuation後的解譯器叫做CK machine。

先列出目前為止的所有求值過程(call-by-value): \begin{equation*}\begin{array}{lcl} eval(X) &=& X \\ eval(b) &=& b \\ eval(\lambda X.M) &=& \lambda X.M \\ eval((+ \; M \; N)) &=& eval(M) + eval(N) \\ eval((- \; M \; N)) &=& eval(M) - eval(N) \\ eval(({iszero} \; M)) &=& {true} \\ && \text{其中} eval(M) = 0 \\ eval(({iszero} \; M)) &=& {false}, \\ && \text{其中} eval(M) \neq 0 \\ eval((M \; N)) &=& eval(L[X \leftarrow eval(N)]) \\ && \text{其中} eval(M) = \lambda X.L \\ eval(({fix} \; X_1 \; X_2 \; M)) &=& \lambda X_2.M[X_1 \leftarrow ({fix} \; X_1 \; X_2 \; M)] \end{array}\end{equation*} 下面分別介紹各個情況下如何加入continuation。

值與fix運算式

值是最簡單的情況,直接應用continuation。 \begin{equation*}\begin{array}{lcl} \left<X, \kappa\right>_v &\rightarrow_v& \left<X, \kappa\right>_c \\ \left<b, \kappa\right>_v &\rightarrow_v& \left<b, \kappa\right>_c \\ \left<\lambda X.M, \kappa\right>_v &\rightarrow_v& \left<\lambda X.M, \kappa\right>_c \\ \end{array}\end{equation*}

fix運算式和值的情況類似: \begin{equation*}\begin{array}{lcl} \left<({fix} \; X_1 \; X_2 \; M), \kappa\right>_v &\rightarrow_v& \left<\lambda X_2.M[X_1 \leftarrow ({fix} \; X_1 \; X_2 \; M)], \kappa\right>_c \end{array}\end{equation*}

(fixX1X2M),κv→v λX2.M[X1←(fixX1X2M)],κc

基本運算

用$(o^n \; M_1 \; M_2 \; ... \; M_n)$表示基本運算,其中$o^n$是運算子,$M_1, ..., M_n$是參數,$n$是代表參數個數。 在我們的語言裡目前有兩個兩參數的基本運算$o^2=\{+, -\}$和一個單參數的基本運算$o^1=\{{iszero}\}$。

計算基本運算前要先對所有參數求值。這裡規定從左至右求值。 當解譯器在求第$i$個參數$M_i$的值時,需要儲存到continuation的資料有: 運算子$o^n$、 已求到的值$V_1, ..., V_{i-1}$(其中$V_1=eval(M), ..., V_{i-1}=eval(M_{i-1})$) 以及還沒求值的參數$M_{i+1}, ..., M_n$。 因此,包含基本運算的continuation定義為: \begin{equation*}\begin{array}{lcl} \kappa &=& {mt} \\ &|& \left<{opd}, \kappa, o^n, (V_1 \; ... \; V_{i-1}), (M_{i+1} \; ... \; M_n)\right> \end{array} \end{equation*}

求值過程為: \begin{equation*}\begin{array}{lcl} \left<(o^n \; M_1 \; M_2 \; ... \; M_n), \kappa\right>_v &\rightarrow_v& \left<M_1, \left<{opd}, \kappa, o^n, (M_2 \; ... \; M_n), ()\right>\right>_v \\ \left<V, \left<{opd}, \kappa, o^n, (M_{i+1} \; ... \; M_n), (V_1 \; ... \; V_{i-1})\right>\right>_c &\rightarrow_c& \left<M_{i+1}, \left<{opd}, \kappa, o^n, (... \; M_n), (V_1 \; ... \; V_{i-1} V)\right>\right>_v \\ \left<V, \left<{opd}, \kappa, o^n, (), (V_1 \; ... \; V_{n-1})\right>\right>_c &\rightarrow_c& \left<V', \kappa\right>_c \\ && \text{其中} V' = o^n(V_1, ..., V_{n-1}, V) \end{array}\end{equation*}

(onM1M2...Mn),κv V,opd,κ,on,(Mi+1...Mn),(V1...Vi1)c V,opd,κ,on,(),(V1...Vn1)c →v→c→c M1,opd,κ,on,(M2...Mn),()v Mi+1,opd,κ,on,(...Mn),(V1...Vi1V)v V′,κc其中V′=on(V1,...,Vn1,V)

函數調用

函數調用$(M \; N)$先計算$M$的值,$N$儲存到continuation: \begin{equation*}\begin{array}{lcl} \left<(M \; N), \kappa\right>_v &\rightarrow_v& \left<M, \left<{arg}, \kappa, N\right>\right>_v \end{array}\end{equation*} $M$計算完後從continuation取出$N$計算(我們採用call-by-value的調用方式),同時儲存$M$的計算結果$V$: \begin{equation*}\begin{array}{lcl} \left<V, \left<{arg}, \kappa, N\right>\right>_c &\rightarrow_c& \left<N, \left<{fun}, \kappa, V\right>\right>_v \end{array}\end{equation*} $N$也計算完後,進行$\beta$歸約: \begin{equation*}\begin{array}{lcl} \left<V, \left<{fun}, \kappa, \lambda X.L\right>\right>_c &\rightarrow_c& \left<L[X \leftarrow V], \kappa\right>_v \end{array}\end{equation*} 這個地方很有意思,可以看到函數調用最後$\beta$歸約的過程不會使continuation增長。 所有求值過程中,會導致continuation增長的幾個過程是基本運算中對參數的計算過程、函數調用中對函數的計算過程以及對參數的計算過程。 一般也認為函數調用中函數的這個位置(也就是$(M \; N)$中$M$這個位置)也算參數位置, 所以判斷一個函數調用是不是尾調用的依據是: 這個函數調用運算式是不是在一個參數位置,如果在參數位置,就不是尾調用。

上面分析的求值過程裡增加了兩種continuation: \begin{equation*}\begin{array}{lcl} \kappa &=& ... \\ &|& \left<{arg}, \kappa, N\right> \\ &|& \left<{fun}, \kappa, V\right> \end{array} \end{equation*}

κ =||...arg,κ,Nfun,κ,V

代碼實現

函數value-of/k是$\rightarrow_v$。 函數apply-cont是$\rightarrow_c$。 編寫代碼要注意對value-of/和apply-cont的調用都必須是尾調用。

過程$\rightarrow_v$的代碼:

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.