JAPH:“Just another Perl hacker”
据我们所知,JAPH 格式是在二十世纪九十年代由 Randal Schwartz 推广的(好几处信息来源都同意这个说法)。今天,JAPH 到处可见,它们是由该流派的那些不知疲倦的艺术家们制作的,比如 comp.lang.lang.perl.misc 新闻组的 Abigail。
下面的讨论中我们将分析 CPAN (请参阅参考资料)上的规范列表中的一些 JAPH,它们适合初级到中级 Perl 程序员。我在这里会对各种技巧作简单的说明,但是有兴趣的读者还是应该参阅 Programming Perl,第三版(请参阅参考资料)来进一步学习。
为了精确地支持这里所给的示例,您的系统中必须安装有 Perl 5.6.0。最好您还安装了最新的(2000 或者更新版本)主流 UNIX 系统(Linux、Solaris、BSD)。尽管这些示例也许能在老一点的 Perl 和 UNIX 版本甚至是其它操作系统上运行,您还是应该考虑一下如果它们运行失败这样需要解决的问题。每个 JAPH 都以规范列表的格式显示,带有一个日期和作者属性。
优良的
在开始讨论更丰富的内容之前,我们先来看看 Randal Schwartz 在 JAPH 的早些时期编写的四段简单有趣的代码。我们下面的第一个示例证明了并不是所有的 JAPH 都是要晦涩难懂的,有一些甚至是很容易看懂的。
清单 1. 一个简单的 true 条件句
Date:
18 Jun 90 15:53:11 GMT
From:
merlyn@iwarp.intel.com (Randal Schwartz)
print "Just another Perl hacker," if "you can't think of anything better..."
在清单 1 中,既然字符串不为空,if() 语句的值就始终为 true(只有空字符串“”、字符串“0”,数字 0 或其等价的表达形式,或者不定义时其值才为 false)。因而,将一直执行打印语句。
清单 2. 使用 Printf
Date:
15 Jun 90 22:06:24 GMT
From:
merlyn@iwarp.intel.com (Randal Schwartz)
printf "%s %s %s %s%c", 'Just', 'another', 'Perl', 'hacker', 44
清单 2 是另一个早期的样本,它使用 printf() 的函数来产生所需的输出,这也证明了如果您愿意的话,Perl 看起来也可以象 C 语言一样。
在清单 3 中 Schwartz 开始玩花招了。现在我们给 print() 一个重新排列过的数组,然后将这个数组打印出来,单词之间有空格($ 是一个变量,它告诉 Perl 在一次打印所有数组元素时在元素之间应该放什么东西)。
清单 3. 重新排列数组
Date:
5 Jun 90 19:07:58 GMT
From:
merlyn@iwarp.intel.com (Randal Schwartz)
$,=" "; print +("hacker,","Just","Perl","another")[1,3,2,0];
数组前面的 + 使 print() 把紧随其后的东西当做一个单独的参数(在本例中因为有圆括号,所以是数组),而不是把圆括号当做是函数调用。换句话说,我们避免了以下情况: print ('a', 'b')[1]; 其中,print() 把‘a’和‘b’当做它的第一个和第二个要打印的参数,然后 Perl 就不知道 [1] 是用来做什么的了。
清单 4 是最早的有记录可查的 JAPH,还有点别出心裁,使用了 split()、sort() 和 grep():
清单 4. Sort 然后 grep
Date:
6 Feb 90 22:31:17 GMT
From:
merlyn@iwarp.intel.com (Randal Schwartz)
print grep(s/^\d+(.*)/$1 /, sort(split(/ /,"8hacker, 4Perl 1Just 2another")));
首先,我们把起始字符串分割成四个元素:“8hacker,” “4Perl” “1Just” “2another”。
然后我们排序 ― 缺省情况下是按字母数字顺序 ― 得到: “1Just” “2another” “4Perl” “8hacker,”。
注意 “10Just” 也应该排在 "8hacker" 的前面 ― 这不是数字排序。
排序后的列表被传递到 grep(),它将每个元素开头的所有数字都去掉,并在剩下的部分后面加上一个空格。结果是: “Just ” “another ” “Perl ” “hacker, ”。
最后,在这个列表上调用 print(),逐字逐元素打印。
糟糕的
看够了 JAPH 的优点后,现在让我们来看看它真正“糟糕到极点”的地方。良好的 JAPH 还有循序渐进的教学作用,糟糕的 JAPH 却让您的思维混乱得象椒盐卷饼一样。当您盯着一段 JAPH 冥思苦想十分钟却只能头疼时,您就知道这个 JAPH 是糟糕的了。
清单 5. 代替和计算
Date:
26 Mar 90 16:20:37 GMT
From:
raymond@sunkist.berkeley.edu (Raymond Chen)
$_='x"Not ";"x\"another \";\'x\\"perl \\";x\\"hacker,\\"\'"';s/x/print/g;eval eval eval;
这里举例说明的一个普通的技巧是用另外的单词来代替原有的一个单词,然后再计算输出(实际上,在您进行这个步骤时正在建立 Perl 代码)。上面的示例中,每一个"x"都被换成了"print"。您还应该懂得 Perl 中的引用规则。Perl 在那个字符串中计算之后看到的是:x"Not ";"x\"another \";'x\"perl \";x\"hacker,\"'"(在 s/// 命令前面加上一个 print() 来察看这一点。)
字符串以一个单引号开头,所以它也必须以一个单引号结束。如果您往前面找单引号,就会发现有两个单引号转义了(带一个反斜杠),第三个才是真的。
现在,运行替换(在 s/// 命令后面加上一个 print() 自己去看结果):print"Not ";"print\"another \";'print\"perl \";print\"hacker,\"'"
接下来是我们的命令了。为什么在这里要运行三个 eval() 命令,而不是仅仅一个呢?仔细看一下。第二个 print() 是在字符串里面的,并不会被第一个 eval() 计算。但第一个 eval() 会返回计算过的第一级字符串:print"another ";'print"perl ";print"hacker,"'。它将打印出“Not”。为什么第一个 eval() 不返回字符串的第一部分?因为 eval() 只返回计算过的最后的东西。用“print eval”代替“eval eval eval”作为最后的语句,看看这样操作的效果如何。
第二个 eval 是做什么的呢?它是用来计算第二个,而不是第三个或第四个 print() 语句的。如果您观察一下就会发现它们两个都是在一对单引号内的字符串里的。第二个 eval 会返回含有第三个和第四个 print() 语句的字符串,留下刚刚打印了“another ”的那个语句。所以第二个 eval 将返回:print"perl ";print"hacker,"
第三个 eval 会运行那两个 print() 语句来结束这段 JAPH(奇怪的是,它会打印出“Not another perl hacker,”)。
正如您所看到的,分解一段糟糕的 JAPH 是要花坏闶奔涞摹<词故窍笪颐歉詹沤庖氲哪敲醇虻サ亩?鳎?詈蠖加泻眉父龈丛拥牟愦巍?
让我们来分析另一个糟糕到极点的 JAPH :
清单 6. Abigail 的天书
#Abigail
$_ = "\x3C\x3C\x45\x4F\x54"; s/
Just another Perl Hacker
EOT
清单 6 看起来也象是一段简单的 JAPH。为什么?字符串就在那里 ― 有什么神秘的地方?其实,Abigail 风格就是以一种新的方式来使用您以前所见过的东西。例如,在这里,给操作符 s/// 加上了修饰符“e”。这样就让它在进行替换之前计算右边的表达式。这样一来,“
编码的 $_字符串最后包含“
编码过的字符串和看似简单的替换是 JAPH 的支柱。尤其是替换,它可以用新的令人惊奇的方式进行,您会发现在您自己的代码中这些方式很有用。
下面是 Abigail 的另一段如恶魔般让您绞尽脑汁的 JAPH:
清单 7. Abigail 解释的原型
#Abigail
perl -wle 'sub _ "Just another Perl Hacker"; print prototype \&_'
理解这段 JAPH 需要一定的使用原型的知识。请参阅“perldoc -f sub”和“perldoc -f prototype”文档来了解这是怎么一回事。基本上,这建立了一个新的函数,它名为“_”,没有函数体,但带有一个“Just another Perl Hacker”的原型。
如果您看了 Programming Perl,第三版(请参阅参考资料)关于原型的实际章节,您会发现原型不能是字符串。Abigail 很随便地忽略了这个事实(因为这个函数永远都不会用到),然后打印出了无效的原型。
这样合法吗?可能吧,因为它并不引起程序崩溃。这样疯狂吗?只有一点点。还有很多更疯狂的方法,但这一种至少还证明了在定义函数时原型的合法性不会受到检查。它还示范了我们可以定义一个名为“_”的函数 ― 这种方法您不应该经常使用,因为它会和内建的“_”操作符冲突。
难看的
我们看过了良好的和糟糕的 JAPH,所剩下的就是难看的 JAPH 了。这些怪兽被精心打造,就是为了让人们畏惧然后到处找药吃,它们定义得太丑陋了。
下面这一段 Kickstart 编写的 JAPH 您应该送给对您非常重要的另一半(不包括家里的宠物)。注意,称其为一段难看的 JAPH 是不止一个原因的 ― 四行的限制被远远的抛在一边。
清单 8. 燃烧的心
#Kickstart from http://www.perlmonks.com/
#note: a slight valentine variation :)
$LOVE=
AMOUR.
true.cards.