跳转到内容

第五部分导读:运行时与元编程

写一个校验库,要检查用户填的表单:名字不能为空,年龄在 0 到 150 之间。写库的人不知道用户会定义什么类型,又要对每个字段做同样的事。办法是让用户把规则写在类型定义上,库在运行时把规则读出来,逐个字段检查。写 Web 路由、序列化、依赖注入的库,面对的是同一个难题。

这一部分只有一章,讲的共同问题是:程序怎样在运行时看见并改写自己。“看见”指反射(reflection),即查看一个值属于什么类型、有哪些成员、成员上挂着什么注解;“改写”指在定义函数或类的时候,用一段代码把它换成另一个。四种语言的分歧,首先在运行时手里还剩多少信息。Python 和 C# 把类型和注解带进了运行时,TS 的类型在编译后就没有了,装饰器只能拿到值和名字,Go 没有注解,只给字段后面的一段字符串标签和 reflect 包。

信息不同,写法跟着不同,代价也不同。TS 的装饰器靠转译成函数调用,C# 的特性编译进程序集元数据,Python 的装饰器是定义时执行的高阶函数。反射要在运行时查元数据,比直接写出的代码慢,还可能和提前编译、裁剪冲突。于是各家另有一条路,把同样的事挪到编译期,由工具生成普通代码。本部分的第 22 章把元数据写在哪里、什么时候被读、运行时能看到什么、编译期有什么替代方案放在同一张表里比较。

章 一句话 四种语言差异最大的一点
第 22 章 装饰器、特性、结构标签与反射 把规则直接写在类型定义上,让一段通用代码在运行时读出规则,处理任意对象 运行时能看到类型吗:TS 只能把规则写成装饰器里的值,另外三种语言都能从类型本身出发找到规则

下面这张图列出这部分常遇到的问题,以及各自该翻的章。

遇到的问题与该看的章

图的左列是你可能遇到的问题,右列是该先翻的章或节。

遇到什么问题先看哪章:

  • 你想在类型定义上标出规则,再让一段通用代码把它读出来 → 看第 22 章 装饰器、特性、结构标签与反射,先读“概念本身”,再读自己语言那一节。
  • 你想弄清运行时到底能不能看到类型 → 看第 22 章的对比表和“差异从哪来”;想先分清类型信息是编译期的还是运行期的,看第 3 章 类型系统。
  • 你想在定义函数或类的时候把它包一层 → 看第 22 章里 Python 与 TS 的装饰器;要区分语法上的装饰器和设计模式里的装饰器,也在这一章。
  • 你嫌反射慢,或者要做提前编译 → 看第 22 章的“差异从哪来”和“实践建议”,里面讲了编译期的替代方案。
  • 你想知道方法在运行时怎样被找到、接口怎样被满足 → 看第 8 章 类、接口与组合。