Skip to content

Latest commit

 

History

History
333 lines (223 loc) · 9.97 KB

code_generation.pod

File metadata and controls

333 lines (223 loc) · 9.97 KB

代码生成

程序员的进步需要你去找寻更好的抽象。越少代码要写越好。解决方案越通用越好。当你 可以删代码加功能的时候,你已经达成了某种完美的目标。

新手程序员常会写出多于要求的代码,其原因部分基于对语言、库、惯用语的不熟悉,同 时也归咎于无法熟练地创建和维护良好的抽象。他们以编写长篇的过程式代码起步,接着 发现函数,再是参数,然后是对象,还有────可能的话────高阶函数和闭包。

元编程(或 代码生成)────编写编写程序的程序────是另一种抽象技巧。它可以如 发掘高阶函数能力般清晰,也可能如鼠洞一般让你身陷其中,困惑而恐惧。然而,这种技巧 强大、实用────其中一些还是 Moose(moose)这类强大工具的基础。

处理缺少函数和方法的 AUTOLOADautoload)技巧展示了此技巧勉强的一面;Perl 5 的函数和方法分派系统允许你定制常规查找失败后的行为。

eval

生成代码最简单的 至少是 概念上.... 技巧莫过于创建一个包含合法 Perl 代码片段 的字符串并通过 eval 字符串操作符编译。不像捕获异常的 eval 代码块操作符,eval 字符串在当前作用域内编译其中内容,包括当前包和词法绑定。

此技巧的常用于提供后备,如果你不能(或不想)加载某个可选的依赖:

如果 Monkey::Tracer 不可用,其中的 log() 函数仍将存在,只是不做任何事。

这个简单的例子可能有点靠不住。为在 eval 代码中包含变量,你必须处理引号问题。 这增加了内插的复杂度:

对忘记加反斜杠的你表示悲哀!祝你调教语法高亮器好运!更糟糕的是,每次对 eval 字符串的调用都将创建一个代表整段代码的全新数据结构。编译代码也不是免费的────也许, 比IO操作便宜些,但并非免费。

即便如此,此技巧简单合理、易于理解。

参数闭包

虽然使用 eval 构建访问器和增变器时很直接,但闭包(closures)允许你在编译期 向已生成的代码添加参数而无需进行额外的求值:

这段代码避免了不愉快的引号问题。由于只有一道编译过程,性能也更好,无论你有多少要 创建的访问器。通过重用 相同的 已编译代码作为两个函数的主体,它甚至使用更少的内 存。所有的区别来自对词法变量 $attrname 的绑定。对于长期运行的进程或是包含大量访 问器的程序中,此技巧非常有用。

向符号表安装比较容易,但很丑陋:

这一古怪的、哈希那样的语法指向当前 符号表 中的一个符号,它是当前名称空间内存 放诸如包全局变量、函数、方法等全局可见符号的地方。将引用赋值给符号表某项将安装或 替换对应的条目。要将一个匿名函数提升为方法,可把函数引用赋值到符号表中的对应条目。

这个操作是一个符号引用,因此应该禁用 strict 对此操作的引用检查。许多程序在类似 的代码中有不少隐晦的缺陷,它们在单个步骤内进行赋值和生成:

这个例子在外部块、内部块和函数体中都禁用严格检查。只有赋值违反了严格的引用检查, 因此只要对该操作禁用即可。

编译期操控

不同于显式编写的代码,通过 eval 字符串生成的代码于运行时生成。虽然你预计一个 常规函数在你程序的生命周期内都是可用的,但(运行时)生成的函数也许直到你要求时才 是可用的。

在编译期强制 Perl 运行代码────生成其他代码────的方法是将其包装于 BEGIN 块内。 当 Perl 5 语法分析器遇到标有 BEGIN 的代码块时,它将对整个代码块进行语法分析。 证实其不含任何语法错误后,代码块将立即执行。执行完毕,语法分析过程就好像未曾中断 一般继续。

实际点说,编写:

……和:

……之间的区别主要是可维护性。

由于 Perl 隐式地将 requireimportimporting)用 BEGIN 包装起来, 在模块内,任何函数外部的代码都会在你 use 它时执行。任何处于函数外、模块内的代 码会在 import() 调用发生 之前 执行。如果你 require 该模块,则不含隐式的 BEGIN 代码块。函数外部代码的执行将放在语法分析的 结尾

同时也请注意词法 声明(名称和作用域间的联系)和词法 赋值 之间的交互。前者 发生于编译期,而后者发生于执行点处。如下代码隐含一处缺陷:

……因为 BEGIN 块在对 $wanted_package 的字符串值赋值 执行。结果将是 意图在未定义值上调用 require() 方法而引发的异常。

Class::MOP

不像安装函数引用来填充名称空间及创建方法,目前没有简易的内置途径在 Perl 5 中创建 类。所幸的是,一个成熟且强大的 CPAN 发行模块恰好可以完成此项工作。Clas::MOPMoosemoose)的支柱库。它提供了 元对象协议(Meta Object Protocol)────一 种用于对象系统创建操控自身的机制。

相比自行编写脆弱的 eval 字符串代码或是尝试手动干涉符号表,你可以通过对象和方法操 控程序中的实体和抽象。

要创建一个类:

在你创建它时,你可以添加属性和方法到该类中:

……或在创建后,把它们添加到 元类(Metaclass)(代表类的对象)中:

……你可以对元类进行内省:

使用 Class::MOP::AttributeClass::MOP::Method,你可以类似地创建、操作 并内省属性和方法。此元对象协议及其带来的灵活性是 Moose(moose)强大的根源。

POD ERRORS

Hey! The above document had some coding errors, which are explained below:

Around line 5:

A non-empty Z<>

Around line 29:

Deleting unknown formatting code N<>

Around line 261:

A non-empty Z<>