【问题标题】:What compile time features does Perl provide that other languages don't?Perl 提供了哪些其他语言没有的编译时特性?
【发布时间】:2011-04-04 06:51:11
【问题描述】:

Perl 是否被视为通用编程语言?

Wikipedia阅读它

Perl 具有图灵完备的文法,因为解析会受到编译阶段执行的运行时代码的影响。[41]因此,Perl 不能被直接的 Lex/Yacc 词法分析器/解析器组合解析。相反,解释器实现了自己的词法分析器,它与修改后的 GNU bison 解析器协调以解决语言中的歧义。

人们常说“只有 perl 才能解析 Perl”,意思是只有 Perl 解释器 (perl) 才能解析 Perl 语言 (Perl),但即便如此,一般情况下也不是这样。因为 Perl 解释器可以在其编译阶段模拟图灵机,所以它需要确定停机问题才能在每种情况下完成解析。 Halting Problem 是不可判定的,这是一个长期存在的结果,因此即使 perl 也不能总是解析 Perl。 Perl 做出了不同寻常的选择,即让用户在其自己的编译阶段访问其全部编程能力。理论上的纯度成本很高,但实际不便似乎很少。

因此,它说虽然 Perl 具有图灵完备的标志,但它与其他语言不同,因为它让“用户在其自己的编译阶段访问其全部编程能力”。那是什么意思? Perl 在编译阶段为我提供了哪些其他人没有的编程能力?

【问题讨论】:

  • 这是一个非主观回答的正确问题。
  • 此维基百科条目需要编辑。编译器不需要在编译期间“决定”停止问题,即使它已经完成。这是循环逻辑。

标签: perl scripting programming-languages


【解决方案1】:

Perl 的所有功能都没有出现在任何其他语言中。 Lisp 可以做任何事情(Lisp 是一个示例,在这里。)。因此,也许我们可以将问题缩小到 Perl 的哪些特性使得广泛的行为摆动成为一件容易的事情。

  • BEGIN 块(END 块,也是。)在编译期间改变行为。所以我可以编写 Perl 代码来更改要加载的模块的位置。

    即使是下面的代码也可能有不同的含义。

    use Frobnify;
    Frobnify->new->initialize;
    

    因为我本可以改变 Frobnify 的加载位置:

    BEGIN { 
        if ( [ localtime ]->[6] == 2 ) { 
            s|^/var|/var/days/tuesday| foreach @INC;
        }
    }
    

    所以在星期二,我加载 /var/days/tuesday/perl/lib/Frobnify.pm

  • Source Filters 可以以编程方式编辑将要执行的代码。 (关于源过滤器的警告!)(粗略且大致相当于 LISP 宏)

  • 在某种程度上与BEGIN 块一起是@INC hooks。因为我可以在开头修改@INC 以查看加载的内容。我可以在@INC 数组的前面设置一个子例程来加载我想要加载的任何内容。钩子可以接收加载Frobnify的请求,并通过加载Defrobnify.pm来响应它。

  • 与此相关的是符号操作。加载Defrobnify.pm后,我可以这样做:

    *Frobnify:: = \*Defrobnify::;
    

    现在Frobnify->new 创建一个Defrobnify 对象!

【讨论】:

  • LISP 比 Perl 更常用。
  • @delnan 这取决于“已编译”的含义。 Perl 被编译成字节码。 @Axeman:“LISP 可以做任何事情”:这是什么意思?任何图灵完备的语言在可计算性方面都可以做任何事情,但我还没有看到一种语言不缺乏我喜欢的其他语言的特性。
  • @delnan:Perl总是编译,所以这是不可能的。我不是最新的 LISP,我的理解是它被解释了。
  • @Philip Potter:是的,理论上 C 可以进行自我修改代码,因为它可以重写内存中的指令——但是没有重写指令的工具,所以你有机会以可移植的方式“做任何事情”尽管理论上它具有这种能力,但使用 C 的能力大大降低。
  • @Philip Potter,Common LISP 有 CLOS,可以进行 OO、Mixins 和面向方面的编程。 LISP 的一个关键特性是它可以以一种使扩展看起来与内置构造没有什么不同的方式进行扩展。在 C 或 Perl 中,代码是程序必须手动解析才能操作的非结构化字符串。这通常容易出错。另一方面,LISP 将所有代码视为一个数据结构,它将执行或将其传递给可以在执行前更改结构的特殊函数。
【解决方案2】:

子例程原型是 Perl 或多或少独有的编译时特性。许多 Perl 的内置函数在其参数(标量、列表、引用、代码块、捕获)上强加了特殊类型的上下文。原型是将部分功能移植到用户定义的子程序的一种方式。

例如,Perl 允许您使用(&) 原型有效地生成新的句法结构。这在 Try::Tiny 等模块中用于向语言添加 trycatch 关键字:

    try {
            die "foo";
    } catch {
            warn "caught error: $_"; # not $@
    };

这是因为trycatch 被声明为sub try (&;@) { ... }sub name {...} 语法等同于BEGIN { *name = sub {...} },这意味着它具有编译时效果。在try 的情况下,(&;@) 原型告诉编译器任何时候它看到标识符try,第一个参数必须是一个裸块,并且块后面是一个可选列表。

这只是原型的一个例子,它们还可以做很多其他事情:

$  imposes scalar context on an argument
&  imposes code context on an argument
@  imposes list context on an argument
%  imposes list context (with an even number of elements)
*  imposes glob context on the argument 
\$ imposes scalar reference context
\@ imposes array reference context
   ... for the rest of the sigils

由于它们的强大功能(并且在其他语言中不存在),原型可能会令人困惑,因此最好适度使用。 (就像 Perl 的所有其他高级功能一样)。

【讨论】:

  • 另外,名字也是个问题。 C 原型与 Perl 原型完全不同。
【解决方案3】:

简单的答案是BEGIN 块提供图灵完备性:

BEGIN {
    my $foo = turing_machine_simulator($program);
}

BEGIN 块在 perl 编译器看到它们时立即执行。这意味着可以要求编译器执行任意复杂度的任务。 Perl 能做的任何事情,它都可以在编译阶段做。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-04-09
    • 1970-01-01
    • 2011-01-06
    • 1970-01-01
    • 2010-09-09
    • 2011-02-21
    • 1970-01-01
    • 2011-07-17
    相关资源
    最近更新 更多