日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

编程实现算术表达式求值_用魔法打败魔法:C++模板元编程实现的scheme元循环求值器...

發布時間:2025/3/15 c/c++ 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 编程实现算术表达式求值_用魔法打败魔法: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

所有沒有繼承自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元循环求值器...的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。