编程界的“二向箔”——Dart元编程
閱讀過(guò)《三體》的同學(xué)肯定知道“降維打擊”,從更高維度看問(wèn)題,直接將對(duì)手KO。今天我們閑聊一下編程界的“二向箔”——元編程。
1. 什么是元編程
我們聽(tīng)過(guò)了太多太多的名詞,耳朵似乎都有點(diǎn)名詞麻痹癥了。比如,有些名詞為了裝x(比如筆者的文章標(biāo)題...)或者其本身的意義難以定義,就會(huì)加上一些似乎閃爍著光芒的前綴——如meta。計(jì)算機(jī)軟件這行業(yè)就有meta data, meta model, meta programming。
今天我們裝x的主角就是meta programming——元編程。
其實(shí)網(wǎng)絡(luò)上也能搜出很多相關(guān)的文章,對(duì)于該詞的定義參考wikipedia的一句話(huà):
Metaprogramming is a programming technique in which computer programs have the ability to treat other programs as their data. It means that a program can be designed to read, generate, analyze or transform other programs, and even modify itself while running. In some cases, this allows programmers to minimize the number of lines of code to express a solution, in turn reducing development time. It also allows programs greater flexibility to efficiently handle new situations without recompilation.
簡(jiǎn)而言之,就是將程序作為數(shù)據(jù),可以用于操作程序或者自身,而實(shí)現(xiàn)某些能力,比如將運(yùn)行時(shí)工作移到編譯時(shí)。
按照編譯器發(fā)展的進(jìn)程,元編程可實(shí)現(xiàn)如代碼替換(通過(guò)宏實(shí)現(xiàn)),泛型編程(從宏演變而來(lái),脫離類(lèi)型,高度抽象邏輯,可減少代碼量),或者在更高級(jí)的語(yǔ)言中,運(yùn)行時(shí)通過(guò)內(nèi)省/反射機(jī)制來(lái)操作代碼邏輯,或者隨著編譯過(guò)程的解耦和開(kāi)放,可以實(shí)現(xiàn)在中間語(yǔ)言階段(AST,IL),操作語(yǔ)法樹(shù)和中間語(yǔ)言,實(shí)現(xiàn)更可擴(kuò)展性的能力。
Dart做為一門(mén)現(xiàn)代高級(jí)語(yǔ)言,除了模板化能力,也能基于中間語(yǔ)言來(lái)操作代碼。本篇文章主要討論如何基于其中間語(yǔ)言(dill),通過(guò)AST樹(shù)的操作來(lái)進(jìn)行元編程,并實(shí)現(xiàn)一些現(xiàn)有dart語(yǔ)法本身實(shí)現(xiàn)不了的能力。并且這種實(shí)現(xiàn)在編譯時(shí),對(duì)于程序在運(yùn)行時(shí)的性能幾乎沒(méi)有影響。
2. Dart中的元編程簡(jiǎn)介
2.1 背景知識(shí)
我們知道,幾乎任何語(yǔ)言中,代碼在 "編譯"(解釋型語(yǔ)言在運(yùn)行時(shí)也有編譯的過(guò)程) 的過(guò)程中,都會(huì)生成一種樹(shù)狀的中間狀態(tài),這就是 AST(抽象語(yǔ)法樹(shù))。AST 描述了每個(gè)表達(dá)式/語(yǔ)句中的子語(yǔ)句的執(zhí)行順序和執(zhí)行邏輯,因而它可以被很方便地翻譯成目標(biāo)代碼 。基于這種抽象,能合理的將編譯器拆分為三階段:FrontEnd,Optimizer, Backend,從而實(shí)現(xiàn)能兼容各種語(yǔ)法形式的語(yǔ)言,更易于遷移并兼容不同架構(gòu)的cpu。見(jiàn)下圖:
?
這三個(gè)階段圍繞這IL(intermediate language)進(jìn)行。IL語(yǔ)言隔離了語(yǔ)法(能輕易適配各種新的語(yǔ)種),平臺(tái)架構(gòu)等的差異性。
?
2.2 Dart的編譯流程
Dart的設(shè)計(jì)也類(lèi)似,其中間語(yǔ)言就是Dill。不同的是,這里的Dill不像java的IL或者DotNet的IL那樣開(kāi)放出來(lái)可以直接編寫(xiě),而是通過(guò)程序的方式操作實(shí)現(xiàn)。
這種方式其實(shí)就是基于A(yíng)ST庫(kù)對(duì)Dill進(jìn)行manipulation。
這個(gè)庫(kù)內(nèi)的組件包含了所有AST樹(shù)涉及到的節(jié)點(diǎn)的定義和訪(fǎng)問(wèn),將類(lèi)型,函數(shù),語(yǔ)句,聲明,表達(dá)式等編程基本概念抽象成了對(duì)象。基于這些對(duì)象我們可以遍歷整個(gè)AST樹(shù), 或者生成新的類(lèi)型和函數(shù),插入代碼語(yǔ)句,實(shí)現(xiàn)新的邏輯。
2.3 幾個(gè)栗子
入門(mén)其實(shí)很簡(jiǎn)單,看一下例子代碼就可以啦。
2.3.1. 比如以下語(yǔ)句定義了一個(gè)新的Map變量,并且調(diào)用了它的構(gòu)造函數(shù):
//組裝參數(shù) Arguments mapFromArgs = Arguments.empty(); mapFromArgs.positional.add(MapLiteral([], keyType:keyInterType)); //調(diào)用from構(gòu)造函數(shù) StaticInvocation mapConstructor = StaticInvocation(MapFromFactoryProc, mapFromArgs); //聲明一個(gè)名字為jsonMap的Map類(lèi)型變量 VariableDeclaration mapInstDecl = VariableDeclaration("jsonMap", type:mapInterType); //相當(dāng)于var jsonMap = new Map(); VariableSet set_mymap = VariableSet(mapInstDecl, mapConstructor);2.3.2. 創(chuàng)建函數(shù)體
函數(shù)體其實(shí)就是Block。
Block bodyStatements = Block(List<Statement>()); bodyStatements.addStatement(mapInstDecl); bodyStatements.addStatement(ExpressionStatement(inst));2.3.3 創(chuàng)建函數(shù)
這個(gè)例子是參考某個(gè)函數(shù)的聲明形式來(lái)創(chuàng)建新函數(shù),篇幅所限,一些參數(shù)從略。
static Procedure createProcedure(Procedure referProcedure ,Statement bodyStatements, DartType returnType) {FunctionNode functionNode = new FunctionNode(bodyStatements,//...參數(shù)從略);Procedure procedure = new Procedure(Name(referProcedure.canonicalName.name, referProcedure.name.library),ProcedureKind.Method, functionNode,//...參數(shù)從略);return procedure;} //調(diào)用函數(shù)創(chuàng)建,并添加到類(lèi)定義中 Procedure overridedToJsonFunc = createProcedure(jsonTypes.StaticBaseToJsonProc, bodyStatements, InterfaceType(mapClass)); youClazz.addMember(overridedToJsonFunc);2.3.4 其他
基于A(yíng)ST還可以創(chuàng)建復(fù)雜的表達(dá)式和語(yǔ)句,如ForInStatement(for...in循環(huán))等,語(yǔ)句和表達(dá)式還可以通過(guò)ExpressionStatement和BlockExpression互相轉(zhuǎn)化。更多的技巧可參考AST的定義。
2.4 如何調(diào)試
編輯好的Dill似乎是個(gè)黑盒,除了看日志或者看異常堆棧,并不能進(jìn)行單步調(diào)試,給開(kāi)發(fā)帶來(lái)了一些困難。但Dart提供了已將將kernel dill轉(zhuǎn)成可閱讀的文本的工具,方便調(diào)試:
$DartHome/dart ../../pkg/vm/bin/dump_kernel.dart /your/dill/file/path /output/dill/text/file.text打開(kāi)的text文件是類(lèi)似于這樣的:
static method __from_Json1(core::Map<dynamic, dynamic> m) → aop2::UserDataT {aop2::UserDataT inst;inst = new aop2::UserDataT::?();inst.name = m.[]("name") is core::String ?{core::String} m.[]("name") : null;inst.city = m.[]("city") is core::String ?{core::String} m.[]("city") : null;inst.age = m.[]("age") is core::int ?{core::int} m.[]("age") : null;inst.squres = m.[]("squres") is core::double ?{core::double} m.[]("squres") : null;inst.sex = m.[]("sex") is core::bool ?{core::bool} m.[]("sex") : null;inst.houses = m.[]("houses") is core::Map<dynamic, dynamic> ?{core::Map<dynamic, dynamic>} block {core::Map<core::String, core::String> mymap;mymap = col::LinkedHashMap::from<core::String, core::String>(<core::String, core::String>{});for (core::String item in (m.[]("houses") as core::Map<dynamic, dynamic>).keys) {mymap.[]=(item, (m.[]("houses") as core::Map<dynamic, dynamic>).[](item) is core::String ?{core::String} (m.[]("houses") as core::Map<dynamic, dynamic>).[](item) : null);}} =>mymap : null;return inst;}3. 應(yīng)用暢想
基于Dill的Manipulation,我們可以實(shí)現(xiàn)往代碼中注入新的邏輯。比如閑魚(yú)科技之前開(kāi)源的AOP庫(kù)AspectD的原理就是通過(guò)加載dill文件生成AST,然后遍歷AST,尋找到已經(jīng)annotation到的函數(shù)或語(yǔ)句,在dill層面操作后又生成dill參加到編譯器后續(xù)的流程,最終實(shí)現(xiàn)了AOP。
類(lèi)似的,我們知道Dart對(duì)于Json解析操作不是很方便,jsonDecode不能直接生成業(yè)務(wù)對(duì)象,而是Map或者List之類(lèi)的集合,還需要用戶(hù)自己手動(dòng)代碼遍歷這些集合并裝載對(duì)象。雖然官方開(kāi)源了一個(gè)基于Source_gen的方案,但使用上也不友好(還有其他一些方案如Dason等,但依賴(lài)于Mirror,詳見(jiàn)這里的比較)。其實(shí)遍歷Map或者List并裝配對(duì)象這樣的操作邏輯很簡(jiǎn)單,我們也可以通過(guò)Dill Manipulation來(lái)做。
其使用方式簡(jiǎn)便,舉例如下:
@JsonModel() class UserData {String name;String city;UserData son; }void main(){var str = '''{"name": "Jim","city": "hangzhou","son":{"name": "Kong","city":"Hangzhou"}}''';UserData userData = JsonDrill.fromJson<UserData>(jsonDecode(str));var map = JsonDrill.toJson<UserData>(userData);print("$map");}更深入的思考一下,Dart現(xiàn)有的mirror能力至今未推薦使用(原因分析可參考這篇文章),那我們是否可以基于Dill Manipulation實(shí)現(xiàn)一個(gè)簡(jiǎn)單輕量的LiteMirror庫(kù)呢?并基于這個(gè)LiteMirror庫(kù)實(shí)現(xiàn)更上層的Json解析和AOP甚至Hook能力?
當(dāng)然,聰明的你或許已經(jīng)發(fā)現(xiàn),Dill Manipulation不可避免的要對(duì)編譯流程進(jìn)行定制,這就要求比如在Flutter環(huán)境中,需要對(duì)Flutter Tool進(jìn)行定制,以加入Dill再編輯的環(huán)節(jié)。劇透一下,閑魚(yú)科技目前就已經(jīng)實(shí)現(xiàn)了Json解析器,正在準(zhǔn)備開(kāi)源中,敬請(qǐng)期待
原文鏈接
本文為云棲社區(qū)原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。
總結(jié)
以上是生活随笔為你收集整理的编程界的“二向箔”——Dart元编程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 数据仓库介绍与实时数仓案例
- 下一篇: 来了!云栖大会都能看到什么?