閱讀過《三體》的同窗確定知道「降維打擊」,從更高維度看問題,直接將對手KO。今天咱們閒聊一下編程界的「二向箔」——元編程。html
咱們聽過了太多太多的名詞,耳朵彷佛都有點名詞麻痹症了。好比,有些名詞爲了裝x(好比筆者的文章標題...)或者其自己的意義難以定義,就會加上一些彷佛閃爍着光芒的前綴——如meta。計算機軟件這行業就有meta data, meta model, meta programming。
今天咱們裝x的主角就是meta programming——元編程。
其實網絡上也能搜出不少相關的文章,對於該詞的定義參考wikipedia的一句話:java
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.git
簡而言之,就是將程序做爲數據,能夠用於操做程序或者自身,而實現某些能力,好比將運行時工做移到編譯時。
按照編譯器發展的進程,元編程可實現如代碼替換(經過宏實現),泛型編程(從宏演變而來,脫離類型,高度抽象邏輯,可減小代碼量),或者在更高級的語言中,運行時經過內省/反射機制來操做代碼邏輯,或者隨着編譯過程的解耦和開放,能夠實如今中間語言階段(AST,IL),操做語法樹和中間語言,實現更可擴展性的能力。github
Dart作爲一門現代高級語言,除了模板化能力,也能基於中間語言來操做代碼。本篇文章主要討論如何基於其中間語言(dill),經過AST樹的操做來進行元編程,並實現一些現有dart語法自己實現不了的能力。而且這種實如今編譯時,對於程序在運行時的性能幾乎沒有影響。express
咱們知道,幾乎任何語言中,代碼在 "編譯"(解釋型語言在運行時也有編譯的過程) 的過程當中,都會生成一種樹狀的中間狀態,這就是 AST(抽象語法樹)。AST 描述了每一個表達式/語句中的子語句的執行順序和執行邏輯,於是它能夠被很方便地翻譯成目標代碼 。基於這種抽象,能合理的將編譯器拆分爲三階段:FrontEnd,Optimizer, Backend,從而實現能兼容各類語法形式的語言,更易於遷移併兼容不一樣架構的cpu。見下圖:編程
這三個階段圍繞這IL(intermediate language)進行。IL語言隔離了語法(能輕易適配各類新的語種),平臺架構等的差別性。json
Dart的設計也相似,其中間語言就是Dill。不一樣的是,這裏的Dill不像java的IL或者DotNet的IL那樣開放出來能夠直接編寫,而是經過程序的方式操做實現。網絡
這種方式其實就是基於AST庫對Dill進行manipulation。架構
這個庫內的組件包含了全部AST樹涉及到的節點的定義和訪問,將類型,函數,語句,聲明,表達式等編程基本概念抽象成了對象。基於這些對象咱們能夠遍歷整個AST樹, 或者生成新的類型和函數,插入代碼語句,實現新的邏輯。app
入門其實很簡單,看一下例子代碼就能夠啦。
2.3.1. 好比如下語句定義了一個新的Map變量,而且調用了它的構造函數:
//組裝參數 Arguments mapFromArgs = Arguments.empty(); mapFromArgs.positional.add(MapLiteral([], keyType:keyInterType)); //調用from構造函數 StaticInvocation mapConstructor = StaticInvocation(MapFromFactoryProc, mapFromArgs); //聲明一個名字爲jsonMap的Map類型變量 VariableDeclaration mapInstDecl = VariableDeclaration("jsonMap", type:mapInterType); //至關於var jsonMap = new Map(); VariableSet set_mymap = VariableSet(mapInstDecl, mapConstructor);
2.3.2. 建立函數體
函數體其實就是Block。
Block bodyStatements = Block(List<Statement>()); bodyStatements.addStatement(mapInstDecl); bodyStatements.addStatement(ExpressionStatement(inst));
2.3.3 建立函數
這個例子是參考某個函數的聲明形式來建立新函數,篇幅所限,一些參數從略。
static Procedure createProcedure(Procedure referProcedure ,Statement bodyStatements, DartType returnType) { FunctionNode functionNode = new FunctionNode(bodyStatements, //...參數從略 ); Procedure procedure = new Procedure( Name(referProcedure.canonicalName.name, referProcedure.name.library),ProcedureKind.Method, functionNode, //...參數從略 ); return procedure; } //調用函數建立,並添加到類定義中 Procedure overridedToJsonFunc = createProcedure(jsonTypes.StaticBaseToJsonProc, bodyStatements, InterfaceType(mapClass)); youClazz.addMember(overridedToJsonFunc);
2.3.4 其餘
基於AST還能夠建立複雜的表達式和語句,如ForInStatement(for...in循環)等,語句和表達式還能夠經過ExpressionStatement和BlockExpression互相轉化。更多的技巧可參考AST的定義。
編輯好的Dill彷佛是個黑盒,除了看日誌或者看異常堆棧,並不能進行單步調試,給開發帶來了一些困難。但Dart提供了已將將kernel dill轉成可閱讀的文本的工具,方便調試:
$DartHome/dart ../../pkg/vm/bin/dump_kernel.dart /your/dill/file/path /output/dill/text/file.text
打開的text文件是相似於這樣的:
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; }
基於Dill的Manipulation,咱們能夠實現往代碼中注入新的邏輯。好比閒魚科技以前開源的AOP庫AspectD的原理就是經過加載dill文件生成AST,而後遍歷AST,尋找到已經annotation到的函數或語句,在dill層面操做後又生成dill參加到編譯器後續的流程,最終實現了AOP。
相似的,咱們知道Dart對於Json解析操做不是很方便,jsonDecode不能直接生成業務對象,而是Map或者List之類的集合,還須要用戶本身手動代碼遍歷這些集合並裝載對象。雖然官方開源了一個基於Source_gen的方案,但使用上也不友好(還有其餘一些方案如Dason等,但依賴於Mirror,詳見這裏的比較)。其實遍歷Map或者List並裝配對象這樣的操做邏輯很簡單,咱們也能夠經過Dill Manipulation來作。
其使用方式簡便,舉例以下:
@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現有的mirror能力至今未推薦使用(緣由分析可參考這篇文章),那咱們是否能夠基於Dill Manipulation實現一個簡單輕量的LiteMirror庫呢?並基於這個LiteMirror庫實現更上層的Json解析和AOP甚至Hook能力?
固然,聰明的你或許已經發現,Dill Manipulation不可避免的要對編譯流程進行定製,這就要求好比在Flutter環境中,須要對Flutter Tool進行定製,以加入Dill再編輯的環節。劇透一下,閒魚科技目前就已經實現了Json解析器,正在準備開源中,敬請期待
原文連接 本文爲雲棲社區原創內容,未經容許不得轉載。