编程实现算术表达式求值_用魔法打败魔法:C++模板元编程实现的scheme元循环求值器...
本文使用 Zhihu On VSCode 創作并發布
[TOC]
前言
寒假時沉迷C++模板元編程,寫了個簡單的Scheme元循環求值器。可以用類似Scheme的語法寫出這樣的C++模板代碼:
_<lambda, _<V(pred), V(lst)>,_<letrec, _<_<V(iter), _<lambda, _<V(lst)>,_<cond,_<_<is_null, lst>, B(false)>,_<_<pred, _<car, lst>>, B(true)>,_<elsee, _<iter, _<cdr, lst>>>>>>>,_<iter, lst>>>等價的Scheme代碼是這樣的:
(lambda (pred lst)(letrec ((iter (lambda (lst)(cond((null? lst) #f)((pred (car lst)) #t)(else (iter (cdr lst)))))))(iter lst)))可以在運行時輸出表達式的值:
/* mutual recursive 1* (letrec* (* (even? (lambda (n)* (if (eqv? n zero)* #t* (odd? (sub1 n)))))* (one 1)* (odd? (lambda (n)* (if (eqv? n zero)* #f* (even? (sub1 n)))))* (sub1 (lambda (n) (- n one)))* (zero (sub1 one)))* (even? 12))*/ using expr = eval<_<letrec,_<_<V(is_even), _<lambda, _<V(n)>,_<iff, _<is_eq, n, V(zero)>,B(true),_<V(is_odd), _<V(sub1), n>>>>>,_<V(one), N(1) >,_<V(is_odd), _<lambda, _<V(n)>,_<iff, _<is_eq, n, V(zero)>,B(false),_<is_even, _<V(sub1), n>>>>>,_<V(sub1), _<lambda, _<V(n)>, _<sub, n, one>>>,_<V(zero), _<sub1, one>>>,_<is_even, N(12)>> >; runtime<expr>::output(std::cout) << std::endl; // #t還有一些簡單的例子:
/* mutual recursive 2* (letrec* ((fs (cons* (lambda (n)* (if (eqv? n 0)* #t* ((cdr fs) (- n 1))))* (lambda (n)* (if (eqv? n 0)* #f* ((car fs) (- n 1)))))))* ((car fs) 12))*/ using expr = eval<_<letrec,_<_<V(fs), _<cons,_<lambda, _<V(n)>,_<iff, _<is_eq, n, N(0) >,B(true),_<_<cdr, fs>, _<sub, n, N(1)>>>>,_<lambda, _<V(n)>,_<iff, _<is_eq, n, N(0) >,B(false),_<_<car, fs>, _<sub, n, N(1)>>>>>>>,_<_<car, fs>, N(12)>> >; runtime<expr>::output(std::cout) << std::endl; // #t /* dot* (let* (* (head (lambda (head . tail) head))* (head 1 2 #f))*/ using expr = eval<_<let,_<_<V(head), _<lambda, _<V(head), dot, V(tail)>, head>>>,_<head, N(1), N(2), B(false)>> >; runtime<expr>::output(std::cout) << std::endl; // 1 // (flat-map list (list 1 2) (list 3 4)) using expr = eval<_<flat_map, list, _<list, N(1), N(2) >, _<list, N(3), N(4)>> >; // interleave runtime<expr>::output(std::cout) << std::endl; // (1 3 2 4)當然求值的結果也可以在編譯期使用,只是懶得實現了(畢竟本質玩具……而且C++編譯期計算用constexpr函數就足夠了)。
代碼以及詳細的介紹位于https://github.com/Light-of-Hers/CCTV
寫完后就忘了這茬事了……
這學期修了胡振江老師的PL課,突然想起了自己寫的這個玩具,便寫下此文記錄一下。
語法元素
- 用變參模板來表示scheme中的列表(list)。
- 用普通類來表示不攜帶其他信息的token,比如關鍵詞(keyword)、標識符(identifier)等。
- 用模板類來表示攜帶額外信息的token,比如字面量(literal):number、boolean等。
- 用模板類來表示denotable value,比如pair、closure等。
其中list和token是用戶可見的,為了方便用戶的書寫:
- keyword提前聲明好,這樣用戶可以直接寫lambda來表示scheme中的lambda。
- 部分keyword和C++的keyword沖突,做了一些修改,如用iff表示if。
- 表示list的模板名取為_,這樣用戶就可以用_<a, b, c>來表示scheme中的(a b c)。
- 用一個macro來聲明代表identifier的普通類:#define V(x) struct x,這樣用戶可以用V(abc)來表示標識符abc了。而且同一個標識符只要用該宏生成一次(同名類只需聲明一次),之后的使用可以不再套個宏了。
- 用宏N(n)來表示number字面量n,用B(b)來表示boolean字面量b。
考慮到keyword、identifier、denotable value等都用類來表示,故使用繼承結構來進行區分:
- lang
- keyword
- lambda, iff, ...
- value
- pair_value
- pair
- atom_value
- null_atom
- null
- number_atom
- number
- boolean_atom
- boolean
- procedure_atom
- closure
- primitive
- null_atom
- pair_value
- keyword
所有沒有繼承自lang的類都視為identifier。
表達式求值
C++的模板可以進行pattern match,因此求值函數大部分時候寫起來還是蠻輕松的,就不多說了。
不過因為C++模板運算是pure functional的,就導致letrec的實現稍微費了點心思。
r6rs和racket的letrec的是借助side effect(let/let*和set!的語法糖)實現的,而用C++模板實現side effect不太現實(讓我用state passing style來實現side effect的話還不如要side effect……)。
fix-point組合子倒是很好的解決方案,不過當時我還沒有這方面知識……因此想了個稍顯古怪但還挺不錯的解決方案:
- 將environment-frame分類為normal-frame和recurse-frame(前者表示lambda和let等普通的綁定生成的frame,后者表示letrec生成的frame):
- 每個frame都有一個前驅frame的引用,一個identifier以及其綁定的value。
- 一個recurse-frame還有一個標記來表示前驅frame是否和該frame由同一個letrec的bindings生成。
- 求值letrec的bindings時,按照let*的規則進行,只是生成的frame為recurse-frame。
- 對environment進行lookup時,若匹配到一個recurse-frame (其所在letrec所生成frame中的最下游frame為),且其綁定的value為包含一個closure ,綁定的environment為,則:
- 若的前驅frame 為所綁定的frame的祖先,即,則返回一個新的closure ,只有綁定的environment與不同,為。
- 否則,直接返回。
后記
謹以此紀念寒假的摸魚時光。
總結
以上是生活随笔為你收集整理的编程实现算术表达式求值_用魔法打败魔法:C++模板元编程实现的scheme元循环求值器...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: unity3d显示c4d材质_C4D小白
- 下一篇: vc6 前进后退 番茄_openMP 使