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

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

作者:余玠

,
    推荐阅读
  • 孕妇做的血型分析是自己的血型吗(这两种血型怀孕后要当心)

    这两种血型怀孕后要当心有人说“如果孕妈和胎儿血型不一样,可能会出现排斥反应,导致胎儿溶血、流产、甚至死胎,生下的宝宝可能智力也受影响!”一时间吓得孕妈们慌忙来医院问“需不需要查血型?”“能做溶血筛查吗?能不能预防?”事实真。

  • 秋冬戴口罩可以预防感冒吗(谣言别信)

    真相来了经核实,该美发店一员工在取得万州当地健康证明后,于25日晚乘坐动车从万州回沙坪坝。该员工到达后未主动向所在社区报告,社区在日常排查中掌握其从万州返回情况。出于谨慎考虑,将按照疫情防控有关规定对其进行集中隔离医学观察。政府部门不会以网民转发的形式发布任何通告内容,请勿轻信。2月28日0-24时,湖南新增确诊病例数据已由湖南省卫健委通过权威媒体公布,其中益阳市报告新增新冠肺炎确诊病例1例。

  • 问鼎中原的含义(问鼎中原释义)

    问鼎中原意思是比喻企图夺取天下出自《左传》,现在小编就来说说关于问鼎中原的含义?下面内容希望能帮助到你,我们来一起看看吧!传说古代夏禹铸造九鼎,代表九州,作为国家权力的象征。夏、商、周三代以九鼎为传国重器,为得天下者所据有。

  • 吕布戏貂蝉的经过(吕布戏貂蝉的经过介绍)

    吕布戏貂蝉的经过吕布戏貂蝉的经过:为除十常侍,何进招西凉太守董卓进宫,而袁绍和曹操把宦官斩尽。最后董卓把持朝政。而貂蝉是司徒王允手下的一歌妓,后当作王允之干女儿许配给吕布,后又送给董卓,由此离间二人。接着王允安排借吕布之手杀死了董卓。之后吕布有了谋士陈宫,便四处投奔四处征讨,讨了严氏,并占领了徐州。当徐州失陷后,吕布被陈登骗到下邳居住。在此,吕布十分颓废,日夜喝酒、陪严氏和貂蝉。

  • 牛肉玉米炒饭的做法(牛肉玉米炒饭的制作方法)

    接下来我们就一起去了解一下吧!牛肉玉米炒饭的做法白米饭需提前冷藏过。牛肉切小条并用糖、蚝油,生粉腌10分钟。打开料理锅,下花生油,烧热,倒入牛肉炒至转色,盛起待用。原锅再下点花生油,放入玉米粒炒至有点焦黄。加入洗过的葡萄干,牛肉丝翻炒均匀,放点葱花。翻炒掉均匀,关火。

  • 山药发黄怎么处理(如果做才能不变色呢)

    下面更多详细答案一起来看看吧!山药发黄怎么处理因霉变而变色的山药不能食用,但如果是因氧化而变色的山药是可以食用的,也不需要做什么处理。新鲜的山药很容易跟空气中的氧发生氧化作用,跟铁刀等金属接触的时候也容易产生褐化现象,看起来就是白色的山药变黄了,直接吃的话是没有问题的,不过做好菜以后色泽不好看。建议将山药重新再去皮或者是把削皮后的山药浸在冷水里,使其与空气中的氧隔绝就不会变色了。

  • 债权人承担什么责任(关于债务人的权利和债权人的责任)

    债权人承担什么责任民法典第589条:债务人按照约定履行债务,债权人无正当理由拒绝受领的,债务人可以请求债权人赔偿增加的费用。在债权人受领迟延期间,债务人无须支付利息。这种障碍在理论上被称为债权人迟延或受领迟延。债权人违反从给付义务,应当向对方承担违约责任;债权人违反对己义务,将丧失就此所遭受的损失要求债务人赔偿的权利。相反,如果债权人依法有理由拒绝受领,则不成立本条规定的法律后果。

  • 三观尽毁是什么意思(什么叫三观尽毁)

    三观尽毁”就是己三观都颠覆了,很崩溃的意思。毁三观为网络常见用词,常用来泛指那些颠覆大多数人一般看法的人、事或物。世界观是指人们对生活于其中的整个世界以及人和外在世界之间的关系的根本观点、根本看法。人生观是指是对人生目的、意义的根本看法和态度。一个人的三观决定了他对客观世界的看法和道德的理解及底线。

  • 牛肉饺子馅怎样调配鲜嫩(饭店的牛肉馅怎么调)

    烹饪小技巧:1、饭店的牛肉馅怎么调?如果你也认为这饭店大厨分享的牛肉馅饺子做法还不错,欢迎将其收藏起来学习,或者是分享给更多需要的人!

  • 白色皮面运动鞋子清洗小妙招(白色皮面运动鞋子清洗小妙招有什么)

    下面内容希望能帮助到你,我们来一起看看吧!白色皮面运动鞋子清洗小妙招人造皮面:人造皮面的清洁比较简单,只要用湿布清洁皮面后立刻用干布擦拭即可。切忌长期用擦皮鞋的保养油来擦拭鞋面,这样会导致鞋面皮质出现小裂纹。