我应该使用哪些Perl动态编程功能?

11

我对脚本语言(尤其是Perl)还比较陌生,我的大部分代码都是下意识地把C代码转换成Perl。

阅读 关于 Perl,经常会提到的其中一个最大区别就是Perl是一种动态语言。因此,它可以在运行时做编译型语言只能在编译时完成的事情,并且可以更好地访问实时信息。

这都没问题,但作为有些C和C++经验的我,在编写Perl代码时,应该注意哪些特定功能才能使用它具备的所有动态编程特性,从而产生一些令人惊叹的代码?


3
请注意,这句话很容易被倒转过来理解:"C++ 是一种静态语言。因此,它可以在编译时处理其他动态语言只能在运行时处理的事情,并且由于可以访问编译时信息,所以在这方面更加优秀。" - Oliver Charlesworth
2
你很可能会产生丑陋难以维护的代码。 - Byron Whitlock
14
不,如果你编写良好的代码并遵循最佳实践,这并不完全正确。你这种有偏见的陈述真让人感到羞耻! - Platinum Azure
@Lazer:是的,而且那样通常更慢、更烦人。在C++中(例如),一个人不需要在运行时检查一个结构体中是否存在特定字段,或者派生类是否正确地重写了所有虚方法,或者你是否拼写错误。当然,它不能涵盖所有可能的拼写错误和遗漏,但它是一个巨大的帮助。 - Oliver Charlesworth
速度是相对的:80% 的时间你在写的是 20% 的代码,其余的可以用 Ruby 来写 ;-). - Peter Tillemans
显示剩余3条评论
9个回答

15

这个问题足以填写一本书。事实上,这正是发生的事情!

Mark Jason Dominus的优秀著作《高阶Perl》可以在线免费获取

以下是书中引人入胜的前言摘录:

大约在1993年左右,我开始阅读关于Lisp的书籍,并发现了一些重要的东西:Perl更像Lisp,而不是C语言。如果你拿起一本好的 Lisp 书,里面会有一个描述Lisp好特点的章节。例如,Peter Norvig所著的 "人工智能编程范例" 这本书包含一个名为“Lisp的特殊之处”的部分,描述了Lisp的七个特性。Perl共享其中六个特性,而C语言没有共享任何一个特性。这些都是重要的特性,比如一级函数、动态访问符号表和自动存储管理。


10

不要将以下C编程习惯带到Perl 5中:

  • 不要在程序/函数的顶部声明变量,需要时再声明。
  • 不要在声明数组和哈希时将空列表分配给它们(它们已经为空,需要初始化)。
  • 不要使用 if (!(complex logical statement)) {},这就是 unless 的用途。
  • 不要使用 goto 打破深度嵌套的循环,nextlastredo 都可以接受循环标签作为参数。
  • 不要使用全局变量(这是一个通用规则,即使对于C语言也是如此,但我发现很多C程序员喜欢使用全局变量)。
  • 不要创建函数,闭包会更好(回调函数尤其适用)。有关更多信息,请参见 perldoc perlsubperldoc perlref
  • 不要使用输入/输出返回,而应该返回多个值。

在Perl 5中应该做的事情:

  • 始终使用 strictwarnings pragma。
  • 阅读文档(perldoc perlperldoc -f function_name)。
  • 像在C中使用 structs 一样使用哈希。

1
我不是Perl程序员,所以我从来没有理解过“unless”结构的意义。它似乎只是对已经非常清晰的东西进行无意义的语法糖处理!你知道它的原理吗? - Oliver Charlesworth
2
有时候使用 unless 语句可以使代码更加流畅。例如 return unless ($foo)。但我通常不会在复杂的逻辑语句中使用它。 - szbalint
1
@Oli:我很少使用unless--它与if not相同(除了操作顺序问题 - 如果有疑问,请使用括号)只是更长 :) - Ether
1
@szbalint:也许我只是一个脾气暴躁的C/C++程序员(不,我绝对是),但在我看来,当阅读代码时,现在似乎有两个地方需要查找控制逻辑。这种观点是否无根据? - Oliver Charlesworth
1
@Oli Charlesworth 我曾经是一名C程序员,我从不会使用||&&来进行流程控制。但作为一名Perl程序员,使用or来执行die操作是非常合理的。那些将andor用于其他目的的人使得代码难以阅读,但or die则非常清晰易懂。 - Chas. Owens
显示剩余11条评论

10

使用最具可维护性、开发时间、可测试性和灵活性等最佳组合解决您的问题。在特定应用上下文之外讨论任何技术、风格或库都没有太大用处。

你的目标不应该是为你的解决方案找问题。学习比你立即计划使用的Perl多一点(并持续学习)。有一天你会遇到一个问题,然后想起“我记得有些东西可能对此有帮助”。

你可能想看一些这些书:

  • Higher-Order Perl
  • Mastering Perl
  • Effective Perl Programming

我建议您在编码中逐渐引入新概念。Perl的设计使您无需了解很多即可开始,但您可以在学习更多知识的同时改进您的代码。试图一次掌握许多新功能通常会以其他方式给您带来麻烦。


6

我认为最大的障碍不是动态方面,而是“电池包含”方面。

我认为perl最强大的方面是:

  • 哈希:它们允许您轻松表达非常有效的数据结构
  • 正则表达式:它们被很好地集成了。
  • 使用默认变量,如$_
  • 库和CPAN用于未安装标准的任何内容

我注意到C转换器的一件事是过度使用for循环。可以使用grepmap来消除许多循环。

Perl的另一个座右铭是“有多种方法可以做到这一点”。为了攀登学习曲线,您必须经常告诉自己:“肯定有更好的方法来做这个,我不可能是第一个想要做...的人。”然后,您通常可以转向谷歌和CPAN及其荒谬的库数量。

Perl的学习曲线不陡峭,但非常漫长...花时间享受旅程。


事实上,我已经想不起上一次在Perl中使用for循环(而不是foreach)。 - Ether
1
@Ether:我更喜欢使用“for”来简洁表达,但是以foreach的风格书写。在这里我想指出,在Perl中,“for”和“foreach”是完全相同的东西。 - szbalint
@szbalint:是的,我指的是C风格的三参数循环,而不是像Perl那样的两参数for[each]循环。 - Ether

3
两点。
首先,总的来说,我认为你应该自问两个稍有不同的问题:
1)哪些Perl动态编程特性可以用于哪些情况/解决哪些问题?
2)每种特性的权衡、陷阱和缺点是什么?
然后,你的问题的答案就变得非常明显了:你应该使用比可比的非DP解决方案更好地解决你的问题(在性能或代码可维护性方面),并且产生的副作用小于最大可接受水平的特性。
例如,引用FM的评论,eval字符串形式有一些相当严重的副作用;但在某些情况下,它可能是一种极其优雅的解决方案,比任何替代DP或SP方法都要好得多。
其次,请注意,Perl的许多“动态编程”特性实际上被打包成对您非常有用的模块,您甚至可能不会意识到它们属于DP性质。
我必须想出一组好的例子,但一个立即涌现在脑海中的例子是文本模板模块,其中许多是使用上述eval字符串形式实现的;或者Try::Tiny异常机制,它使用块形式的eval
另一个例子是通过Moose实现的方面编程(我现在找不到相关的StackOverflow链接 - 如果有人有,请编辑链接)。它在底层使用了DP的符号表访问特性。

2
大多数其他评论在这里已经讨论得很全了,我不会再重复。我想重点谈一下关于在你编写代码的语言中过度或不足使用语言习惯用法的个人偏见。正如一句俗语所说,可以用任何语言编写C。同样,在任何语言中都可能编写出难以阅读的代码。
我在大学学习了C和C++,后来学习了Perl。Perl非常适合解决快速问题和一些长期解决方案。我用Perl和Oracle为国防部解决物流问题构建了一个公司,有大约100名活跃程序员。我也有一些管理其他Perl程序员(新手和老手)习惯的经验。(我是创始人/首席执行官,并不是直接从事技术管理...)
我只能评论一下我转变成为Perl程序员时所看到的情况以及我的公司所遇到的问题。我们的许多工程师都分享了我的背景,主要是通过培训成为C / C++编程人员,之后选择成为Perl程序员。
我看到的第一个问题(也是我自己遇到的问题)是编写出如此惯用的代码,以至于在短时间内就无法阅读、维护和使用。Perl和C++共享编写简洁代码的能力,在阅读时很有趣,但你会忘记它、离开它,其他人也不会理解它。
在我担任公司负责人的五年中,我们雇用了(并解雇了)很多程序员。一个常见的面试问题是:编写一个简短的Perl程序,打印出1到50之间的所有奇数,每个数字之间用空格分隔,并以CR结尾。不要使用注释。他们可以在自己的时间内完成这个任务,也可以在计算机上进行输出验证。
在他们编写脚本并解释完之后,我们会要求他们在面试官面前修改它,只打印偶数,然后根据每个单数位偶数、每个奇数位数、除了第七和第十一个数字外的其他数字等结果模式进行修改。另一个可能的修改是在这个范围内每隔一个偶数,那个范围内每隔一个奇数,并且不包括质数等等。目的是看看他们原来的小脚本是否经得起被修改、调试和讨论,并且是否预先考虑到规格可能会改变。
虽然测试没有要求“在一行中”,但许多人接受了这个挑战,使其成为一行简洁的代码,但牺牲了可读性。其他人则制作了一个完整的模块,但给出的简单规格需要太长的时间。我们的公司需要快速交付可靠的代码,所以我们使用Perl。我们需要思考方式相同的程序员。
以下提交的代码片段都执行完全相同的操作:
1) 这段代码类似于C语言,但非常容易修改。因为它采用了C风格的三个参数for循环,所以进行替代周期需要更多容易出错的修改。容易调试,也是常见的提交方式。任何几乎任何语言的程序员都能理解这个。没有什么特别的问题,但也不是杀手锏:
for($i=1; $i<=50; $i+=2) {
    printf("%d ", $i);
} 
print "\n";

非常类似 Perl,容易获取偶数,容易(通过子例程)获取其他循环或模式,易于理解:
print  join(' ',(grep { $_ % 2 } (1..50))), "\n"; #original
print  join(' ',(grep { !($_ % 2) } (1..50))), "\n"; #even
print  join(' ',(grep { suba($_) } (1..50))), "\n"; #other pattern

"太惯用语了,有点奇怪,为什么结果之间要有空格?受访者在获取偶数时犯了错误。更难调试或阅读:"
print "@{[grep{$_%2}(1..50)]}\n";   #original
print "@{[grep{$_%2+1}(1..50)]}\n"; #even - WRONG!!!
print "@{[grep{~$_%2}(1..50)]}\n"; #second try for even

聪明!但也太惯用语了。必须思考从范围运算符列表创建的“annon hash”发生了什么,以及为什么会产生奇数和偶数。无法修改为另一种模式:
print "$_ " for (sort {$a<=>$b} keys %{{1..50}}), "\n"; #orig
print "$_ " for (sort {$a<=>$b} keys %{{2..50}}), "\n"; #even
print "$_ " for (sort {$a<=>$b} values %{{1..50}}), "\n"; #even alt

5)类似C语言,但有一个坚实的框架。可以轻松地进行修改,甚至可以超越奇偶性。非常易读:
for (1..50) { 
    print "$_ " if ($_%2); 
    }              #odd
print "\n";

for (1..50) { 
    print "$_ " unless ($_%2); 
    } #even
print "\n";

也许这是我最喜欢的答案。非常类似于Perl,但易读(至少对我来说),并且形成逐步向右和从右向左的流程。列表在右侧,并且可以更改,处理立即在左侧进行,然后再次格式化到左侧,在最左侧执行'print'的最终操作。
print map { "$_ " } grep { $_ & 1 } 1..50;  #original
print "\n";
print map { "$_ " } grep { !($_ & 1) } 1..50;  #even
print "\n";
print map { "$_ " } grep { suba($_) } 1..50;  #other
print "\n";

这是我最不喜欢的可信答案。既不是C也不是Perl,如果不彻底修改循环就无法修改,主要展示了申请人熟悉Perl数组语法。他非常想要一个case语句...
for (1..50) { 
    if ($_ & 1) { 
        $odd[++$#odd]="$_ ";
        next;
    } else {    
        push @even, "$_ ";
    }
}   
print @odd, "\n";
print @even;

采访者中回答了5、6、2和1的人得到了工作并表现良好。回答7、3、4的人没有被雇用。
您的问题是关于使用动态构造,如eval或其他在纯编译语言(如C)中无法实现的内容。最后一个示例是“动态”的,其中正则表达式中包含了eval,但确实是非常糟糕的风格:
$t='D ' x 25;
$i=-1;
$t=~s/D/$i+=2/eg;
print "$t\n";     # don't let the door hit you on the way out...

许多人会告诉你“不要用 Perl 写 C 代码”。我认为这只是部分正确的。错误和错误在于,即使在 Perl 中有更多表现形式时,过于刻板地以 C 风格编写新的 Perl 代码。使用那些表现形式。是的,不要用 C 风格编写新的 Perl 代码,因为 C 语法和习惯用法是你所知道的全部。(坏狗--没有饼干)
不要仅仅因为可以而在 Perl 中编写动态代码。你会遇到某些算法,你会说“我不太知道如何用 C 写 THAT”,其中许多使用 eval。您可以编写 Perl 正则表达式来解析许多东西(XML、HTML 等),使用递归或 eval 在正则表达式中,但不应该这样做。像在 C 中一样使用解析器。然而,有些算法中,eval 是一个礼物。Larry Wall 的文件名修复程序rename需要更多的 C 代码来复制,是吗?还有许多其他例子。
不要过分死板地避免C风格。对于某些算法来说,for循环的C 3参数形式可能非常适合。此外,请记住您使用Perl的原因:大概是为了高效的程序员生产力。如果我有一个完全调试好的C代码片段,它恰好可以满足我的需求,并且我需要将其转换成Perl,那么我就会按照C风格在Perl中重写这个愚蠢的东西!这是这种语言的优点之一(但对于更大或团队项目来说也是其弱点,因为个人编码风格可能会有所不同,从而使整体代码难以跟踪。)
到目前为止,对于这个面试问题(来自回答6的申请人),最致命的口头回答是:这一行代码符合规范,并且可以轻松修改。然而,还有许多其他的编写方式。正确的方法取决于周围代码的风格、如何调用、性能考虑以及输出格式是否可能会改变。哥们儿!你什么时候开始啊?(顺便说一句,他最终进入了管理层。)
我认为这种态度也适用于你的问题。

有趣的讨论。最近我参加了一堆招聘委员会。我将这个面试问题添加到我的工具库中。谢谢。 - FMc
1
@FM:谢谢!那是很多年前(2000-2005),涵盖了我们国家的困难时期,在全世界运送各种奇怪的军事物资。我们在其中提供了帮助。如果用C语言来实现,可能需要更长时间...回想起那些挑战还是很有趣的。 - dawg

1

至少在我的看法中,“动态”的特性并不是非常重要的。我认为你需要考虑的最大区别是,在C或C++中,你通常习惯于只使用库代码带来的相当小的优势。库中已经编写和调试好了,所以很方便,但如果必要的话,你通常可以自己完成相同的事情。对于效率而言,主要是看你能否编写出更专业的东西,是否超过了库作者花费更多时间打磨每个例程的能力。然而,差异很小,除非库例程确实符合你的需求,否则你最好自己编写。

但在Perl中,这种说法不再适用。与C相比,(庞大的)库中的许多内容实际上是用C编写的。除非你编写一个C模块,否则尝试自己编写任何类似的东西几乎肯定会慢得多。因此,如果你能找到一个库例程,它甚至接近你想要的功能,那么你最好使用它。在Perl中,使用预先编写的库代码比在C或C++中更加重要。


Perl有一些优秀的库,但与C不同的是,大多数库并未默认安装。CPAN提供了一些很好的工具来安装它们,但需要手动运行。您需要注意您的依赖关系。 - BillThor
1
我认为大多数C库也不是默认安装的。 :) - brian d foy

1

良好的编程实践并不特定于个别语言,它们适用于所有语言。从长远来看,您可能会发现最好不要依赖动态语言中可能存在的技巧(例如,可以返回整数或文本值的函数),因为这会使代码更难以维护和快速理解。因此,最终回答您的问题,我认为您不应该寻找特定于动态类型语言的功能,除非您有一些强烈的原因需要它们。保持简单和易于维护-这将在长期内更具价值。


-2

动态语言有很多你只能用它来做的事情,但最酷的是eval。请参阅此处以获取更多详细信息。

使用eval,您可以执行字符串,就像它是预先编写的命令一样。您还可以在运行时按名称访问变量。

例如,

$Double = "print (\$Ord1 * 2);";

$Opd1 = 8;
eval $Double;  # Prints 8*2 =>16.

$Opd1 = 7;
eval $Double;  # Prints 7*2 =>14.

变量$Double是一个字符串,但我们可以将其执行为一个常规语句。这在C/C++中是不可能的。

很酷的一点是,字符串可以在运行时进行操作;因此,我们可以在运行时创建命令。

# string concatenation of operand and operator is done before eval (calculate) and then print.
$Cmd = "print (eval (\"(\$Ord1 \".\$Opr.\" \$Ord2)\"));";

$Opr  = "*";
$Ord1 = "5";
$Ord1 = "2";

eval $Cmd;  # Prints 5*2 => 10.

$Ord1 = 3;
eval $Cmd;  # Prints 5*3 => 15.

$Opr = "+";
eval $Cmd;  # Prints 5+3 => 8.

eval非常强大,就像蜘蛛侠一样,力量伴随责任。明智地使用它。

希望这对你有帮助。


4
虽然eval有其使用场景,但在我看来,这些示例都不符合该命令的良好使用。此外,需要注意字符串eval和块级eval之间的重要区别。无论如何,我不会鼓励刚学这门语言的程序员专注于eval。相反,应将其视为最后的选择,特别是字符串eval,因为通常有更好的方法。 - FMc
@FM - 你是在模仿以太网吗? :) - DVK
我完全同意你对eval的危险性的看法。事实上,在我的帖子中,我提到它应该小心使用。但这并不改变eval是动态语言(包括Perl)最独特的功能之一的事实,尽管它有所有的危险性。 - NawaMan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接