启示录 3
编辑注:这个启示录已经过时,但出于历史原因仍在此保留。有关最新信息,请参阅概要 03。
对我来说,语言设计中最令人痛苦的一个方面是设计一套有用的运算符系统。对于其他语言设计者来说,这可能是令人烦恼的小事。毕竟,你可以把所有运算符视为简单的语法糖——运算符只是看起来古怪的功能调用。一些语言将所有功能调用统一成一种语法作为一种特性。因此,所谓的函数式语言往往会让你括号键磨损,而面向对象的编程语言往往会让你点号键磨损。
但尽管你的电脑喜欢一切看起来都一样,大多数人并不像电脑那样思考。人们喜欢不同的事物看起来不同。他们也希望有快捷方式来完成常见任务。(即使是数学家也不会追求完全正交。我们通常用于运算符的许多快捷方式实际上最初是由数学家发明的。)
因此,让我列举一些在设计运算符系统时我会权衡的原则。
- 不同的运算符类别应该看起来不同。这就是为什么文件测试运算符与字符串或数值运算符看起来不同。
- 相似的运算符类别应该看起来相似。这就是为什么文件测试运算符看起来彼此相似。
- 常见的操作应该进行“霍夫曼编码”。也就是说,常用运算符应该比不常用运算符短。在我看来,Perl 5中的标量运算符太长了。
- 保留你的文化很重要。所以Perl从其他熟悉的语言中借鉴了许多运算符。例如,我们使用了Fortran的
**
运算符来进行指数运算。当我们进入Perl 6时,大多数运算符将直接从Perl 5“借鉴”。 - 打破你的文化也很重要,因为这是我们理解其他文化的方式。作为一个明确的多文化语言,Perl在这个领域一直做得不错,尽管我们总是可以做得更好。计算机文化之间跨文化交流的例子包括XML和Unicode。(不出所料,这些功能也使人类文化之间的跨文化交流更好——我们真诚地希望。)
- 有时运算符应该对其上下文做出响应。Perl有许多运算符在标量上下文和列表上下文中执行不同但相关的事情。
- 有时运算符应该将其上下文传播到其参数上。当前的
x
运算符对其左参数这样做,而短路运算符对其右参数这样做。 - 有时运算符应该强制对其参数施加上下文。历史上,Perl的标量数学运算符强制对其参数施加标量上下文。以下讨论的某些RFC建议对其进行修订。
- 有时运算符应该对其参数的类型做出多态响应。方法调用和重载就是这样工作的。
- 运算符优先级的设计应最小化括号的需求。你可以把运算符的优先级看作是对运算符的部分排序,这样它就最小化了典型代码中需要括号的“不自然”配对的数量。
- 运算符优先级应该尽可能简单。Perl的优先级表目前有24个级别。这可能太多也可能不够。如果我们放弃与C-like运算符的严格C兼容性,我们可能将其减少到大约18个级别。
- 人们实际上并不想过多地考虑优先级,所以优先级的设计应该符合期望。不幸的是,了解优先级表的人的期望不会与不了解的人的期望相匹配。而且Perl一直迎合C程序员的期望,至少到目前为止。我们无法从一开始就处理不同的文化期望。
这些原则中的任何一个都很容易被推向极端,从而牺牲其他原则。事实上,各种语言都曾这样做过。
我的主要设计原则始终是,解决方案的复杂性应该很好地映射到问题空间的复杂性。简化是好的!过度简化是坏的!在解决方案空间上施加人为的限制会导致与问题空间的阻抗不匹配,结果就是使用人为简单化的语言会在该语言编写的所有解决方案中引入人为复杂性。
所有计算机语言都必须处理的一个人为限制是键盘上可用的符号数量,这大致相当于ASCII符号的数量。大多数计算机语言通过定义包括双字符图、三字符图等系统来补偿。这在某种程度上是有效的。但这意味着某些常见的单字符运算符不能用作双字符图运算符的结尾。C语言的早期版本中,赋值运算符的顺序是错误的。例如,曾经有一个=-
运算符。如今它被写作-=
,以避免与单字符减号冲突。
同样,在不要求大多数二进制运算符之前加空格的情况下,很难简单地定义一个单字符=
运算符,因为许多二进制运算符以=
字符结尾。
Perl通过跟踪是否期望运算符或项来解决这些问题。实际上,单字符运算符就是当Perl期望项时出现的运算符。因此,Perl可以跟踪单字符=
运算符,即使人类程序员可能会感到困惑。因此,我将单字符=
运算符归类为“可以,但不要用它做会引起广泛混淆的事情。”请注意,我目前没有提出单字符=
运算符的具体用途。我只是在告诉你们我的想法。如果我们最终实现了单字符=
运算符,我们希望这些问题得到考虑。
虽然我们可以根据期望运算符或项来区分运算符,但这也意味着一些语法限制。例如,你不能用相同的符号同时表示后缀运算符和二进制运算符。所以你永远不会在Perl中看到二进制++
运算符,因为Perl不知道在该符号后是期望项还是运算符。这也意味着我们不能使用“相邻”运算符。也就是说,你不能把两个项并排放置,并期望发生某些事情(例如,如awk中的字符串连接)。如果第二个项以看起来像运算符的东西开始怎么办?它将被误解为二进制运算符。
好了,不要再说这些模糊的普遍性了。让我们谈谈具体而模糊的内容。
这次灾变的RFC(通常)散布各地,但并没有涵盖所有内容。我先谈谈RFC涵盖了什么,然后谈谈没有涵盖的内容。以下是恰好被归类到第3章的RFC。
RFC PSA Title
--- --- -----
024 rr Data types: Semi-finite (lazy) lists
025 dba Operators: Multiway comparisons
039 rr Perl should have a print operator
045 bbb C<||> and C<&&> should propagate result context to both sides
054 cdr Operators: Polymorphic comparisons
081 abc Lazily evaluated list generation functions
082 abc Arrays: Apply operators element-wise in a list context
084 abb Replace => (stringifying comma) with => (pair constructor)
104 ccr Backtracking
138 rr Eliminate =~ operator.
143 dcr Case ignoring eq and cmp operators
170 ccr Generalize =~ to a special "apply-to" assignment operator
283 ccc C<tr///> in array context should return a histogram
285 acb Lazy Input / Context-sensitive Input
290 bbc Better english names for -X
320 ccc Allow grouping of -X file tests and add C<filetest> builtin
请注意,您可以点击以下RFC标题来查看有关RFC的副本。讨论有时假设您已经阅读了RFC。
编辑注:这个启示录已经过时,但出于历史原因仍在此保留。有关最新信息,请参阅概要 03。
RFC 025:运算符:多路比较
以前的灾变 |
本RFC建议,涉及多个链式比较的表达式应该像数学家预期的那样工作。也就是说,如果你这样说
0 <= $x < 10
那么它实际上意味着类似这样
0 <= $x && $x < 10
$x
只会被评估一次。(这与我们用来解释赋值运算符,如$x += 3
的改写规则非常相似。)
我开始考虑这个RFC,仅仅是因为无论我接受与否,它都不会产生任何翻天覆地的变化。权衡的焦点在于是否在语法中增加一点复杂性,以节省某些Perl程序中的一些小复杂性。在这里,语法的复杂性并不是什么大问题,因为它分散到所有可能的用途中,并且它已经符合大量人的已知心理。
然而,有一个潜在的问题与优先级级别有关。如果我们选择允许像这样的表达式
0 <= $x == $y < 20
那么我们就必须将比较运算符的优先级与等价运算符的优先级统一。我觉得这并没有什么大问题,因为它们之所以不同,主要(我认为)是为了让你能够编写两个比较的排他表达式,如下所示
$x < 10 != $y < 10
然而,Perl有一个内置的 xor
运算符,所以这并不是什么大问题。而且,在那个最后表达式中强制括号也是为了清晰。所以除非有人提出我尚未注意到的大反对意见,否则这个RFC将被接受。
RFC 320:允许分组 -X 文件测试并添加 filetest
内置函数
这个RFC提议允许将文件测试运算符分组,就像一些Unix工具允许将单个字符开关捆绑在一起一样。也就是说,如果你说
-drwx $file
那么它实际上意味着类似这样
-d $file && -r $file && -w $file && -x $file
遗憾的是,按照目前的提案,这种语法将会非常令人困惑。我们必须能够否定命名运算符和子程序。在单一负号后放置空格的提议的解决方案过于繁琐和反直观,至少是反文化的。
拯救这个提案的唯一方法可能就是断言这些运算符以某种方式自动加载;任何被否定但 未识别 的运算符将被假定是一个分组的文件测试。这可能会很危险,因为它将阻止Perl在编译时捕捉到被否定时拼写错误的子程序名称,并且如果名称中的所有字符都是有效的文件测试,并且如果参数可以被解释为一个文件名或文件句柄(这通常是这样),那么错误可能根本无法在运行时捕捉到。在Perl 6中,所有方法名称在运行时之前基本上都属于未识别类别,所以不可能确定是否将减号解析为真正的否定。Perl 6的可选类型声明只能帮助编译器处理实际声明为有类型的变量。幸运的是,一个否定的1仍然是真实的,所以即使我们将否定解析为真正的否定,它仍然可能做正确的事情。但这非常糟糕。
所以我在考虑一个不同的方法。不是捆绑字母
-drwx $file
让我们考虑返回 $file
的值以表示真值的方法。然后我们会写出嵌套的单一运算符,如下所示
-d -r -w -x $file
那个问题的一个难点是运算符是从右到左应用的。并且它们并不真正地像堆叠的 &&
那样短路(尽管优化器可能可以修复这个问题)。所以我预计我们可以为此设置默认值,如果你想将 -drwx
作为自动加载的后备,你可以明确声明。
无论如何,提议的 filetest
内置函数不一定必须是内置的。它可以是通用方法。(或者可能是字符串和文件句柄的通用方法?)
我在使级联运算符像那样工作时的一个犹豫是,人们可能会被诱惑去玩弄返回的文件名
$handle = open -r -w -x $file or die;
这可能会对很多人来说非常令人困惑。这个难题的解决方案将在下一节的末尾呈现。
RFC 290: 为-X操作符提供更好的英文名称
本RFC建议使用长名称作为各种文件测试操作符的别名,因此,你不必说
-r $file
你可以说类似于以下的内容
use english;
freadable($file)
实际上,我觉得没有必要使用use english
,这些名称应该是通用的(或几乎是通用的)方法。无论如何,我们应该开始习惯这样的想法,即mumble($foo)
与$foo.mumble()
等价,至少在没有局部子程序定义的情况下是这样。因此,我认为我们会看到以下两种情况
is_readable($file)
和
$file.is_readable
类似于前一部分中的级联文件测试操作,一种可能的方法是布尔方法在成功时返回相关对象,这样就可以堆叠方法调用而无需重复对象
if ($file.is_dir
.is_readable
.is_writable
.is_executable) {
但是,对于某些可读性的定义来说,-drwx $file
可能仍然更易读。而且级联方法并没有真正短路。此外,返回的值可能必须是类似于“$file is true”的东西,以防止对文件名“0”的混淆。
还有一个问题,就是这除了给我们带来一点书写便利之外,是否真的节省了什么。如果每个这些方法都必须对文件名执行stat操作,那么它将会相当慢。为了解决这个问题,我们实际上需要返回的不仅仅是一个文件名,而是一个包含stat缓冲区的对象(在Perl 5中由_
字符表示)。如果我们这样做,我们就不必玩$file is true
的游戏,因为有效的stat缓冲区对象(假设)始终是真实的(至少直到它变为假)。
同样的论点也适用于我们之前讨论过的级联文件测试操作符。一个自动加载的-drwx
处理程序可能会足够智能地只做一次stat。但通过调用自动加载机制,我们可能会失去速度上的优势。因此,级联操作符(无论是-X
风格还是.is_XXX
风格)是可行的方法。它们只是返回对象,这些对象知道如何在上下文中是布尔对象还是stat缓冲区对象。这暗示着你甚至可以说
$statbuf = -f $file or die "Not a regular file: $file";
if (-r -w $statbuf) { ... }
这允许我们简化Perl 5中由_
令牌表示的特殊情况,这总是很难解释。而且返回stat缓冲区而不是$file
可以防止混淆
$handle = open -r -w -x $file or die;
当然,除非我们决定让stat缓冲区对象在字符串上下文中返回文件名。:-)
编辑注:这个启示录已经过时,但出于历史原因仍在此保留。有关最新信息,请参阅概要 03。
RFC 283: 在数组上下文中tr///
应该返回直方图
是的,但是...
虽然我确实很久以前就把这项内容放入待办事项列表中,但我觉得直方图可能应该有自己的接口,因为直方图应该以完整的哈希表的形式在标量上下文中返回,但我们无法猜测他们是否需要一个直方图来进行普通的tr///
操作。另一方面,它可能只是一个/h
修饰符。但我们已经对tr///
做了暴力修改,使其在不进行转写的情况下进行字符计数,所以这可能并不是那么遥不可及。
这个RFC的一个问题是在输入字符串上而不是在输出字符串上做直方图。原始的待办事项条目没有指定这一点,但这是我原本的意图。但将直方图应用于最终字符更有用,因为这样你就可以使用tr///
本身将字符分类,例如,元音和辅音,然后计算结果中的V和C的数量。
另一方面,我认为tr///
的接口真的很糟糕,并且每天都在变得更糟糕。整个tr///
接口对于任何动态生成数据来说都很糟糕。即使没有动态数据,也存在严重的问题。当字符集只是ASCII时,就已经很糟糕了。基本问题是符号是颠倒的,因为实际上它没有显示哪些字符对应,所以你必须计数字符。在Perl 5中,我们在以下方面取得了一些进展,我们允许你说
tr/abcdefghijklmnopqrstuvwxyz/VCCCVCCCVCCCCCVCCCCCVCCCCC/
tr[abcdefghijklmnopqrstuvwxyz]
[VCCCVCCCVCCCCCVCCCCCVCCCCC]
如果您知道左侧的重复项更喜欢第一次提及而不是后续提及,您还可以玩一些花招。
tr/aeioua-z/VVVVVC/
但是您仍在与记法作斗争。我们需要一种更明确的方法来将字符类对应起来。
当我们将字符集扩展到ASCII之外时,会出现更多的问题。长期以来,tr///
用于大小写转换的使用已经半废弃,因为像 tr/a-z/A-Z/
这样的范围省略了带重音符号的字符。现在随着Unicode的出现,关于什么是字符的整个概念变得更加容易解释,而 tr///
接口并没有告诉Perl是否将字符修饰符视为基本字符的一部分。对于某些双宽字符来说,甚至很难仅仅 看 到字符就能判断它是一个字符还是两个。计数字符列表和Fortran中的hollerith字符串一样古老。
因此,我怀疑tr///
语法将被降级为只是实际转写模块的一个类似引号接口,其主要接口将以翻译对的形式指定,左边给出一个匹配模式(通常是字符类),右边说明如何翻译匹配项。把它想象成一系列协调的并行 s///
操作。语法仍然可以协商,直到末日5。
但其中肯定可以有一个直方图选项。
RFC 084:将 =>
(字符串化逗号)替换为 =>
(对构造函数)
我喜欢对的概念,因为它不仅适用于散列值。命名参数几乎肯定会使用对来实现。
我对RFC有一些争议。建议的 key
和 value
内置函数应该是配对对象上的lvalue方法。如果我们使用配对对象来实现散列中的条目,则键必须是不可变的,或者如果键更改,必须有某种重新散列键的方法。
关于使用配对进行mumble-but-false的内容是错误的。我们将使用属性来处理这种诡计。 (并且多路比较不会依赖于这种诡计。参见上文。)
RFC 081:延迟评估的列表生成函数
对不起,您不能拥有冒号—至少,不能不共享它。冒号将是一种“超级逗号”,为某个先前运算符提供一个副词列表,在这种情况下将是先前的冒号或点号。
(我们无法将 ?:
实现为 ?
上的 :
修饰符,因为优先级将是错误的,除非我们将 :
限制为单个参数,这将阻止其用于消除间接对象的歧义。关于这一点以后再谈。)
有关 attributes::get(@a)
的RFC建议已被值属性所取代。因此,如果变量支持相关方法,则 @a.method()
应直接提取变量的属性。一个延迟列表对象肯定应该有这些方法。
将延迟列表分配给绑定数组是一个问题,除非绑定实现处理延迟。默认情况下,绑定数组可能强制立即列表评估。立即列表评估不适用于无限列表。这意味着如果您尝试说类似
@my_tied_file = 1..Inf;
延迟评估是可能的,但并不一定是规范。在数学领域中延迟“纯”函数的评估很好,因为您可能无论如何都会得到相同的结果。但是,许多Perl编程都是用随时间变化的真实世界数据完成的。如果 $b
可以更改,并且延迟函数仍然引用变量而不是其实时值,那么说 somefunc($a .. $b)
可能会变得非常混乱。另一方面,捕获当前状态也存在开销。
另一方面,懒列表对象确实是值的快照,在这个情况下没有问题。忘记我提到过它了。
关于懒列表的棘手之处不在于懒列表本身,而在于它们与语言其他部分的交互方式。例如,如果你说
@lazy = 1..Inf;
@lazy[5] = 42;
@lazy
在修改后仍然是懒的吗?我们是否还记得@lazy[5]
是一个“例外”,并继续按照原始规则生成剩余的值?如果@lazy
是由递归函数生成的呢?我们是否已经生成了@lazy[5]
会很重要吗?
那么我们如何简单地向人们解释这一点,让他们理解?我们必须非常清楚地区分抽象值和具体值。我认为懒列表是数组默认值的定义,而数组的实际值将覆盖任何默认值。将值赋给之前已经缓存的元素将覆盖缓存的值。
如果能有一种方法来声明不能被覆盖的“纯”数组定义,这将有助于优化器。
再考虑这一点
@array = (1..100, 100..10000:100);
单个平面数组可以作为其默认定义的一部分拥有多个懒列表。我们必须跟踪这一点,如果定义通过切片定义开始重叠,这可能会变得特别棘手。
在实践中,人们会将默认值视为实际值。如果你将懒列表作为数组参数传递给一个函数,该函数可能不知道或关心它从数组中获取的值是即时生成的还是一开始就在那里。
我可以想到其他可能引起问题的方面,但我确信我太笨拙了,无法想出所有这些问题。尽管如此,我的直觉是我们可以使事物更符合人们的期望,而不是更不符合。我总是有点羡慕REXX可以有默认值的数组。:-)
编辑注:这个启示录已经过时,但出于历史原因仍在此保留。有关最新信息,请参阅概要 03。
RFC 285:懒输入/上下文敏感输入
使用want()
来解决这个问题是错误的,但我觉得基本思想是正确的,因为这是人们的期望。实际上,want()
应该是多余的。本质上,如果列表赋值右侧产生懒列表,而左侧请求有限数量的元素,则列表生成器只会产生足够满足需求的元素。它不需要提前知道数量。它只需要在请求时产生另一个标量值。生成器不需要对其上下文很聪明。懒列表生成器的口号应该是,“我们的不是去质疑为什么,我们的只是去做(下一个)或者死。”
使这一点工作起来会很有挑战性
($first, @rest) = 1 .. Inf;
RFC 082:数组:在列表上下文中逐元素应用运算符
APL,我们来了… :-)
这是这些RFC中最难决定的一个,所以我将在这里进行很多思考。这是研究——或者至少,是搜索。请耐心等待。
我预计Perl程序员分为两类——那些会认为这些“超”运算符很自然的人,以及那些不会的人。默认启用此功能可能会给那些(根据Perl 5的经验)期望在列表上下文中数组总是返回其长度的用户带来很多烦恼。可以合理地认为我们需要使标量运算符默认,但要在词法作用域内轻松启用超运算符。无论如何,这两组运算符都需要在任何地方可见——我们只是在争论谁得到简短的传统名称。所有运算符都可能需要更长的名称来用作函数调用。而不是只使用长名称来命名运算符
operator:+
operator:/
更长的名称可以像这样区分“超”性质
@a scalar:+ @b
@a list:/ @b
这暗示它们也可以这样调用
scalar:+(@a, @b)
list:/(@a, @b)
我们可能会发现一些简短的词首字符可以代表“列表”或“标量”。明显的候选者是@
和$
@a $+ @b
@a @/ @b
不幸的是,在这种情况下,“明显”与“错误”是同义的。从视觉角度来看,这些操作符会让人完全困惑。如果将名词标记放在名词上的主要心理目的是为了让它们从动词中脱颖而出,那么就不应该在动词上使用相同的标记。这就像德国人开始将所有单词都大写,而不是只大写名词一样。
相反,我们可以借鉴shell通配符中的单数/复数memelet,其中*
代表多个字符,而?
代表一个字符。
@a ?+ @b
@a */ @b
但这有一个不好的歧义。你怎么知道**
是指数运算还是列表乘法?所以如果我们走这条路,我们可能不得不这么说:
@a ?:+ @b
@a *:/ @b
或者类似的。但如果我们要走这么远,那么可能有一些前缀字符不会那么模糊。冒号和点也具有视觉上的单数/复数价值。
@a .+ @b
@a :/ @b
我们已经在改变点(并且我计划拯救冒号,使其不再是?:
操作符)的旧意义,所以也许这可以行得通。你几乎可以认为点和冒号是互补的方法调用,你可以这样表示:
$len = @a.length; # length as a scalar operator
@len = @a:length; # length as a list operator
但这会干扰冒号的其他期望用途。此外,将这些视为单数和复数操作符实际上会让人困惑,因为我们虽然指定了我们想要一个“复数”操作符,但没有指定如何处理复数。考虑这个例子:
@len = list:length(@a);
任何人都可能天真地认为返回的是列表的长度,而不是列表中每个元素的长度。为了在英语中使其起作用,我们实际上必须说些像这样的事情:
@len = each:length(@a);
$len = the:length(@a);
这将相当于以下方法调用:
@len = @a.each:length;
$len = @a.the:length;
但这真的意味着存在两个具有那些奇怪名称的数组方法吗?我不这么认为。我们已经达到了一个几乎可以说是reductio ad absurdum的结果。在我看来,这个RFC的整个重点是,“eachness”最简单的是通过列表上下文来指定的,再加上length()
是一个将一个标量值映射到另一个值的函数/方法。这个函数在数组值上的分布不是标量函数应该关心的,除非它必须确保其类型签名是正确的。
而且,这就是问题的关键。我们真正在谈论的是为了正确工作而强制强类型。当我们说
@foo = @bar.mumble
我们怎么知道mumble
是否具有魔法般地使@bar
可迭代的类型签名?那个定义在其他某个我们可能还没有完全记住的文件中。我们需要一些更明确的语法来表明期望自动迭代,无论操作符的定义是否得到了很好的规范。魔法自动迭代在可选类型语言中不会很好地工作。
因此,解决方案是,操作符的无标记形式将像Perl 5中那样强制标量上下文,我们需要一个特殊的标记来表示操作符应该自动迭代。这个特殊的标记是一个向上箭头,向高阶函数致敬。也就是说,超操作符
@a ^* @b
等同于这个
parallel { $^a * $^b } @a, @b
(其中parallel
是一个假设的函数,可以并行迭代多个数组。)
超操作符还会直觉地判断其参数中是否缺少一个维度,并在该维度上复制一个标量值到列表值。这意味着你可以说
@a ^+ 1
来得到一个值,该值是@a
中每个元素加1的结果。(@a
保持不变。)
我相信上箭头符号没有不可克服的歧义。目前有一个表示异或的上箭头运算符,但在实际中很少使用,并且使用时通常不跟其他运算符一起使用。我们可以用~
来表示异或。(无论如何,我都喜欢这个想法,因为一元~
是1的补码,二元的~
将只是对第一个参数中位集合的第二参数进行1的补码。另一方面,波浪线还有其他的文化含义,所以它是否是正确的事情并不完全明显。尽管如此,这就是我们正在做的事情。)
无论如何,本质上,我是在拒绝这个RFC的潜在前提,即我们将有足够强的类型来直观地了解正确的行为而不会让人困惑。尽管如此,我们仍然会有易于使用(更重要的是,易于识别)的超运算符。
这个RFC还询问了如何指定如abs()
等函数的返回值。我预计子声明(可选)可以包括返回类型,这样就可以弄清楚哪些函数知道如何将标量映射到标量。我们还应该再次指出,尽管基本语言不会试图推断哪些运算符应该是超运算符,但从原则上讲,没有人不能发明一种实现这一点的方言。如果你预先声明,一切皆有可能。
RFC 045: ||
和 &&
应将结果上下文传播到两侧
是的。这使得它在Perl 6中工作,在Perl 5中几乎不可能工作,是因为在Perl 6中,列表上下文不意味着立即列表展平。更准确地说,它以概念上的方式指定立即列表展平,但实现可以延迟展平直到实际需要时。内部,展平的列表仍然是一个对象。所以当@a || @b
评估数组时,它们被评估为可以返回布尔值或列表的对象,具体取决于上下文。并且将同时应用两种上下文到第一个参数上。(当然,计算机实际上首先查看它的布尔上下文。)
这与RFC 81没有冲突,因为这些运算符的超版本将这样表示
@a ^|| @b
@a ^&& @b
编辑注:这个启示录已经过时,但出于历史原因仍在此保留。有关最新信息,请参阅概要 03。
RFC 054: 运算符:多态比较
我不确定在数字等于字符串等于时使用回退的性能影响。也许vtables有助于解决这个问题。但我认为这个RFC提出的建议过于具体。更普遍的问题是,你如何允许内置的变体,而不仅仅是==
,还有其他像<=>
和cmp
这样的运算符,更不用说所有其他具有标量和列表变体的运算符了。
一个通用的相等运算符可以由运算符定义提供。我预计类似的机制将允许我们定义比较运算符cmp
的抽象程度,这样我们就可以根据定义的Unicode级别进行排序和归并。
你无法进行通用编程的论点有些牵强。Perl 5中的问题是你不能命名运算符,所以即使你想,你也不能用一个通用运算符代替特定的运算符。我认为在Perl 6中确保所有运算符都有真实的功能名更为重要。
operator:+($a, $b); # $a + $b
operator:^+(@a, @b); # @a ^+ @b
my sub operator:<?> ($a, $b) { ... }
if ($a <?> $b) { ... }
@sorted = collate \&operator:<?>, @unicode;
RFC 104: 回溯
正如所提议的,这可以通过运算符定义来轻松完成,以调用一系列闭包。然而,我不确定这个建议是否完整。可能需要对回溯引擎有更多的“没有发生”语义。如果你通过赋值模拟Prolog统一,如何在回溯超过它之后取消变量的分配?
通常,临时值的作用域限于一个块,但我们在这里使用块的方式不同,就像括号在正则表达式中的作用一样。后面的括号不会撤销前面括号的“统一”。
在正常的命令式编程中,这些临时的确定是通过普通的范围变量来记忆的,当前假设通过递归扩展。一个andthen
操作符需要一种方式来保持BLOCK1的范围直到BLOCK2成功或失败。也就是说,从词法范围的角度来看
{BLOCK1} andthen {BLOCK2}
需要更像是
{BLOCK1 andthen {BLOCK2}}
这可能会作为一个简单的模块很难安排。然而,通过重写规则,可能会在BLOCK1中安装必要的范围语义,使其能够如此工作。所以我认为这并不像连续性那样是一个原始的概念。现在让我们假设我们可以从连续性构建回溯操作符。这些将在未来的某个时候介绍。
RFC 143:忽略大小写的eq
和cmp
操作符
这是另一个RFC,它提出了一个可以通过更通用的功能来处理的具体特性,在这个案例中,是一个操作符定义
my sub operator:EQ { lc($^a) eq lc($^b) }
顺便说一下,我注意到RFC被规范化为大写。我怀疑现在将这些规范化为小写可能更好,因为Unicode区分标题大小写和大写,并为两者都提供了映射到小写的方法。
RFC 170:将=~
泛化为一个特殊的“应用到”赋值操作符
我认为论点不应该从右边来。我认为将其视为一个对象会更自然,因为所有Perl变量本质上都是对象,如果你挖掘它们的话。呃,左边。
我很好奇我们是否可以将=~
泛化为一个列表操作符,它可以在多个对象上调用给定的方法,所以
($a, $b) =~ s/foo/bar/;
将等同于
for ($a, $b) { s/foo/bar/ }
但也许它是多余的,除非你可以说
@foo =~ s/foo/bar/
在表达式中。但总的来说,我认为我更愿意看到
@foo.grep {!m/\s/}
而不是使用=~
进行本质上是一个方法调用的操作。与之前讨论的相一致,列表版本可以是超操作符
@foo . ^s/foo/bar/;
或者可能是
@foo ^. s/foo/bar/;
请注意,在一般情况下,这都意味着在如何声明方法调用和如何声明类似引用的操作符之间存在某种交互。看起来让类似引用的声明从词法范围中逸出可能是危险的,但另一方面,也不清楚方法调用声明如何能够被词法范围化。所以,我们可能无法废除=~
作为一个明确的标记,表示左侧的是字符串,右侧的是引用结构。这意味着超替换实际上被写成
@foo ^=~ s/foo/bar/;
诚然,这并不是世界上最好看的东西。
编辑注:这个启示录已经过时,但出于历史原因仍在此保留。有关最新信息,请参阅概要 03。
非RFC考虑事项
RFCs提出了各种具体特性,但没有提供一个关于操作符整体的系统视图。在本节中,我将尝试给出一个更连贯的画面,展示我看到的事情发展的方向。
二元.
(点)
这现在是方法调用操作符,符合行业惯例。这也对我们如何声明对象属性变量有影响。我预计,在类模块中,说
my int $.counter;
将声明一个$.counter
实例变量和一个用于类内部使用的counter
访问器方法。(如果标记为公共的,它还会声明一个用于类外部的counter
访问器方法。)
一元.
(点)
可能一元.
会在类内部调用当前对象上的方法。也就是说,它将与左边的$self
(或等效)的二进制.
相同
method foowrapper ($a, $b) {
.reallyfoo($a, $b, $c)
}
另一方面,可能更符合最佳实践的是要明确
method foowrapper ($self: $a, $b) {
$self.reallyfoo($a, $b, $c)
}
(但请记住,这个声明语法还不是最终的。)
二元_
由于.
被用于方法调用,我们需要一种新的方法来连接字符串。我们将使用一个单独的下划线来实现这一点。所以,而不是
$a . $b . $c
你会说
$a _ $b _ $c
唯一的缺点是变量名和运算符之间必须有空格。这被视为一个特性。
一元 _
由于表示状态缓冲区的_
标记即将消失,一元下划线运算符将强制字符串化,就像插值一样,只是没有引号。
一元 +
类似地,在Perl 6中,一元+
将强制数值化,与Perl 5不同。如果失败,则返回NaN(不是一个数字)。
二元 :=
我们需要区分两种不同的赋值形式。尽可能与Perl 5一样工作的标准赋值运算符=
。也就是说,它试图使其看起来像值赋值。这是我们文化遗产的一部分。
但我们也需要一个像赋值一样工作但更具定义性的运算符。如果你熟悉Prolog,你可以把它想象成一种统一运算符(尽管没有隐式的回溯语义)。用人类的话来说,它将左侧视为一组形式参数,就像它们是在函数声明中一样,并将右侧的一组参数绑定起来,就像它们正在传递给函数一样。这就是新:=
运算符所做的事情。下面会详细介绍。
一元 *
一元*
是列表扁平化运算符。(参见Ruby中的先例。)当用于r值时,它关闭其余参数的函数签名匹配,例如
@args = (\@foo, @bar);
push *@args;
将等同于
push @foo, @bar;
在这方面,它充当了Perl 5中禁用原型语法&foo(@bar)
的替代品。这将被翻译为
foo(*@bar)
在l值中,一元*
表示后续数组名称吸收所有剩余的值。所以这将交换两个数组
(@a, @b) := (@b, @a);
而这也将把@c
和@d
的数组元素全部赋值给@a
。
(*@a, @b) := (@c, @d);
普通的扁平化列表赋值
@a = (@b, @c);
相当于
*@a := (@b, @c);
这不同于
@a := *(@b, @c);
这将取@b
的第一个元素作为新定义的@a
,并丢弃其余的,就像你传递了太多的参数给函数一样。它可以选择在运行时爆炸。(它不能在编译时爆炸,因为我们不知道@b
和@c
的总元素数。可能恰好有一个元素,这就是左侧想要的。)
列表上下文
在Perl 6中,列表上下文的概念有所修改。由于列表可以是惰性的,列表扁平化的解释也必然是惰性的。这意味着在没有*
列表扁平化运算符(或等效的老式列表赋值)的情况下,Perl 6中的列表是对象列表。也就是说,它们被解析为如果它们在标量上下文中是一个对象列表。当你看到一个函数调用,例如
foo @a, @b, @c;
通常应该假设有三个独立的数组被传递给该函数,除非你知道foo
的签名包括列表扁平化*
。如果子程序没有签名,出于旧习惯,它被假定为有签名(*@_)
。注意,这并不是Perl的新特性,Perl始终为内置函数区分这一点,并通过原型如\@
和\%
将其扩展到Perl 5中的用户定义函数。我们只是在Perl 6中改变了语法,使未标记的形式参数期望一个标量值,并且你可以选择声明最后一个形式参数期望一个列表。这是霍夫曼编码的问题,不要提节省对反斜杠键的磨损。
二元 :
正如我在之前的预言中指出的,计算机语言设计的首要规则是每个人都想要冒号。我认为这意味着我们应该尽可能多地给尽可能多的特性提供冒号。
因此,这个操作符以副词性修改前面的操作符。也就是说,它可以将任何操作符转换为三元操作符(前提是声明了合适的定义)。它可以用来给范围操作符提供一个“步长”,例如。它还可以用作一种超级逗号,将间接宾语与随后的参数列表分开
print $handle[2]: @args;
当然,这与旧的定义冲突了?:
操作符。见下文。
在方法类型签名中,这个操作符表示前面的参数(或参数)是要考虑方法调用的“self”。(将其放在多个参数之后可能表明希望进行多方法调度!)
三元??::
旧的?:
操作符现在写作??::
。也就是说,由于它实际上是一种短路操作符,我们只需像&&
和||
操作符一样将两个字符都加倍。这使得它对C程序员来说很容易记住。只需将
$a ? $b : $c
改为
$a ?? $b :: $c
基本问题是旧的?:
操作符浪费了两个非常有用的单字符,而这个操作符并不常用到足以证明浪费两个字符的合理性。换句话说,它是坏的Huffman编码。在RFCs中提出的每个冒号的使用都与?:
操作符冲突。我认为这说明了某些问题。
我无法在这里列出我所考虑的所有可能的?:
拼写。我只是认为??::
在这所有拼写中是最具视觉吸引力和记忆性的。
二进制//
二进制//
操作符是默认操作符。也就是说
$a // $b
是短语的
defined($a) ?? $a :: $b
不过,左边的表达式只计算一次。它将对数组和哈希以及标量有效。它还有一个相应的赋值操作符,只有当左边未定义时才执行赋值
$pi //= 3;
二进制;
二进制;
操作符在列表中分隔两个表达式,类似于C风格的for
循环中的表达式。显然,表达式需要某种括号结构来避免与语句结束的歧义。根据上下文,这些表达式可能被解释为for
循环的参数,多维数组的切片,或任何其他内容。在没有其他上下文的情况下,默认的做法是简单地创建一个列表的列表。也就是说
[1,2,3;4,5,6]
是短语的
[[1,2,3],[4,5,6]]
但是通常会有其他上下文,例如想要进行切片的多维数组或想要模拟某种控制结构的语法结构。一个模拟三参数for
循环的结构可能强制所有表达式都是闭包,以便它们可以在循环的每次迭代中计算。用户定义的语法将在第18章中讨论,如果在此之前。
一元^
一元^现在保留给超操作符。请注意,它也适用于赋值操作符
@a ^+= 1; # increment all elements of @a
一元?
保留供将来使用。
二进制?
保留供将来使用。
二进制~
这现在是位异或操作符。回想一下,一元~
(1的补码)只是与包含所有1位的值进行异或。
二进制~~
这是一个逻辑异或操作符。它是低优先级xor
操作符的高优先级版本。
用户定义的操作符
用户定义操作符的声明语法仍然在争夺之中,但我们可以谈论一些关于它的事情。首先,我们可以通过参数的数量来区分一元和二元的声明。(返回类型的声明也可能有助于区分后续解析。一个不需要的地方是,希望知道它们是否应作为超操作符行为的操作符。这种压力通过显式的^
超标记得到了缓解。)
我们还需要考虑这些运算符定义与重载之间的关系。我们可以将一个运算符视为第一个对象的方法,但有时应该是第二个对象控制动作。(或者在多方法调度的情况下,两个对象。)这些需要在普通方法调度策略下进行讨论。重要的是要认识到运算符只是一个看起来很奇怪的方法调用。当你说出
$man bites $dog
基础设施需要理清是人咬狗,还是狗被男人咬。实际的咬合动作可以实现在 Man
类或 Dog
类中,或者在多方法的情况下,在其他地方。
Unicode 运算符
与使用越来越长的 ASCII 字符串来表示用户定义的运算符相比,允许(审慎地)使用 Unicode 运算符将更容易阅读。
短期内,我们不会看到很多这样的用法。随着屏幕分辨率在未来20年内提高,我们将更习惯于更丰富的符号集。我看不出(除了害怕混淆(以及害怕害怕混淆))为什么不能允许使用 Unicode 运算符。
注意,与 APL 不同,我们将不依赖于硬件,也就是说,任何 Perl 实现都将始终能够解析 Unicode,即使你无法很好地显示它。(但请注意,Vim 6.0 刚推出了 Unicode 支持。)
优先级
我们将至少统一等价性和关系运算符的优先级。其他统一也是可能的。例如,not
逻辑运算符可以与列表运算符组合在优先级上。然而,你可以做的简化是有限的,因为你不能混合右结合和左结合。总的来说,如果你期望它基本上保持不变,优先级表将是你期望的。
这对于 Perl 6 来说仍然成立。我们在这里谈了很多我们正在改变的事情,但我们还有更多没有改变的事情。Perl 5 做了很多正确的事情,我们并不特别感兴趣于“修复”这些。
标签
反馈
这篇文章有什么问题吗?请通过在 GitHub 上打开一个问题或拉取请求来帮助我们。