肥宅钓鱼网
当前位置: 首页 钓鱼百科

dct算法的基本原理(编程界的二向箔)

时间:2023-07-10 作者: 小编 阅读量: 1 栏目名: 钓鱼百科

本篇文章主要讨论如何基于其中间语言,通过AST树的操作来进行元编程,并实现一些现有dart语法本身实现不了的能力。AST描述了每个表达式/语句中的子语句的执行顺序和执行逻辑,因而它可以被很方便地翻译成目标代码。这种方式其实就是基于AST库对Dill进行manipulation。这个库内的组件包含了所有AST树涉及到的节点的定义和访问,将类型,函数,语句,声明,表达式等编程基本概念抽象成了对象。更多的技巧可参考AST的定义。

阅读过《三体》的同学肯定知道“降维打击”,从更高维度看问题,直接将对手KO。今天我们闲聊一下编程界的“二向箔”——元编程。

1. 什么是元编程

我们听过了太多太多的名词,耳朵似乎都有点名词麻痹症了。比如,有些名词为了装x(比如笔者的文章标题...)或者其本身的意义难以定义,就会加上一些似乎闪烁着光芒的前缀——如meta。计算机软件这行业就有meta data, meta model, meta programming。

今天我们装x的主角就是meta programming——元编程。

其实网络上也能搜出很多相关的文章,对于该词的定义参考wikipedia的一句话:

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.

简而言之,就是将程序作为数据,可以用于操作程序或者自身,而实现某些能力,比如将运行时工作移到编译时。

按照编译器发展的进程,元编程可实现如代码替换(通过宏实现),泛型编程(从宏演变而来,脱离类型,高度抽象逻辑,可减少代码量),或者在更高级的语言中,运行时通过内省/反射机制来操作代码逻辑,或者随着编译过程的解耦和开放,可以实现在中间语言阶段(AST,IL),操作语法树和中间语言,实现更可扩展性的能力。

Dart做为一门现代高级语言,除了模板化能力,也能基于中间语言来操作代码。本篇文章主要讨论如何基于其中间语言(dill),通过AST树的操作来进行元编程,并实现一些现有dart语法本身实现不了的能力。并且这种实现在编译时,对于程序在运行时的性能几乎没有影响。

2. Dart中的元编程简介

2.1 背景知识

我们知道,几乎任何语言中,代码在 "编译"(解释型语言在运行时也有编译的过程) 的过程中,都会生成一种树状的中间状态,这就是 AST(抽象语法树)。AST 描述了每个表达式/语句中的子语句的执行顺序和执行逻辑,因而它可以被很方便地翻译成目标代码 。基于这种抽象,能合理的将编译器拆分为三阶段:FrontEnd,Optimizer, Backend,从而实现能兼容各种语法形式的语言,更易于迁移并兼容不同架构的cpu。见下图:

这三个阶段围绕这IL(intermediate language)进行。IL语言隔离了语法(能轻易适配各种新的语种),平台架构等的差异性。

2.2 Dart的编译流程

Dart的设计也类似,其中间语言就是Dill。不同的是,这里的Dill不像java的IL或者DotNet的IL那样开放出来可以直接编写,而是通过程序的方式操作实现。

这种方式其实就是基于AST库对Dill进行manipulation。

这个库内的组件包含了所有AST树涉及到的节点的定义和访问,将类型,函数,语句,声明,表达式等编程基本概念抽象成了对象。基于这些对象我们可以遍历整个AST树, 或者生成新的类型和函数,插入代码语句,实现新的逻辑。

2.3 几个栗子

入门其实很简单,看一下例子代码就可以啦。

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的定义。

2.4 如何调试

编辑好的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; }

3. 应用畅想

基于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解析器,正在准备开源中,敬请期待

作者:余玠

,
    推荐阅读
  • 蟹粉三鲜馄饨的做法(好吃又简单)

    蟹粉三鲜馄饨的做法肉剁馅,一定要剁哟,嫌麻烦,可以让卖家切片,再回来剁。买直接处理好的肉馅太碎,不好吃。放入三大勺料酒,两大勺生抽,鸡精少许,加一点水,顺着一个方向搅拌至上劲儿,再加水,继续搅拌,反复三四次。虾仁去壳和虾线,放入一大勺料酒,姜粉,盖上保鲜膜放到冰箱,腌制半小时,去腥。小香葱切碎将虾仁一半剁泥,一半切粒,和小葱一起放入肉馅中,搅拌均匀。韭菜切碎,放入香油搅拌,增香,避免出水。

  • 求闻之若此中的之是什么用法 求闻之若此中的之是什么意思

    意思是像这样听到传闻,还不如不听。出自《穿井得一人》:“宋君令人问之于丁氏,丁氏对曰:得一人之使,非得一人于井中也。求闻之若此,不若无闻也。”以子之矛,攻子之盾。表示一般的修饰关系:光荣之家。大道~行也,天下为公。如因势利导,则如水之就下,极为自然。

  • 一个专门讲干货的博主(建议你趁早取关的5类博主)

    建议你趁早取关的5类博主我是小郁儿,点击右上方“关注”,每天为你分享【自媒体写作运营】与【个人精进】干货01.夸张化妆对比类封面是一张超丑的人脸图,然后配上“化妆之后我就是公主”之类的夸张标题,但是点进去之后,发现博主本人并。

  • 戚风蛋糕卷的做法(怎么做戚风蛋糕卷)

    戚风蛋糕卷的做法蛋白打发到湿性发泡即可,把做好的蛋糕糊倒入铺了油纸的8寸烤盘。把面糊尽量的抹平。把烤盘放入预热好的烤箱,上下火175度,烤15分钟左右。出炉以后脱模,并把四周的蛋糕纸趁热撕开。然后等到蛋糕冷却,在冷却后的蛋糕表面涂上一层果酱。通过擀面杖的辅助,可以很方便的把蛋糕卷起来。油纸两端拧成糖果状,把卷好的蛋糕卷放进冰箱冷藏15分钟以上,使蛋糕卷定型。

  • 心坟文章梗概(心坟内容梗概)

    以下内容希望对你有帮助!心坟文章梗概一座心城,一座坟,坟里葬着未亡人。童话里,人鱼公主变成了泡沫,消失在大海里,王子和公主从此幸福的生活。现实中,王子只惦记着人鱼公主月光下暗自哭泣的眼泪,公主从此活在婚姻的坟墓中。但是,这座坟,是宋予问自己逼来的,所以披荆斩棘,也只能独自往前走,因为,她是爱情的故事里,永远的坏心女配角。

  • 川菜麻辣猪脚(如何做麻辣猪脚)

    我们一起去了解并探讨一下这个问题吧!川菜麻辣猪脚原料:猪蹄10个。卤水2000克,味精3克,绍酒3克,盐3克,白胡椒3克,白糖3克,香油5克,葱、姜、蒜粒各10克,麻椒粉3克,炸椒面5克,红油5克。猪蹄洗净,去掉猪蹄上的余毛,焯水过凉切开。

  • 自古红颜多薄命下一句(自古红颜多薄命怎么接下一句)

    自古红颜多薄命,香消玉殒谁人怜,我来为大家科普一下关于自古红颜多薄命下一句?美人自古如名将,不许人间见白头。江山情重美人轻,宛转蛾眉马前死。“自古红颜多薄命”的基本意思是:自古以来,无数事例证明,美女的命运总是坎坷的,甚至是悲惨的。元·洪希文《书美人图》:“可怜前代汗青史,薄命佳人类如此。”

  • 杭州上地铁用什么软件(杭州地铁神器上新)

    杭州地铁对整个地铁线网丢失物品做了一个统计,目前,地铁线网存有乘客失物约3790件,每日接收寻物案件约100件。对于地铁车厢范围内拾得的物品,工作人员均交接存放至地铁各站点。有失主联系方式的,热线或车站及时通知失主领取。无失主相关信息或无法获取失主联系方式的失物,在2个小时内完成失物招领管理系统信息登记、发布招领公告。

  • 论语里好听的名字(出自论语中的宝宝名字介绍)

    宝宝起名字论语1——谨信出处:子曰:“弟子,入则孝,出则悌,谨而信,泛爱众,而亲仁。”7——以安出自《论语·宪问第十四》:修己以安人。16——仁静取自《论语·雍也第六》中的句子:“知者动,仁者静。”19——文彬出自《论语》:文质彬彬,然后君子。20——安之取自《论语·季氏篇》:“既来之,则安之。”21——行简出自《论语》:居敬而行简,以临其民,不亦可乎。

  • 盼望已久的一场雨(等待一场阔别已久的重逢)

    盼望已久的一场雨“如酥小雨无声洒,润得大地着青裳。”最是一年春好处,绝胜烟柳满皇都。随风潜入夜,润物细无声。晓看红湿处,花重锦官城。“雨水到来地解冻,化一层来耙一层。”……若有来源标注错误或侵犯了您的合法权益,请作者持权属证明与本网联系,我们将及时更正、删除,谢谢。