1. 介绍
本章是该语言的全貌图; 如果你还不了解正在发生的一切,请不要担心。担心如果你到了本书的最后,你仍然没有了解全部!有讲很多东西,所以将围绕一些话题,重新审视其他话题,并通过一些练习看看它们是如何融合在一起的 - 这真的都是关于实践的。
1.1. 为什么是 Raku?
对于初学者,你要学习 Raku。你也可能通过使用这种语言来使得物有所值!
但是什么使这种语言变得有吸引力? Perl 家族一直喜欢 DWIM-Do What I Mean。你经常做的事情应该很容易做,而最困难的事情应该是可能的。任何编程语言的用处都可以通过它解决问题的程度来衡量。
Raku 是一种出色的文本处理语言 - 甚至可能比 Perl 5 更好。正则表达式(第15章)有许多令人兴奋的新功能,可以更容易地匹配和提取文本。内置 grammar(第17章)功能允许你轻松编写复杂的规则来处理和响应文本。
渐进类型(第3章)允许你注释变量,并限制你可以存储的内容。例如,你可以指定数字必须是整数,正数或两个其他数字之间的数。你不必必须使用它(这是渐进的部分)。你将能够注释子例程接受什么以及它应该返回什么。这可以快速揭示数据边界的错误。
内置并发(第18章)功能允许你将问题分解为单独运行并可能同时运行的部分。该语言为你处理大部分内容。
惰性列表和无限列表允许你处理序列而无需过多复制或甚至一次拥有整个列表(第6章)。你可以轻松创建自己的无限惰性列表。
我可以继续前进,但是当你按照本书的方式工作时,你会遇到更多令人惊叹的功能。
有时你不想使用 Raku。没有语言能适用所有的工作。如果你更喜欢其他更好的东西,或者可以使用不同的工具更快地完成任务,那么对你来说更强大!但是,我希望本书可以帮助你在 Raku 中快速有效地完成你需要做的事情。
1.2. First Steps with the REPL
REPL 是一个 Read-Evaluate-Print-Loop 的工具,提供交互式提示。 REPL 计算你键入的代码,显示结果,然后再次提示你。这是尝试代码片段的快速方法。当你运行不带参数的 raku 时,它会启动它的 REPL:
% raku
To exit type 'exit' or '^D'
>
>
是等待你输入内容的提示。当你键入 Return 时,REPL 开始工作。通过使两个数字相加来尝试 REPL:
% raku
> 2 + 2
4
如果出现错误,它会让你知道并再次提示你:
% raku
> 2 + Hamadryas
===SORRY!=== Error while compiling:
Undeclared name:
Hamadryas used at line 1
>
你还不知道为什么这失败,因为你刚开始读本书。这真的不重要,只要你知道 REPL 捕获错误并为你提供新提示就好了。如果你需要纠正错误,你应该能够使用向上箭头返回到上一行(或更远)来编辑和重新运行某些内容。
在继续之前,你应该了解一些其他技巧,这些技巧可以帮助你了解该语言的轮廓。
当我在本书中编写方法时,我通常会在方法调用点号前面加上它,因此你知道它们是方法,就像在 .is-prime 中一样。点号不是名称的一部分。
|
方法是用于对象的预定义行为的标签。每个对象都有一个类型,而 .^name
方法告诉你它的类型:
% raku
> 3.^name
Int
对于整数,字面量 3
是 [Int
](https://docs.raku.org/type/Int.html) 类型的对象。一旦你知道类型是什么东西,你可以阅读其文档,以找出你可以用它做什么。
行为定义在类中(第12章),这些类可以通过继承基于更通用的类。你可以使用 .^mro
方法查看继承链(尽管文档也告诉你了):
% raku
> 3.^mro
((Int) (Cool) (Any) (Mu))
对象可以执行从其继承的所有类的所有行为。这表明 3
是一个 [Int
](https://docs.raku.org/type/Int.html),然后它是一个 [Cool
](https://docs.raku.org/type/Cool.html)(方便的面向对象循环),然后它是一个 [Any
](https://docs.raku.org/type/Any.html)(几乎所有东西的基类),最后是一个 [Mu
](https://docs.raku.org/type/Mu.html)(一个不是东西的东西 - 好好想一想!)。
使用 .^methods
查看对象的方法列表:
% raku
> 3.^methods
(Int Num Rat FatRat abs Bridge chr sqrt base
polymod expmod is-prime floor ceiling round
...)
该类型也是一个对象(一个类型对象)。这是没有具体值的东西的抽象表达。它也有方法:
% raku
> Int.^methods
(Int Num Rat FatRat abs Bridge chr sqrt base
polymod expmod is-prime floor ceiling round
...)
但是,你无法在类型对象上调用许多这样的方法。你会收到错误,因为还没有值:
% raku
> Int.sqrt
Invocant of method 'sqrt' must be an object instance of
type 'Int', not a type object of type 'Int'.
方法 .^name
, .^mro
和 .^methods
来自语言的元编程基础。考虑到文章的篇幅,这对于本书有点高级,所以你不会在这里阅读更多相关内容。
1.3. 阅读文档
现在你已了解 REPL 以及如何查找对象的类型,你可能希望阅读文档中的那些内容。 p6doc 程序可以做到:
% p6doc Int
... lots of text
如果你想了解某种方法,可以将其添加到该类型中:
% p6doc Int.polymod
method polymod
Defined as:
method polymod(Int:D: +@mods)
Usage:
INTEGER.polymod(LIST)
...
有时你找不到你期望的文档。当发生这种情况时,尝试继承的类之一:
% p6doc Int.sqrt
No documentation found for method 'sqrt'
% p6doc Cool.sqrt
routine sqrt
Defined as:
sub sqrt(Numeric(Cool) $x)
method sqrt()
...
我发现自己主要在 https://docs.raku.org 阅读在线文档。比这更糟糕的是谷歌像 “raku Int” 这样的东西,并跟踪第一个结果。该网站还有一个方便的搜索功能以帮助你查找,而无需使用全文搜索。你可以在本地运行同一站点。在每个页面的底部查找这些详细信息。
1.4. 基础语法
你经常需要从内到外地阅读代码,就像阅读一个数学公式一样,所以我在这里就是这样做的:从极小的地方开始,然后从那里建立起来。这是对你需要知道的事情的调查,在接下来的章节中你会读到这些内容。如果你在这一点上有点不知所措,不要担心。随着你的练习,你会习惯这些东西的。
1.4.1. 项
程序中最低级别的是项。这些是构成其他一切的基本构成要素。将这些视为语言的名词。下面是一些项:
2
e
π
'Hello'
$x
now
这些包括字面值数据,例如 2
和 'Hello'
;变量,例如 $x
;和定义的符号,例如 π。now
是一个项,表示当前时间为 Instant
对象。
变量通常以 sigil 开头 - 一个表示该变量的特殊字符。变量 $x
有 $
sigil。先不要担心这些,尽管你会在本章后面看到更多。
1.4.2. 运算符和表达式
表达式是项和操作符的组合,产生一个新的值。如果项是名词,那么运算符就是指定动作的动词。它们将一个或多个项转化为一个新的值。操作数是操作符使用的值。一元运算符对单个操作数进行操作。
- 137 # negate 137 to make -137
+ '137' # convert the string '137' to a number
$x++ # add 1 to the current value in $x
"#" 和后面的文本是注释(一会儿你会看到更多)。它是一些被程序忽略的文本,是你对你的代码留下注释的一种方便方式。我经常使用注释来加强观点或显示表达式的输出。
二元运算符对两个操作数起作用。通常这些操作符显示在操作数之间(下标)。
2 + 2 # add two numbers
$object.method() # the . method call operator
$x = 137 # assign a value to a variable
A ternary operator, such as the conditional operator, ?? !!
, has three operands:
三元运算符,例如条件运算符,?? !!,有三个操作数:
$some_value ?? 'Yes' !! 'No' # choose one of two values
If the first thingy evaluates to True
, it selects the second thingy. Otherwise it selects the third thingy. You’ll see more of these in [Chapter 3](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch03.html#camelia-numbers).
如果第一个东西评估为True,它会选择第二个东西。否则它选择第三个东西。你将在第3章中看到更多这些内容。
BEFORE, AFTER, AND AROUND
Operators come in several varieties, with names that describe their position and the number of operands they expect. You’ll see these terms throughout the book. A prefix operator comes before its operand and usually takes only one operand. The increment operator is an example. It adds one to the number in $x
:
运营商有多种类型,其名称描述了他们的位置和他们期望的操作数。你会在整本书中看到这些术语。前缀运算符位于其操作数之前,通常只需要一个操作数。增量运算符就是一个例子。它在$ x中添加一个数字:
++$x
A postfix operator comes after its operand. There are increment forms of this type as well:
后缀运算符位于其操作数之后。还有这种类型的增量形式:
$x++
A circumfix operator surrounds its operand. Examples include the parentheses and the double quote marks:
外围操作符包围其操作数。示例包括括号和双引号:
( 1, 2, 3 )
"Hello"
A postcircumfix operator surrounds its operand but comes after something else. A single-element access to an [Array
](https://docs.raku.org/type/Array.html) or a [Hash
](https://docs.raku.org/type/Hash.html) surrounds the index and comes after the variable name. The []
and <>
are the operators that come after the name but surround the key:
postcircumfix运算符包围其操作数,但是在其他内容之后。对数组或哈希的单元素访问包围索引并位于变量名称之后。 []和<>是名称后面但围绕键的运算符:
@array[0]
%hash<key>
Those terms are in the documentation. There are other ways you can arrange operators that don’t have standard terms, so I’ve fashioned my own that I don’t expect to use that much.
A precircumfix operator surrounds an operand and comes before other operands. The reduction operator ([Chapter 6](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch06.html#camelia-lists)) surrounds an operator that it places between each of the items that follow it. This adds all the numbers without having to specify a +
between every pair:
这些术语在文档中。还有其他方法可以安排没有标准术语的操作员,所以我自己设计了我不希望使用那么多的操作员。
precircumfix运算符包围操作数,并且位于其他操作数之前。缩减操作符(第6章)围绕操作员,操作员将其放置在跟随它的每个项目之间。这会添加所有数字而不必在每对之间指定+:
[+] 1, 2, 3
A circumfix infix operator surrounds an infix operator. The hyperoperators <<>>
surround an operator and distribute that infix operator along the two lists ([Chapter 6](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch06.html#camelia-lists)):
外接中缀运算符围绕中缀运算符。超级运算符<< >>围绕一个运算符并在两个列表中分配该中缀运算符(第6章):
(1, 2, 3) <<+>> (4, 5, 6)
There are other arrangements you might encounter in this book, but you can generally tell how they work by picking apart the name.
Operators are actually methods. Their names look a bit complicated because they start with the sort of operator they are and have the symbol in angle brackets:
你可能会在本书中遇到其他安排,但你通常可以通过挑选名称来了解它们的工作原理。
运算符实际上是方法。它们的名字看起来有点复杂,因为它们以它们的运算符开头,并在尖括号中有符号:
infix:<+>(1, 2) # 3
my @array = 1, 2, 3
postcircumfix:<[ ]>( @array, 1 )
You won’t need these forms, but you should know that the operators figure out what to do based on the arguments.
你不需要这些表单,但你应该知道运算符根据参数确定要执行的操作。
优先级
You can chain operations one after the other. Try this in the REPL:
你可以一个接一个地链接操作。在REPL中尝试这个:
1 + 2 + 3 + 4
The expression is evaluated in order of operator precedence and associativity. Precedence decides which operators go first and associativity figures out the order among operators of the same precedence (or even two of the same operator).
An operator’s precedence is relatively looser or tighter than other operators. With a chain of terms the tighter operator goes first. Multiplication (*
) happens before addition (+
), just like in high school algebra:
表达式按运算符优先级和关联性的顺序进行计算。优先级决定哪些运算符优先,关联性在相同优先级的运算符(或者甚至是同一运算符中的两个)之间确定顺序。
运营商的优先级比其他运营商相对宽松或紧凑。通过一系列术语,更严格的运营商成为第一。乘法(*)在加法(+)之前发生,就像在高中代数中一样:
2 + 3 * 4 # 14
If you don’t like the order you can change it with parentheses. Things inside parentheses are computed before things outside. Another way to say that is that parentheses have the highest precedence. Now the addition happens first:
如果你不喜欢订单,可以使用括号更改订单。括号内的东西是在外面的东西之前计算出来的。另一种说法是括号具有最高优先级。现在首先添加:
(2 + 3) * 4 # 20
If you have two operators of the same precedence then associativity decides the order of evaluation. Operators can be either left associative or right associative. The exponentiation operator is right associative, so the operation on the right happens first:
如果你有两个具有相同优先级的运算符,则关联性决定评估的顺序。运算符可以是左关联的,也可以是右关联的。取幂运算符是右关联的,因此右边的操作首先发生:
2 ** 3 ** 4 # 2.4178516392293e+24
It’s the same order as if you put explicit parentheses around the right two numbers:
它与在右边两个数字周围放置明确括号的顺序相同:
2 ** (3 ** 4) # 2.4178516392293e+24
Use parentheses to make the left operation happen first:
使用括号使左操作首先发生:
(2 ** 3) ** 4 # 4096
Some operators can’t be combined and don’t have associativity. The range operator is one of the operators you can’t combine:
有些运营商无法合并,也没有相关性。范围运算符是你无法组合的运算符之一:
0 .. 5 # Range operator, nonassociative
0 .. 3 .. 5 # Illegal
1.4.3. 语句
A statement is a complete, standalone part of a program. An expression can be a statement but it can also be part of a statement. Here’s a statement using put
to output a message. It adds a newline for you:
声明是程序的完整独立部分。表达式可以是语句,但也可以是语句的一部分。这是一个使用put来输出消息的语句。它为你添加了换行符:
put 'Hello Raku!'
You separate statements with a semicolon. Here are two statements; they are on separate lines but you still need a semicolon between them:
用分号分隔语句。这是两个陈述;它们在不同的行上但你仍然需要在它们之间加一个分号:
put 'Hello Raku!';
put 'The time is ', now;
You don’t need the ;
unless another statement follows, but I tend to put a semicolon at the end of every statement because I know I’ll forget to add it when I add more code:
你不需要;除非有其他声明,但我倾向于在每个语句的末尾加上分号,因为我知道在添加更多代码时我会忘记添加它:
put 'Hello Raku!';
put 'The time is ', now;
Most whitespace is insignificant, which means you can use it how you like to format your program. These statements have a differently organized manner:
大多数空白都是无关紧要的,这意味着你可以按照自己喜欢的方式使用它来格式化程序。这些陈述的组织方式不同:
put
'Hello Raku!'
; put 'The time is ',
now ;
There are a few situations where whitespace matters, but you’ll read about that when you need to know about it.
在某些情况下,空白很重要,但是当你需要了解它时,你会读到这些内容。
1.4.4. 块儿
A block ([Chapter 5](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch05.html#camelia-blocks)) combines one or more statements into a single unit by surrounding them with a set of braces. Sometimes the block has a control keyword, such as loop
, attached to it. This block continually evaluates its statements until you stop the program with Control-C. This is an infinite loop:
一个块(第5章)通过用一组括号围绕它们将一个或多个语句组合成一个单元。有时块会附加一个控制关键字,例如循环。在使用Control-C停止程序之前,此块会持续评估其语句。这是一个无限循环:
loop {
state $count = 0;
sleep 1;
print $count, "\r";
}
Each statement is separated by a semicolon and the last statement has a semicolon for good measure.
You don’t see a ;
after the closing brace for that loop
, but it’s implicitly there. A }
followed by nothing more than whitespace until the end of the line implies a ;
. If you have more stuff on the same line, though, you need a ;
after the }
:
每个语句用分号分隔,最后一个语句用分号表示。
你没有看到;在该循环的右括号之后,但它隐含在那里。 A}后面只有空格,直到行尾意味着;。但是,如果你在同一条线上有更多的东西,你需要一个;之后 }:
loop { ... }; put "Done";
The …
(yada yada) operator is the way you signal that there’s something there but you don’t care to say what it is at the moment. Use it when you intend to fill in the details later. I’ll use those to hide code to save space in examples. It compiles but gives you an error when you run it. You’ll see this used throughout the book to shorten examples to fit on the page.
A block creates a lexical scope. You can see what this scope is based on the position of the braces (hence, lexical). Things you define inside a scope only matter inside that scope and the deeper scopes it defines. This limits the effects of many things to exactly where you need them. The effects of variables and modules are limited to their lexical scope.
……(yada yada)操作员是你发出信号的方式,但是你不想在此刻说出它是什么。如果你打算稍后填写详细信息,请使用它。我将使用这些来隐藏代码以节省示例中的空间。它会编译,但在运行时会出错。你将在本书中看到这一点,以缩短示例以适应页面。
块创建词法范围。你可以看到这个范围基于大括号的位置(因此,词汇)。你在范围内定义的内容仅在该范围内以及它定义的更深范围内。这将许多事物的影响限制在你需要它们的确切位置。变量和模块的影响仅限于它们的词法范围。
1.4.5. 注释
Comments are a way to leave ourselves notes that the program doesn’t care about. The compiler mostly ignores these things. You can make a comment with a # when the compiler is expecting a new token. The compiler skips everything from that # to the end of the line. Here’s a mostly useless comment:
评论是一种让自己留下该程序不关心的注释的方法。编译器大多忽略了这些东西。当编译器期望新的令牌时,你可以使用#进行注释。编译器会跳过从#到行尾的所有内容。这是一个无用的评论:
put 'Hello Raku!'; # output a message
A better comment expounds on the purpose, not the effect, of the code. This type of little program is often used as a first exercise to check that everything is working. The comment can say that:
更好的评论阐述了代码的目的,而不是效果。这种类型的小程序通常用作检查一切正常的第一个练习。评论可以说:
put 'Hello Raku!'; # show that the program ran
An alternative is an embedded comment. Put your message inside the parentheses in #
( )` somewhere in your statement (or even between statements):
另一种选择是嵌入式评论。将你的消息放在语句中某处(甚至语句之间)的#()`中的括号内:
put #`(Marketing asked for this) 'Hello Raku!';
This is a nice way to have multiline comments:
这是获得多行注释的好方法:
#`(
* show that the program ran
* need to add blockchain email AI feature
)
put 'Hello Raku!';
Since a closing parenthesis ends the comment, you can’t have one in your comment.
Both of those are fine for short comments. Sometimes you want to comment out several lines to prevent them from running. If you put the #
at the beginning of a line you effectively remove that line from the program:
由于右括号结束了注释,因此你的注释中不能有注释。
这两个都适合简短的评论。有时你想要注释掉几行以防止它们运行。如果你把#放在一行的开头,你就可以从程序中删除该行:
loop {
state $count = 0;
# sleep 1;
print $count, "\r";
}
You might add another comment to remind yourself why that line is still in the code. Often programmers do this as they are debugging so they remember what was there before they started:
你可以添加另一条注释来提醒自己为什么该行仍在代码中。程序员通常会在调试时执行此操作,以便他们记住启动之前的内容:
loop {
state $count = 0;
# Testing this for ticket 1234 (bug://1234)
# I think that the sleep slows the program down too much
# sleep 1;
print $count, "\r";
}
1.4.6. Unspace
In most places Raku doesn’t care about whitespace, but there are some parts of the Raku syntax that don’t allow spaces. Space between the name of a subroutine and its opening parenthesis for an argument list changes the meaning:
在大多数地方,Raku并不关心空格,但Raku语法的某些部分不允许使用空格。子例程名称与参数列表的左括号之间的空格改变了含义:
my-sub 1, 2, 3; # three arguments
my-sub( 1, 2, 3 ); # three arguments
my-sub ( 1, 2, 3 ); # one argument (a List)
In that last line there’s a space between my-sub
and the (
. That compiles and runs, but instead of three arguments the subroutine gets a single [List
](https://docs.raku.org/type/List.html) argument ([Chapter 6](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch06.html#camelia-lists)). You can unspace that space with a backslash. Any whitespace following the \
is basically invisible to the compiler:
在最后一行中,my-sub和(。)之间有一个空格编译并运行,但是子程序不是三个参数,而是获取单个List参数(第6章)。你可以用反斜杠取消空格。任何空格跟随\对编译器基本上是不可见的:
my-sub\ (1, 2, 3 );
You might want to do this to format code into columns to make it easier to read:
你可能希望这样做以将代码格式化为列以使其更易于阅读:
my-sub\ ( 2, 4, 8 );
my-much-longer-name( 1, 3, 7 );
1.4.7. 对象和类
Raku is a class-based object system. I’ll skip most of the theory of object-oriented programming (that could be a whole other book), but you should know that in these systems a class ([Chapter 12](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch12.html#camelia-classes)) defines the abstract structure and behavior of an object. The object is a particular concrete version of that class.
Most of the data in Raku are objects, and each object knows what class defines it. Classes define methods, which are the behaviors of the object. Classes can inherit from another class to include its behavior, but they can also include roles that add behavior without inheritance. When you see class names in the digital version of this book the name should link to the online documentation for that class (for example, the [Int
](https://docs.raku.org/type/Int.html) class).
You create objects by calling a constructor method, often called .new
([Chapter 12](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch12.html#camelia-classes)). You pass arguments to the method in parentheses after the method name:
Raku是一个基于类的对象系统。我将跳过面向对象编程的大部分理论(可能是另一本书),但你应该知道在这些系统中,一个类(第12章)定义了一个对象的抽象结构和行为。该对象是该类的特定具体版本。
Raku中的大多数数据都是对象,每个对象都知道哪个类定义了它。类定义方法,这是对象的行为。类可以从另一个类继承以包含其行为,但它们也可以包含添加行为而不继承的角色。当你在本书的数字版本中看到类名时,该名称应该链接到该类的在线文档(例如,Int类)。
你可以通过调用构造函数方法来创建对象,通常称为.new(第12章)。在方法名称后面的括号中将参数传递给方法:
my $fraction = Rat.new( 5, 4 );
There’s also a colon syntax for method arguments which relieves you from the burden of typing the closing parenthesis as long as there’s nothing more in the statement:
方法参数还有一个冒号语法,只要语句中没有其他内容,就可以减轻输入右括号的负担:
my $fraction = Rat.new: 5, 4;
Type objects represent the abstract idea of a class but aren’t objects. Sometimes they are useful as placeholders when you know what sort of object you want but you don’t know its value yet:
*类型对象*表示类的抽象概念,但不是对象。当你知道你想要什么样的对象但你还不知道它的价值时,有时候它们可以用作占位符:
my $fraction = Rat;
With gradual typing you can restrict variables to fit into a type. These are runtime checks, so you don’t know that it didn’t work until you try it:
通过逐步输入,你可以限制变量以适合类型。这些是运行时检查,所以在尝试之前你不知道它不起作用:
my Int $n;
Since you haven’t assigned a value to $n
yet, it’s an [Int
](https://docs.raku.org/type/Int.html) type object. When you want to assign a value it must match that type:
由于尚未为$ n分配值,因此它是Int类型对象。如果要分配值,则必须与该类型匹配:
$n = 137; # works because it's an integer
$n = 'Hamadryas'; # fails
Look through a class’s documentation to see what sorts of things its objects can do. In many of the exercises I’ll ask you to use a method that I haven’t shown you. This trains you to go to the docs, but also lets you learn things about seeing what’s out there. This saves some space in the book. Let’s try some of those now.
EXERCISE 1.1What type of object is 137
? Compute its square root. Is it a prime number? You should be able to do each of these with a simple method.
查看类的文档,了解其对象可以执行的操作。在许多练习中,我会要求你使用我没有给你看的方法。这会训练你去看文档,但也可以让你学习看看那里有什么。这节省了书中的一些空间。我们现在试试吧。
练习1.1什么类型的对象是137?计算其平方根。它是素数吗?你应该能够使用一种简单的方法完成其中的每一项。
1.5. Variables
Raku has named values. They can be immutable, which means you can’t change the values once you set them. They can also be mutable, which means you can change the values. The mutable ones are commonly called variables, but they are also known as containers. A container holds a value and you can replace that value with another. You’ll read more about that in the next chapter. Despite the possibility that you can’t change the value, I’ll still call all of these “variables.”
A named value has an identifier—a fancy word for “name.” Names can include letters, digits, the underscore, the hyphen, and the apostrophe ('
). You must start your name with a letter or digit. These are valid identifiers:
Raku具有命名值。它们可以是不可变的,这意味着一旦设置它们就无法更改它们。它们也可以是可变的,这意味着你可以更改值。可变的通常称为变量,但它们也称为容器。容器包含值,你可以将该值替换为另一个值。你将在下一章中阅读更多相关内容。尽管你无法更改该值,但我仍然会调用所有这些“变量”。
命名值具有标识符 - “名称”的奇特单词。名称可以包括字母,数字,下划线,连字符和撇号(')。你必须以字母或数字开头。这些是有效的标识符:
butterfly_name
butterfly-name
butterfly'name
The underscore, hyphen, or apostrophe can separate words to make them easier to read. Sometimes the underscore pattern is called snake case since the word separators crawl along the ground. The hyphen pattern is called kebab case (or sometimes lisp case).
Some people might feel more at home capitalizing the first letter of each word instead. This is known as camel case since it imitates the humps on a camel’s back. In this example there’s one hump, which is the best number of humps for a camel:
下划线,连字符或撇号可以分隔单词以使其更易于阅读。有时下划线模式称为蛇形,因为单词分隔符沿着地面爬行。连字符模式称为kebab案例(或有时称为lisp案例)。
有些人可能会觉得在家里更多地将每个单词的第一个字母大写。这被称为骆驼案,因为它模仿骆驼背上的驼峰。在这个例子中有一个驼峰,这是驼峰的最佳驼峰数:
butterflyName
There are some rules with -
and '
. You can’t have two -
or '
characters in a row, and the character after either must be a letter (not a number). Also, you cannot start an identifier with these characters. None of these are valid:
有一些规则 - 和'。你不能连续两个 - 或’字符,并且后面的字符必须是字母(不是数字)。此外,你无法使用这些字符启动标识符。这些都不是有效的:
butterfly--name
butterfly''name
'butterfly
butterfly-1
A variable name combines a sigil with an identifier. The sigil is a character that gives some context to the identifier. A scalar is a single thingy. A scalar variable holds a single value and has a $
sigil. The $
looks similar to S
, for scalar:
变量名称将sigil与标识符组合在一起。 sigil是一个为标识符提供一些上下文的字符。标量是一个单一的东西。标量变量包含单个值并具有$ sigil。对于标量,$看起来类似于S:
$butterfly_name
As you encounter the different types you’ll encounter the other sigils. The @
is for positionals (Chapter 6), the %
is for associatives (Chapter 9), and the &
is for callables (Chapter 11).
The first time you use a variable you must declare it. You do this so the compiler knows you definitely want to use that name and to avoid problems with misspelling variables. The my
keyword declares the variable to be private to the current scope:
当你遇到不同的类型时,你会遇到其他的印记。 @代表位置(第6章),%代表关联(第9章),&是适用于callables(第11章)。
第一次使用变量时,必须声明它。你这样做是因为编译器知道你肯定想要使用该名称并避免拼写错误变量的问题。 my关键字将变量声明为当前范围的私有变量:
my $number;
The next time you use $number
in that same scope you don’t need to declare it. You probably want to assign it a value. The =
is the assignment operator:
下次在同一范围内使用$ number时,你不需要声明它。你可能想要为其分配值。 =是赋值运算符:
$number = 137;
You initialize a variable first time you assign a value to it. You can do this at the same time that you declare it:
首次为其赋值时初始化变量。你可以在声明它的同时执行此操作:
my $number = 137;
Since Raku already knows which variables you intend to use it knows when you misspell one:
由于Raku已经知道你打算使用哪些变量,因此它知道你拼错了一个:
$numbear = 137;
You get an error that’s often aware enough to guess what you meant:
你会得到一个经常意识到足以猜出你的意思的错误:
Variable '$numbear' is not declared. Did you mean '$number'?
1.5.1. 简单输出
To see what’s in a variable you can use (or “call”) the put
routine. This outputs the value to standard output and adds a newline to the end:
要查看变量中的内容,你可以使用(或“调用”)put例程。这会将值输出到标准输出并在结尾添加换行符:
put $number;
If you use say
it calls the .gist
method for you. This often results in the same output, but some complicated objects may summarize or elide data to give you something easier to read. These two do the same thing:
如果你使用say它会为你调用.gist方法。这通常会产生相同的输出,但是一些复杂的对象可能会总结或删除数据,以便你更容易阅读。这两个做同样的事情:
say $number;
put $number.gist;
If you don’t want to add a newline you can use print
:
如果你不想添加换行符,可以使用print:
print $number;
There are also method forms of each of these:
还有以下各种方法形式:
$number.put;
$number.say;
$number.print;
1.5.2. 词法作用域
A variable is only visible in its lexical scope. If you define a variable inside braces you can’t use it outside the braces:
变量仅在其词法范围内可见。如果在大括号内定义变量,则不能在大括号外使用它:
{
my $number = 137;
}
$number = 5; # a compilation error
This is caught when you try to compile the program:
当你尝试编译程序时会捕获此信息:
Variable '$number' is not declared
A variable of the same name can exist in the outer scope and isn’t disturbed when the same name is reused in a deeper scope:
外部作用域中可以存在同名变量,并且在更深的作用域中重用相同名称时不会受到干扰:
my $number = 5;
put $number;
{
my $number = 137;
put $number;
}
put $number;
These are two different variables that happen to use the same name. The compiler can tell them apart based on where you declared them. The inner scope declaration “hides” the outer scope one, so the result is:
这是两个碰巧使用相同名称的不同变量。编译器可以根据你声明它们的位置区分它们。内部范围声明“隐藏”外部范围一,因此结果是:
5
137
5
Sometimes a named value doesn’t have a sigil. These sigilless variables don’t create containers, which means that you can’t change their values. This makes them handy for values you don’t want anyone to accidentally change. Prefix the identifier with a \
:
有时命名值没有印记。这些无形变量不会创建容器,这意味着你无法更改其值。这使得它们非常便于你不希望任何人意外更改的值。使用\前缀标识符:
my \magic-number = 42;
magic-number.put;
These statements actually create terms, but since you declare them like variables it’s slightly easier to be a little wrong than pedantically correct.
这些语句实际上创建了术语,但是因为你将它们声明为变量,所以稍微容易出错而不是迂腐正确。
1.5.3. 预定义变量
Raku defines several variables for you. These are prefixed with a sigil and then an additional character called a twigil. The combination of characters tells you something about the variable. Don’t worry about all the sorts of twigils that exist. Know that they do exist and that you can read about them at https://docs.raku.org/language/variabless。
Raku为你定义了几个变量。这些是以印记为前缀,然后是一个名为twigil的附加角色。字符组合会告诉你有关变量的信息。不要担心存在的所有种类的树枝。知道它们确实存在,你可以在https://docs.raku.org/language/variables或p6doc上阅读它们:
% p6doc language/variables
The ?
twigil marks values that the compiler sets as it does its work. These are compile-time variables. If you want to know the file the compiler is working on you can look in $?FILE
. The $
is the sigil and the ?
is the twigil:
的? twigil标记编译器在工作时设置的值。这些是编译时变量。如果你想知道编译器正在处理的文件,你可以查看$?FILE。 $是sigil和?是twigil:
put $?FILE;
The *
twigil marks dynamic variables. These are looked up through the caller’s scope, but that’s not the important part for this section. Your program automatically sets these values. Some of them are about the environment of the program:
twigil标志着动态变量。这些是通过调用者的范围查找的,但这不是本节的重要部分。你的程序会自动设置这些值。其中一些是关于该计划的环境:
% raku
To exit type 'exit' or '^D'
> $*EXECUTABLE
"/Applications/Rakudo/bin/raku".IO
> $*PROGRAM
"interactive".IO
> $*USER
hamadryas
> $*CWD
"/Users/hamadryas".IO
Others provide information about your version of Raku. This information might be useful if you need to report an issue:
其他人提供有关你的Raku版本的信息。如果你需要报告问题,此信息可能很有用:
> $*PERL
Raku (6.c)
> $*VM
moar (2018.04)
There are other dynamic variables for the standard filehandles. Each program gets output, input, and error filehandles. The standard output (the default place where output goes) is in $*OUT
and standard error is in $*ERR
. These are IO::Handle
objects and you can call .put
on them to make output:
- 标准文件句柄还有其他动态变量。每个程序都获得输出,输入和错误文件句柄。标准输出(输出的默认位置)在$ * OUT中,标准错误在$ * ERR中。这些是IO
-
Handle对象,你可以在它们上调用.put来进行输出:
$*OUT.put: 'Hello Hamadryas!';
$*ERR.put: 'Hello Hamadryas!';
EXERCISE 1.2What is the $*CWD
variable? What’s its value on your system?
EXERCISE 1.2 $ * CWD变量是什么?它对你的系统有什么价值?
1.6. Making and Running a Program
It’s time you wrote a program. That’s just a plain-text file that contains your source code. You don’t need any special software to create these files. They must be plain text though; word processors insert extra stuff and the compiler won’t tolerate that.
The first line in the program is typically the shebang line. That’s a Unix thing that lets a text file pretend to be a program. When you “run” the text file the system sees that the first two characters are #!
. It uses the rest of that line as the name of the program that will actually run the code. That’s the interpreter:
是时候写一个程序了。这只是一个包含源代码的纯文本文件。你不需要任何特殊软件来创建这些文件。但它们必须是纯文本;字处理器插入额外的东西,编译器不会容忍。
该计划的第一行通常是shebang线。这是一个让文本文件伪装成程序的Unix东西。当你“运行”文本文件时,系统会看到前两个字符是#!。它使用该行的其余部分作为实际运行代码的程序的名称。那是翻译:
#!/Applications/Rakudo/bin/raku
Your package (or custom installation) may have installed it somewhere else, in which case you’d use that path:
你的包(或自定义安装)可能已将其安装在其他位置,在这种情况下你将使用该路径:
#!/usr/local/bin/raku
Some people use env since that looks through your PATH
to find the program:
有些人使用env,因为它通过你的PATH查找程序:
#!/bin/env raku
Windows doesn’t know about shebangs, but it’s a good idea to include the shebang anyway since useful programs tend to escape into the world (life will find a way). For the rest of the book I’ll leave off the shebang line just to save space.
The rest of your file is your program. Here’s a common one that tests that you’ve probably done everything right. If you can run this program you’ve probably installed everything correctly:
Windows不知道shebangs,但是最好包括shebang,因为有用的程序往往会逃到这个世界(生活会找到一种方式)。为了本书的其余部分,为了节省空间,我将离开shebang线。
你文件的其余部分是你的程序。这是一个常见的测试,你可能已经做好了一切。如果你可以运行这个程序,你可能已正确安装了一切:
put 'Hello World!';
Ensure your editor is set to encode your file as UTF-8. Save the file using any name that you like. raku doesn’t care about the name, although the docs suggest a .p6 or .pl6 extension.
Run your program from the command line:
确保你的编辑器设置为将文件编码为UTF-8。使用你喜欢的任何名称保存文件。 raku并不关心名称,尽管文档建议使用.p6或.pl6扩展名。
从命令行运行你的程序:
% raku hello-world.p6
When you do this raku first compiles the program. It sees all of your program text and parses it. That’s the compile time part of the process. If it finds no problem it then runs what it has already compiled.
If you want to check your program without running it you can use the -c
switch. This is a syntax check:
当你这样做时raku首先编译程序。它会查看所有程序文本并对其进行解析。这是该过程的编译时间部分。如果它没有发现任何问题,则运行它已编译的内容。
如果要在不运行程序的情况下检查程序,可以使用-c开关。这是语法检查:
% raku -c hello-world.p6
Most errors at this point are syntax errors; you wrote a program that Raku couldn’t parse.
EXERCISE 1.3Create the “Hello World” program and get it to run. Use any tools you like for that.
此时的大多数错误都是语法错误;你写了一个Raku无法解析的程序。
练习1.3创建“Hello World”程序并让它运行。使用你喜欢的任何工具。
1.7. 总结
You’ve seen the basic structure of a program and how you build up a program from smaller elements. You wrote some very small programs. You have some insights into the documentation; you’ll get more practice with that throughout your programming career. Now the trick is to make slightly larger programs.
你已经了解了程序的基本结构以及如何从较小的元素构建程序。你写了一些很小的程序。你对文档有一些了解;在整个编程生涯中,你将获得更多练习。现在的诀窍是制作稍大的程序。 == 猜数字
ou’re about to be thrown in the deep end. There are some basic things you need to know to write useful programs, and you’ll meet a lot of them in this chapter so you can write a number-guessing program by the end. It’s quite a bit to take in all at once but it should make the rest of the chapters more interesting.
你将要陷入深渊。编写有用的程序需要了解一些基本的东西,本章中你会遇到很多基本的东西,所以你可以在最后编写一个数字猜测程序。一下子就可以完全接受它,但它应该让其他章节更有趣。
1.8. 绑定和赋值
You read a little about variables in [Chapter 1](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch01.html#camelia-introduction). To store a value in a variable you assign to it. The item assignment operator, =
, stores a single thingy for you. $number
is a scalar variable; it can store exactly one thingy. This is item assignment because there’s one thingy. This “sets” the value:
你将在第1章中阅读一些关于变量的内容。将值存储在你为其分配的变量中。项目赋值运算符=为你存储单个东西。 $ number是一个标量变量;它可以存储一个东西。这是项目分配,因为有一个东西。这“设置”了价值:
my $number = 2;
If you decide that you don’t want that value you can replace it:
如果你认为不需要该值,则可以替换它:
$number = 3;
Sometimes you want a value that you can’t change (more likely a value you don’t want another part of your program to change). Instead of the assignment operator you can use the binding operator, :=
, to set the value:
有时你想要一个你无法改变的值(更可能是你不希望程序的另一部分改变的值)。你可以使用绑定运算符:=来设置值,而不是赋值运算符:
my $sides-of-a-square := 4;
$sides-of-a-square = 5
When you try to change the value you get an error:
当你尝试更改该值时,你会收到错误:
Cannot assign to an immutable value
It’s not the binding operator that makes the variable immutable. It merely makes the thingy on the left the same as the one on the right. In this case, $sides-of-square
is actually 4
and not just a variable that happens to store 4
. You can’t assign to 4
, so you can’t assign to $sides-of-a-square
.
If you first assign to a scalar variable then bind to that variable you end up with two names for the same variable:
它不是使变量成为不可变的绑定运算符。它只是让左边的东西和右边的东西相同。在这种情况下,$ sides-of-square实际上是4而不仅仅是恰好存储4的变量。你不能分配给4,所以你不能分配到$ side-of-a-square。
如果你首先分配给标量变量然后绑定到该变量,则最终会为同一个变量使用两个名称:
my $number = 3;
my $sides := $number;
You can change $sides
or $number
, and the “other” will change. But there is no “other” to change because they are the same thing! You might think of these as aliases, but it’s a bit more complicated.
There’s an important concept here that you should learn early. A variable assignment with =
creates a container, then puts a value in that container. A container is just a box that can store a value. You can add, remove, and replace the value in that box. This is mostly invisible to you because the language handles it for you.
The binding operator skips this containerization. It aliases the thingy on the right side directly. If it’s already a container that’s what you bind to. You can break down the action of assignment into two steps. First you bind to an anonymous container. That’s right: a container can exist without a name. An anonymous container is just the $
sigil:
你可以更改$ sides或$ number,“other”将更改。但是没有“其他”可以改变,因为它们是同一个东西!你可能会将这些视为别名,但它有点复杂。
这里有一个重要的概念,你应该尽早学习。带=的变量赋值创建一个容器,然后在该容器中放入一个值。容器只是一个可以存储值的盒子。你可以添加,删除和替换该框中的值。这对你来说几乎是不可见的,因为语言会为你处理它。
绑定操作员跳过此容器化。它直接在右侧别名。如果它已经是一个与你绑定的容器。你可以将分配操作分解为两个步骤。首先绑定到匿名容器。没错:容器可以没有名字而存在。一个匿名的容器只是$ sigil:
my $number := $;
After that you can change the value in the container using =
:
之后,你可以使用=更改容器中的值:
$number = 3;
Sometimes you’ll need to know if the thingy you have is a container, and there will be times you’ll want to skip the container. Start thinking about this early, before you develop bad habits, and your programming life will be easier.
有时你需要知道你拥有的东西是否是一个容器,有时候你会想要跳过容器。在你养成坏习惯之前就开始考虑这个问题,你的编程生活会更容易。
1.9. A MAIN Program
In [Chapter 1](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch01.html#camelia-introduction) you saw some examples of statements. This is a complete program:
在第1章中,你看到了一些语句示例。这是一个完整的程序:
put 'Hello Raku!';
If you’ve programmed in some other languages you may have encountered a subroutine called main
or something similar. Those languages probably required you to put your program inside that routine; when you ran your program it automatically ran that subroutine for you. Raku is a little different because it assumes that your entire file is already that main
.
You can still have such a subroutine though. If you define a MAIN
subroutine (all caps!) your program will call that automatically if you run the program:
如果你使用其他语言进行编程,则可能遇到了一个名为main或类似的子程序。那些语言可能要求你把你的程序放在那个例程中;当你运行程序时,它会自动为你运行该子程序。 Raku有点不同,因为它假设你的整个文件已经是主要文件。
你仍然可以拥有这样的子程序。如果你定义一个MAIN子程序(所有大写!),你的程序将在你运行程序时自动调用它:
sub MAIN {
put 'Hello Raku!'
}
You won’t read about subroutines until [Chapter 11](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch11.html#camelia-subroutines), so trust me for a bit on this one. You’ll read more of an explanation of MAIN
as you go through the book.
EXERCISE 2.1Create both versions of the “Hello Raku” program. The one-line version and MAIN
version should give you the same output.
在第11章之前,你不会阅读关于子程序的内容,所以请相信我一点。在阅读本书时,你将阅读更多关于MAIN的解释。
EXERCISE 2.1创建“Hello Raku”程序的两个版本。单行版本和MAIN版本应该为你提供相同的输出。
1.9.1. Program Arguments
You probably have seen other command-line programs that take arguments. The filenames you give to more or type are arguments that tell those programs which file’s contents you want to see:
你可能已经看过其他带参数的命令行程序。你提供给更多或类型的文件名是告诉这些程序你想要查看哪些文件内容的参数:
% more hello-world.p6
C:\ type hello-world.p6
Your Raku program can take arguments too. When you try it with your existing program you get a help message instead of the output that you expected:
你的Raku程序也可以参数。当你使用现有程序尝试它时,你会得到一条帮助消息,而不是你期望的输出:
% raku hello-world-main.p6 1 2 3
Usage:
hello-world-main.p6
To accept arguments you have to tell MAIN
to expect them. Your program had an implicit set of empty parentheses in it. Those parentheses define the parameters, which are the templates for the arguments. Arguments are what you get; parameters are what you wanted. In this case you didn’t specify any parameters, so your program expects no arguments and complains if you try to give it some:
要接受参数,你必须告诉MAIN期望它们。你的程序中有一组隐含的空括号。这些括号定义参数,这些参数是参数的模板。争论就是你得到的;参数是你想要的。在这种情况下,你没有指定任何参数,因此如果你尝试给它一些,你的程序不需要参数和抱怨:
sub MAIN () {
put 'Hello Raku!'
}
You can change this. You can specify a variable in the parameter list. One parameter allows your MAIN
subroutine to take exactly one argument. Change your put
statement to output the value in $thingy
by defining a signature after the subroutine name:
你可以改变这个。你可以在参数列表中指定变量。一个参数允许MAIN子例程只取一个参数。通过在子例程名称后定义签名,更改put语句以输出$ thingy中的值:
sub MAIN ( $thingy ) {
put $thingy;
}
When you run this program with no command-line arguments you get a different help message. You needed one argument and gave it none. Curiously, the help message tells you the name of the variable you used in the parameter:
如果在没有命令行参数的情况下运行此程序,则会收到不同的帮助消息。你需要一个论点并且没有给它。奇怪的是,帮助消息告诉你在参数中使用的变量的名称:
% raku main-one-thingy.p6
Usage:
main-one-thingy.p6 <thingy>
% raku main-one-thingy.p6 Hello
Hello
Quote the entire value or escape the whitespace (Unix shells only) to preserve whitespace inside a value you want to give to the thingy:
引用整个值或转义空格(仅限Unix shell)以保留要为thingy赋值的内部空格:
% raku main-one-thingy.p6 "Hello Raku"
Hello Raku
% raku main-one-thingy.p6 Hello\ Perl\ 6
Hello Raku
You can specify more than one parameter by separating them with commas. You can also output multiple things in a single put
by separating them with commas:
你可以通过用逗号分隔多个参数来指定它们。你也可以通过用逗号分隔它们来输出单个put中的多个内容:
sub MAIN ( $thingy1, $thingy2 ) {
put '1: ', $thingy1;
put '2: ', $thingy1;
}
Now you have to give your program two arguments. If you don’t give it exactly two arguments it doesn’t work:
现在你必须给你的程序两个参数。如果你不准确地给它两个参数它不起作用:
% raku main-two-thingys.p6 Hamadryas
Usage:
main-two-thingys.p6 <thingy1> <thingy2>
% raku main-two-thingys.p6 Hamadryas perlicus
1: Hamadryas
2: perlicus
Hamadryas perlicus is the (un)scientific name I’ve given to the butterfly on the cover. Sometimes I call him “Hama” for short because it rhymes with “llama.” |
Sometimes you don’t want to specify two arguments even though you need two values. You can specify a default value for some parameters. Use the =
to specify the default:
Hamadryas perlicus *是我在封面上给蝴蝶的(非)学名。有时我称他为“哈马”,因为它与“美洲驼”押韵。
有时你不希望指定两个参数,即使你需要两个值。你可以为某些参数指定默认值。使用=指定默认值:
sub MAIN ( $thingy1, $thingy2 = 'perlicus' ) {
put '1: ', $thingy1;
put '2: ', $thingy2;
}
When you call it with two arguments it works as before, but when you specify exactly one argument it uses the default for the second:
当你用两个参数调用它时,它像以前一样工作,但是当你指定一个参数时,它使用第二个参数的默认值:
% raku main-two-thingys-default.p6 Hamadryas februa
1: Hamadryas
2: februa
% raku main-two-thingys-default.p6 Hamadryas
1: Hamadryas
2: perlicus
Any parameters with defaults have to show up after those without them. You’ll see much more about parameters in [Chapter 11](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch11.html#camelia-subroutines).
EXERCISE 2.2Create a program that takes three command-line arguments and outputs them on separate, numbered lines. Give two of the parameters default values.
任何带有默认值的参数都必须显示在没有它们的参数之后。你将在第11章中看到有关参数的更多信息。
练习2.2创建一个带有三个命令行参数的程序,并将它们输出到不同的编号行上。给出两个参数默认值。
1.9.2. Prompting for Values
The prompt
routine outputs a message asking for input. When you type some text followed by Return `prompt`reads that text and returns it. You can assign that value to a variable:
提示例程输出要求输入的消息。当你键入一些文本,然后返回提示时,将读取该文本并将其返回。你可以将该值分配给变量:
my $answer = prompt 'What is your favorite number? ';
put 'Your answer was [', $answer, ']';
When you run the program you see the prompt and start typing right after it on the same line:
运行程序时,你会看到提示并在同一行后面开始输入:
% raku prompt.p6
What is your favorite number? 137
Your answer was [137]
The value you get back from prompt
does not include the line ending from Return.
EXERCISE 2.3Write a program that asks for your name and then outputs a greeting to that name. If your name is Gilligan it should output “Hello Gilligan.” Can you use a MAIN
subroutine and only prompt if there’s no command-line argument?
从提示中返回的值不包括以Return结尾的行。
练习2.3写一个程序,询问你的名字,然后输出一个问候语到该名称。如果你的名字是Gilligan,它应该输出“Hello Gilligan。”你能使用MAIN子程序,只有在没有命令行参数的情况下才会提示吗?
1.10. Literal Numbers
Literal values are those that you type directly into the program. They are fixed and are sometimes called “hardcoded” values because they exist directly in the program instead of coming from input or configuration. These are terms, and you can write them in several ways.
An integer is a whole number. These are the numbers of everyday life expressed with the digits from 0 to 9:
文字值是你直接在程序中键入的值。它们是固定的,有时称为“硬编码”值,因为它们直接存在于程序中,而不是来自输入或配置。这些是术语,你可以通过多种方式编写它们。
整数是整数。这些是用0到9的数字表示的日常生活数量:
137
4
-19
0
Digital computers are more comfortable with powers of two. Prefix a literal number with 0x
to specify a hexadecimal number. That’s base 16 and uses the digits 0 to 9 and the letters A to F (in either case) to represent 0 to 15:
数字计算机更适合两种能力。使用0x前缀一个文字数字以指定十六进制数字。这是基数16并使用数字0到9和字母A到F(在任何一种情况下)代表0到15:
0x89
0xBEEF
-0x20
Octal numbers are base 8 and use the digits 0 to 7. Prefix a literal octal number with 0o
:
八进制数是基数8并使用数字0到7.用0o前缀一个文字八进制数:
0o211
-0o177
Binary numbers are base 2 and use the digits 0 and 1. These are handy when you deal with binary formats. Prefix them with 0b
:
二进制数字是基数2并使用数字0和1.当你处理二进制格式时,这些都很方便。用0b作为前缀:
0b10001001
Choose a representation that’s easy for you to understand or that’s natural for the task. The compiler converts those representations into values that the physical computer can use. It doesn’t care which one you use; they are just numbers. These are all the same value:
选择一个易于理解或对任务而言很自然的表示。编译器将这些表示转换为物理计算机可以使用的值。它并不关心你使用哪一个;他们只是数字。这些都是相同的价值:
137 # decimal, base 10
0b10001001 # binary, base 2
0o211 # octal, base 8
0x89 # hexadecimal, base 16
EXERCISE 2.4In the REPL try the different base examples. What decimal value does the REPL echo?
Perhaps you don’t like the ASCII digits 0 to 9. You can use any digits that Unicode supports; Raku knows about anything that’s a number character. Eastern Arabic numerals work. Notice that the radix prefixes are the same:
EXERCISE 2.4在REPL中尝试不同的基础示例。 REPL回显的十进制值是多少?
也许你不喜欢ASCII数字0到9.你可以使用Unicode支持的任何数字; Raku知道任何数字角色。东部阿拉伯数字工作。请注意,基数前缀是相同的:
١٣٧
0b١٠٠٠١٠٠١
0o٢١١
0x٨٩
So do Bengali digits:
孟加拉语数字也是如此:
১৩৭
0b১০০০১০০১
0o২১১
0x৮৯
I don’t encourage you to represent numbers like this in your program, but Raku understands them. This is useful when you are processing text that contains them. Your program will be able to convert these to a number type.
You can choose other bases up to base 36. You’ve already seen base 16, which uses 0 to 9 and A to F. Base 17 would add G, and so on up to base 36, which includes Z. Use a colon before the base (in decimal), then put the digits inside angle brackets:
我不鼓励你在你的程序中代表这样的数字,但Raku理解它们。当你处理包含它们的文本时,这非常有用。你的程序将能够将这些转换为数字类型。
你可以选择基数为36的其他基数。你已经看过基数为16,使用0到9和A到F.基数17将添加G,依此类推到基数36,其中包括Z.使用冒号之前基数(十进制),然后将数字放在尖括号内:
:7(254)
:19<IG88>
:26<HAL9000>
:36<THX1138>
EXERCISE 2.5Try the unusual base examples in the REPL. What decimal numbers are they?
练习2.5尝试REPL中不寻常的基础示例。它们的十进制数是多少?
1.10.1. Formatting Numbers
Literal numbers are objects. You can call methods on objects. The .base
method allows you to specify the base that you want to represent:
文字数字是对象。你可以在对象上调用方法。 .base方法允许你指定要表示的基数:
put 0x89.base: 10; # 137
You can choose some other base, up to 36:
你可以选择其他一些基地,最多36个:
put 0x89.base: 2; # 10001001
put 0x89.base: 8; # 211
put 0x89.base: 16; # 89
EXERCISE 2.6Write a program that takes a decimal number as its single command-line argument. Output its binary, octal, decimal, and hexadecimal values. What happens if you give it a hexadecimal number on the command line? What if you specify the decimal number in Eastern Arabic digits?
In the previous exercise you couldn’t specify a hexadecimal number as an argument. That’s because you weren’t actually specifying a number as an argument. It was text made up of digit characters. If you want to use a hexadecimal number you have to tell your program how to convert the number. You can use .parse-base
for that. You tell it which base you expect and it does the rest:
练习2.6编写一个以十进制数作为单个命令行参数的程序。输出二进制,八进制,十进制和十六进制值。如果在命令行上给它一个十六进制数,会发生什么?如果你在东部阿拉伯数字中指定十进制数怎么办?
在上一个练习中,你无法将十六进制数指定为参数。那是因为你实际上没有指定一个数字作为参数。它是由数字字符组成的文本。如果要使用十六进制数,则必须告诉程序如何转换数字。你可以使用.parse-base。你告诉它你期望的基础,剩下的就是:
my $number = $thingy.parse-base: 16;
EXERCISE 2.7Modify your answer from the previous exercise to accept a hexadecimal number command-line argument. Your program will now only handle hexadecimal numbers if you’re using only what you’ve seen so far.
练习2.7修改上一练习中的答案以接受十六进制数字命令行参数。如果你只使用到目前为止看到的内容,你的程序现在只能处理十六进制数字。
1.11. Numeric Operations
Numeric operators transform numbers into new values. The simplest demonstration is to immediately output the result. The +
is the addition operator:
数字运算符将数字转换为新值。最简单的演示是立即输出结果。 +是加法运算符:
put 2 + 2;
You can also store the result in a variable and then output it. The item assignment is an operation and so is the addition. The +
happens first because it has higher precedence:
你还可以将结果存储在变量中,然后将其输出。项目分配是一项操作,添加也是如此。 +首先发生,因为它具有更高的优先级:
my $sum = 2 + 2;
put $sum;
There are operators for subtraction (-
), multiplication (), division (
/
), and exponentiation (*
). You’ll see more in the next chapter.
Outputting a single number is easy. If you want to output a series of numbers, you could have multiple lines:
有减法( - ),乘法(),除法(/)和取幂(*)的运算符。你将在下一章中看到更多内容。
输出单个数字很容易。如果要输出一系列数字,可以有多行:
my $sum = 0;
put $sum + 1;
put $sum + 1 + 1;
put $sum + 1 + 1 + 1;
Each time you add one more to it. That repeats a lot of structure. You can back up a little to make an improvement where the put
statement is the same in each case:
每次再添加一个。这重复了很多结构。在每种情况下,put语句相同时,你可以稍微备份以进行改进:
my $sum = 0;
$sum = $sum + 1;
put $sum;
$sum = $sum + 1;
put $sum;
$sum = $sum + 1;
put $sum;
The $sum
variable shows up on the left and right of the assignment. That’s okay; the compiler’s not going to get confused. It evaluates everything on the right side using the current value of $sum
. When it’s reduced the right side to its value it assigns that to $sum
, replacing the value that’s already there. You’re still doing the same thing over and over again, but now that same thing looks exactly like the other things.
Now it’s time to introduce loop
. It repeatedly executes the code inside its braces. This code will run until you interrupt the program (probably with Control-C):
$ sum变量显示在赋值的左侧和右侧。没关系;编译器不会混淆。它使用$ sum的当前值评估右侧的所有内容。当它将右侧减少到它的值时,它会将其分配给$ sum,替换已存在的值。你仍然一遍又一遍地做同样的事情,但现在同样的事情看起来和其他事情完全一样。
现在是时候介绍循环了。它重复执行括号内的代码。此代码将一直运行,直到你中断程序(可能使用Control-C):
my $sum = 0;
loop {
$sum = $sum + 1;
put $sum;
}
You can combine the two statements inside loop
. The result of an assignment is the value that you assigned. Here, you add to $sum
then assign that result back to $sum
, and use that expression as the value you give to put
:
你可以在循环内组合这两个语句。赋值的结果是你指定的值。在这里,你添加$ sum然后将该结果分配回$ sum,并将该表达式用作你放置的值:
my $sum = 0;
loop {
put $sum = $sum + 1;
}
This sort of structure is so common that it has its own operator: the ++
unary prefix autoincrement operator. It adds one before you use the value:
这种结构很常见,它有自己的运算符:++一元前缀自动增量运算符。它在你使用该值之前添加一个:
my $sum = 0;
loop {
put ++$sum;
}
There’s also a unary postfix version. It adds one to the value, but after you use it:
还有一个一元的后缀版本。它会在值中添加一个,但在你使用它之后:
my $sum = 0;
loop {
put $sum++;
}
EXERCISE 2.8What’s the difference in output in the two programs that use the prefix and postfix autoincrement operators? Can you figure it out without running the programs?
So far you’ve declared variables with my
. That limits their definition to the current scope. That’s a problem for variables you want in a loop
if they should keep their values. This wouldn’t work because each time through the loop
would get a new variable even though you used the same name:
练习2.8使用前缀和后缀自动增量运算符的两个程序的输出差异是什么?如果不运行程序,你能搞清楚吗?
到目前为止,你已经用我的声明了变量。这将他们的定义限制在当前范围内。如果它们应该保留它们的值,那么在循环中你想要的变量就是一个问题。这不起作用,因为即使你使用相同的名称,每次循环都会获得一个新变量:
loop {
my $sum = 0;
put $sum++;
}
Declare the variable with state
instead: this makes the variable private to the block but doesn’t reset it each time through it. A state
declaration only executes the first time through the block and is ignored after that. The assignment to $sum
happens once:
用状态声明变量:这使得变量对块是私有的,但每次都不会重置它。状态声明仅在块中第一次执行,之后将被忽略。 $ sum的赋值发生一次:
loop {
state $sum = 0;
put $sum++;
}
This is a bit nicer because everything about $sum
is contained inside the block. Always try to give variables the smallest scope they need. If they don’t need to be outside the block define them inside it.
Those operators add or subtract one. If you want to increment by a different number you’re back to using +
:
这有点好,因为$ sum的所有内容都包含在块中。始终尝试为变量提供所需的最小范围。如果他们不需要在块之外定义它们。
那些运营商增加或减少一个。如果你想增加一个不同的数字,你就回到了使用+:
loop {
state $sum = 0;
put $sum = $sum + 2;
}
That’s still one too many $sum`s in that code. There’s a special form of the assignment operator that lets you shorten this. You can put the infix operator before the `=
, like this:
那段代码中仍然有太多$ sum。有一种特殊形式的赋值运算符可以让你缩短它。你可以在=之前放置中缀运算符,如下所示:
$sum += 2;
This convenient shorthand is binary assignment. It’s the same as using the variable on both sides of the =
but it’s easier to type:
这种方便的简写是二进制赋值。它与在=的两侧使用变量相同,但更容易输入:
$sum = $sum + 2;
Most binary operators can do this, even if they are multiple characters:
大多数二元运算符都可以执行此操作,即使它们是多个字符:
$product *= 5;
$quotient /= 2;
$is-divisible %%= 3;
EXERCISE 2.9Rewrite the looping program to output only multiples of three by adding the appropriate interval to the previous value. Further modify the program to accept the multiple as a command-line argument.
练习2.9通过将适当的间隔添加到上一个值,重写循环程序以仅输出三的倍数。进一步修改程序以接受多个作为命令行参数。
1.12. Conditional Execution
This chapter has been working its way to a number-guessing program. You know a little bit about numbers, command-line arguments, prompting, and looping. Next you need to know how to decide between two or more paths in your code. That comes in two parts: comparing things to get an answer and using that answer to select the next thing to do.
本章一直致力于数字猜测程序。你对数字,命令行参数,提示和循环有一点了解。接下来,你需要知道如何在代码中的两个或多个路径之间做出决定。这分为两部分:比较事情以获得答案并使用该答案选择下一步要做的事情。
1.12.1. Boolean Values
Boolean values are logical values that can be one thing or the other: yes or no, on or off, or True
or False
. These are of type [Bool
](https://docs.raku.org/type/Bool.html). You’ll use these values to decide between different paths in your program. First, a little Boolean math.
You can combine Boolean values with logical operators. The &&
logical AND operator evaluates to True
if both operands are True
. The ||
logical OR operator evaluates to True
if one or more operators are True
:
布尔值是逻辑值,可以是一个或另一个:是或否,打开或关闭,或者是True或False。这些是Bool类型。你将使用这些值来决定程序中的不同路径。首先,一点布尔数学。
你可以将布尔值与逻辑运算符组合使用。如果两个操作数均为True,则&& logical AND运算符的计算结果为True。 ||如果一个或多个运算符为True,则逻辑OR运算符的计算结果为True:
% raku
> True && True
True
> True && False
False
> True || True
True
> True || False
True
All of these operators have spelled out “word” versions. These are the lowest-precedence operators (aside from the sequence operators). These operations always happen last:
所有这些运营商都拼写出“单词”版本。这些是优先级最低的运算符(除了序列运算符)。这些操作总是最后发生:
% raku
> True and True
True
> True and False
False
> True or False
True
The !
unary prefix operator changes one [Bool
](https://docs.raku.org/type/Bool.html) value to the other one: True
becomes False
, and the other way around. This is called negating the condition. not
is the low-precedence version of that:
的!一元前缀运算符将一个Bool值更改为另一个:True变为False,反之亦然。这被称为否定条件。不是那个低优先级的版本:
% raku
> ! True
False
> ! False
True
> not True
False
> not False
True
Many objects can collapse themselves to a [Bool](https://docs.raku.org/type/Bool.html) value when needed, but it’s up to each object how it does that. For numbers, 0
is False
and everything else is True
.
For most objects (not just numbers) you can use a prefix ?
to coerce into either True
or False
. It calls the .Bool
method on the object. The builtin types know how to convert their values to Booleans using whatever rule they decide. For numbers, 0
is False
and everything else is True
:
许多对象可以在需要时将自身折叠为Bool值,但是由每个对象决定它是如何做到的。对于数字,0为False,其他所有为True。
对于大多数对象(不仅仅是数字),你可以使用前缀?强迫无论是真还是假。它在对象上调用.Bool方法。内置类型知道如何使用他们决定的任何规则将其值转换为布尔值。对于数字,0为False,其他一切为True:
% raku
> ?1
True
> ?0
False
> ?-1
True
> 1.Bool
True
> 0.Bool
False
> (-1).Bool
True
The .so
method and so
routine do the same thing:
> 1.so
True
> 0.so
False
> (-1).so
True
> so 0
False
> so 1
True
Type objects know what they are but they have no concrete value. They are always False
:
类型对象知道它们是什么,但它们没有具体的价值。他们总是错的:
% raku
> Int.so
False
Some things that want Boolean values will implicitly do these coercions for you.
一些需要布尔值的东西会隐式地为你做这些强制。
SHORT-CIRCUIT OPERATORS
The logical operators don’t really evaluate to Boolean values. &&
and ||
test their expressions for True
or False
, but the entire structure evaluates to the last expression it evaluated.
||
needs only one expression to be True
for the entire thing to be True
. If it gets back anything that’s True
, then the entire thing is True
. All of these are False
, but you can see the last expression ||
evaluated:
逻辑运算符并不真正评估为布尔值。 &&和||测试他们的表达式是True还是False,但整个结构的计算结果是它评估的最后一个表达式。
||只需要一个表达式为True,整个事物就是True。如果它返回任何真实的东西,那么整个事情就是真的。所有这些都是假的,但你可以看到最后一个表达式||评价:
% raku
> 0 || Nil
Nil
> 0 || False
False
> 0 || Failure
(Failure)
These are True
. When ||
finds any value that would evaluate to True
as a Boolean it stops right away. These are sometimes called short-circuit operators:
这些是真的。当||找到任何值将被评估为True的值作为它立即停止的布尔值。这些有时被称为短路运营商:
% raku
> True || 0
True
> 137 || True
137
It’s the same with &&
. It returns the last expression it evaluated. If that value is False
then one of those expressions was False
:
与&&相同。它返回它评估的最后一个表达式。如果该值为False,则其中一个表达式为False:
% raku
> 0 && 137
0
> 42 && 8
8
There’s a third operator that’s similar. The defined-or operator, //
, tests its left side for definedness. If the left value is defined that’s the result, even if that value is False
:
还有第三个类似的运营商。定义的或运算符//测试其左侧的定义。如果定义左值是结果,即使该值为False:
% raku
> 0 // 137
0
> Nil // 19
19
A type object is never defined:
永远不会定义类型对象:
% raku
> Int // 7
7
The defined-or is part of a common technique to set a value if a variable doesn’t already have one (or has one that is not defined). You’ll see it as a binary assignment:
如果变量还没有(或者没有定义一个变量),则定义或者是设置值的常用技术的一部分。你会将其视为二进制赋值:
$value //= 137;
1.12.2. Comparing Things
A comparator evaluates to True
or False
based on some relative measure. The numeric equality operator, ==
, compares two numbers to test if they are exactly the same. If they are the same it evaluates to True
; otherwise it evaluates to False
:
比较器根据某些相对度量计算为True或False。数字相等运算符==,比较两个数字以测试它们是否完全相同。如果它们相同则评估为True;否则评估为False:
% raku
> 1 == 1
True
> 1 == 3
False
The numeric inequality operator !=
tests that two numbers are not the same:
数值不等式运算符!=测试两个数字不相同:
% raku
> 1 != 1
False
> 1 != 3
True
Some operators have two versions. You just saw the “ASCII” version, but there’s also a “fancy” Unicode version with ≠
:
一些运营商有两个版本。你刚看到“ASCII”版本,但也有一个“奇特”的Unicode版本,≠:
% raku
> 1 ≠ 3
True
Instead of a literal value you can compare a variable. It doesn’t matter which side you put the values on:
你可以比较变量而不是文字值。将值放在哪一方并不重要:
% raku
> my $number = 37
37
> $number == 38
False
> 39 == $number
False
> $number == 37
True
You can have an expression on either side of the comparator or variables on both sides:
你可以在比较器的任一侧或两侧的变量上都有一个表达式:
% raku
> 2 + 2 == 4
True
> 5 == 2
False
> my $thing1 = 17
17
> my $thing2 = 13
13
> $thing1 == $thing2
False
> $thing1 != $thing2
True
The >
tests that the first operand is numerically greater than the second number and the <
tests that the first is less than the second:
>
测试第一个操作数在数值上大于第二个数字,并且<测试第一个操作数小于第二个数字:
% raku
> 1 > 3
False
> 1 < 3
True
> 3 < 3
False
With an equals sign the test can include the number. >=
tests that the first number is numerically equal to or greater than the second, and ⇐
tests that it is less than or equal:
使用等号,测试可以包括数字。 > =测试第一个数字在数值上等于或大于第二个数字,并且⇐测试它是否小于或等于:
% raku
> 3 < 3
False
> 3 <= 3
True
> 7 > 7
False
> 7 >= 7
True
You can also write these with fancier symbols: >=
as ≥
and ⇐
as ≤
.
Although not a comparator, the %%
operator also returns a Boolean. It tests if the number on the left side is evenly divisible by the number on the right side. This is quite handy:
你也可以使用更高的符号来编写这些符号:> =as≥且⇐as≤。
虽然不是比较器,但%%运算符也返回一个布尔值。它测试左侧的数字是否可以被右侧的数字整除。这非常方便:
% raku
> 10 %% 2
True
> 10 %% 3
False
CHAINED COMPARISONS
You can chain comparison operators. You can test that a number is inside or outside of a window (remember the >
at the start of the input lines is the REPL prompt) like this:
你可以链接比较运算符。你可以测试一个数字是在窗口内部还是外部(请记住输入行开头的>是REPL提示符),如下所示:
% raku
> $n = 10
10
> 7 < $n < 15
True
> 7 <= $n < 15
True
> 7 < $n > 15
False
> 7 > $n < 15
False
Without this you’d have to perform additional and separate comparisons:
如果没有这个,你必须进行额外的和单独的比较:
> 7 < $n and $n < 15
True
CONDITIONALLY RUNNING A STATEMENT
The if
keyword allows you to evaluate a statement only when some condition is satisfied. The postfix form is the easiest. The part after the if
is the condition; it evaluates to True
or False
:
if关键字允许你仅在满足某些条件时评估语句。后缀形式是最简单的。 if之后的部分是条件;它评估为真或假:
my $number = 10;
put 'The number is even' if $number %% 2;
The condition is satisfied when it evaluates to True
. “Satisfaction” is getting what you want; the if
wants (roughly) its condition to be True
before it allows the statement to run. If the condition is False
the program skips that statement.
The if
condition is a Boolean context; it calls .Bool
for you when you don’t do it explicitly. All of these are the same, but you’ll probably do the last one:
在评估为True时满足条件。 “满意”正在得到你想要的东西;在允许语句运行之前,if(大致)将其条件设置为True。如果条件为False,程序将跳过该语句。
if条件是布尔上下文;当你不明确地做它时,它会调用.Bool。所有这些都是一样的,但你可能会做最后一个:
put 'Always outputs' if 1.Bool;
put 'Always outputs' if 1.so;
put 'Always outputs' if ?1;
put 'Always outputs' if 1;
With this you can improve your looping program. Previously you had no way to stop it. The last
keyword immediately leaves the loop:
有了这个,你可以改善你的循环程序。以前你无法阻止它。最后一个关键字立即离开循环:
loop {
state $sum = 0;
put $sum++;
last;
}
This outputs one line then finishes the loop. That’s what last
said to do, but that’s not very useful. This version evaluates last
only when $sum
is 5
:
这输出一行然后完成循环。这就是上次说的,但这并不是很有用。仅当$ sum为5时,此版本才会评估最后一次:
loop {
state $sum = 0;
put $sum++;
last if $sum == 5;
}
EXERCISE 2.10What is the output of this program? Can you work it out without running the program?
The next
command is similar to last
, but it goes on to the next iteration of the loop. You can use a postfix `if`to skip numbers that are divisible by two (when more than one thingy is using a variable in a condition it’s probably better to change it in a separate step):
练习2.10这个程序的输出是什么?你可以在不运行程序的情况下解决问题吗?
下一个命令与last类似,但它继续循环的下一次迭代。你可以使用后缀if跳过可被2整除的数字(当一个条件中有多个东西使用变量时,最好在单独的步骤中更改它):
loop {
state $sum = 0;
$sum += 1;
next if $sum %% 2;
put $sum;
last if $sum > 5;
}
Now you get the odd numbers:
现在你得到奇数:
1
3
5
7
1.12.3. Conditional Branching
You can also write if
in a block form. The code inside the block runs only when the if
is satisfied:
你也可以用块形式写。块中的代码仅在满足if时运行:
if $number %% 2 {
put 'The number is even';
}
You can use parentheses for grouping if you like but they can’t be immediately next to the if
; there must be some whitespace:
如果你愿意,可以使用括号进行分组,但不能紧跟if;必须有一些空白:
if ($number %% 2) {
put 'The number is even';
}
With no space between the if
and the (
it looks like a subroutine call, which it isn’t. This is a syntax error:
if和the之间没有空格(它看起来像子程序调用,它不是。这是语法错误:
if($number %% 2) { # ERROR!
put 'The number is even';
}
An unless
is the opposite sense of if
. It executes its block when the condition is False
. Another way to think about that is that it skips the block when the condition is True
:
除非是相反的if。它在条件为False时执行其块。另一种思考方式是在条件为True时跳过块:
unless $number %% 2 {
put 'The number is odd';
}
Some people prefer an if
with a negated condition:
有些人更喜欢具有否定条件的if:
if ! $number %% 2 {
put 'The number is odd';
}
An else
allows you to provide a default block to run when the if
is not satisfied:
如果不满足if,则允许你提供默认块以运行:
if $number %% 2 {
put 'The number is even';
}
else {
put 'The number is odd';
}
These different possibilities are branches of your code. You go down one or the other branch but not both. This is one example of a control structure that decides which code runs.
The entire if
structure evaluates to a value when you put a do
in front of it. The do
allows you to treat a control structure as an expression. The result is the last evaluated expression from inside the structure. This way you can isolate only the parts that are different, then use one statement for output:
这些不同的可能性是代码的分支。你去一个或另一个分支,但不是两个。这是决定运行哪些代码的控制结构的一个示例。
当你在它前面放置一个do时,整个if结构的计算结果为一个值。 do允许你将控制结构视为表达式。结果是结构内部的最后一个计算表达式。这样,你只能隔离不同的部分,然后使用一个语句进行输出:
my $type = do if $number %% 2 { 'even' }
else { 'odd' }
put 'The number is ', $type;
You can skip the intermediate variable (although if that’s confusing it’s okay to do it the longer way):
你可以跳过中间变量(虽然如果这让人感到困惑,可以用更长的方式去做):
put 'The number is ',
do if $number %% 2 { 'even' }
else { 'odd' }
There’s a shortcut for this. The conditional operator has three parts: the condition, the True
branch, and the False
branch. Between those parts are ??
and !!
:
这有一个捷径。条件运算符有三个部分:条件,True分支和False分支。那些部分之间是??和!!:
CONDITION ?? TRUE BRANCH !! FALSE BRANCH
Using this operator you can rewrite the preceding example. The particular formatting isn’t important, but this fits nicely on the page and lines up the different parts. You don’t use a block, which makes this useful for short bits of code:
使用此运算符可以重写前面的示例。特定的格式并不重要,但这非常适合页面并排列不同的部分。你不使用一个块,这使得这对短代码有用:
put 'The number is ',
$number %% 2 ?? 'even' !! 'odd';
An elsif
specifies another branch with its own condition, so you have three ways this code might run. Some people think zero is neither odd nor even, and they can add another branch for that:
elsif指定另一个具有自己条件的分支,因此你可以通过三种方式运行此代码。有些人认为零既不是奇数也不是偶数,他们可以为此添加另一个分支:
if $number == 0 {
put 'The number is zero';
}
elsif $number %% 2 {
put 'The number is even';
}
else {
put 'The number is odd';
}
This code works, but it has some repeated structure because each branch has a put
. A do
cleans that up nicely. Here’s another way to write that:
这段代码有效,但它有一些重复的结构,因为每个分支都有一个put。 A做得很好清理。这是写另一种方式:
put 'The number is ', do
if $number == 0 { 'zero' }
elsif $number %% 2 { 'even' }
else { 'odd' }
EXERCISE 2.11Create a program that outputs the numbers from 1 to 100. However, if the number is a multiple of three, output “Fizz” instead of the number. If it’s a multiple of five, output “Buzz”. If it’s a multiple of both three and five, output “FizzBuzz”.
练习2.11创建一个从1到100输出数字的程序。但是,如果数字是3的倍数,则输出“Fizz”而不是数字。如果它是五的倍数,则输出“Buzz”。如果它是三个和五个的倍数,则输出“FizzBuzz”。
1.13. Putting It All Together
With a few more things you can now write the number-guessing program. The .rand
method returns a fractional number between 0 and the integer (exclusively):
通过更多的东西,你现在可以编写数字猜测程序。 .rand方法返回0和整数(仅限)之间的小数:
% raku
> 100.rand
62.549491627582
The .Int
method coerces that to a whole number. It discards the fractional portion; it does not round the number. Put that together with .rand
and you get a whole number between 0 and the starting number:
% raku
> 100.rand.Int
23
Put that together in a complete program. Choose the number, then test what side of another number (sometimes called the “pivot”) it’s on:
把它放在一个完整的程序中。选择数字,然后测试它所在的另一个数字(有时称为“数据透视”)的哪一侧:
my $number = 100.rand.Int;
if $number > 50 {
put 'The number is greater than 50';
}
elsif $number < 50 {
put 'The number is less than 50';
}
else {
put 'The number is 50';
}
Run that several times and you should get different output eventually:
运行几次,你最终会得到不同的输出:
% raku random.p6
The number is less than 50
% raku random.p6
The number is less than 50
% raku random.p6
The number is greater than 50
EXERCISE 2.12Wrap the pivot program in a MAIN
subroutine so you can specify the highest possible number as a command-line argument. Default to 100
if you don’t supply an argument. Adjust that so the program can take another command-line argument to specify the pivot number.
In the previous exercise you set the default for the second argument using a hard-coded literal integer:
练习2.12在MAIN子例程中包含pivot程序,以便你可以将最高可能的数字指定为命令行参数。如果你不提供参数,则默认为100。调整它,以便程序可以使用另一个命令行参数来指定数据透视表编号。
在上一个练习中,你使用硬编码的文字整数设置第二个参数的默认值:
sub MAIN ( $highest = 100, $pivot = 50 ) { ... }
If you run the program with one command-line argument that is less than 50
(or whatever you chose as your default) the output will always be the same:
如果使用一个小于50的命令行参数(或者你选择作为默认值的任何内容)运行程序,则输出将始终相同:
% raku number-program.p6 37
The number is less than 50
You can use parameters you’ve already specified to compute defaults for other parameters. Use $highest
to compute $pivot
:
你可以使用已指定的参数来计算其他参数的默认值。使用$ highest来计算$ pivot:
sub MAIN ( $highest = 100, $pivot = $highest / 2 ) {
EXERCISE 2.13Modify your answer to the previous exercise so you can set the pivot to half the highest value. Default to 50
if you don’t specify two arguments.
Now you have everything you need to write your number-guessing program. Your program chooses a secret number that you then have to figure out. This early in the book that seems like a complicated program, but you’ve seen just enough to make it:
练习2.13修改上一练习的答案,以便将枢轴设置为最高值的一半。如果未指定两个参数,则默认为50。
现在,你拥有编写数字猜测程序所需的一切。你的程序会选择一个你必须弄清楚的密码。在本书的早期,这看起来像一个复杂的程序,但你已经看到了足够的成就:
-
Choose a secret number (
.rand
). -
Loop repeatedly until the person guesses the number (
next
andlast
). -
Get the person’s guess (
prompt
). -
Give the person a hint about their guess. Tell them if they are too high or low (comparators,
if
).
•选择一个密码(•。和•)。
反复循环,直到该人猜到该号码(下一个和最后一个)。
得到这个人的猜测(提示)。
给这个人一个关于他们猜测的暗示。告诉他们是否太高或太低(比较,如果)。
EXERCISE 2.14Implement the number-guessing program. If you supply a command-line argument use that as the maximum number; otherwise use 100. It may help to immediately output the secret number as you get your program working.
练习2.14实施数字猜测程序。如果提供命令行参数,请将其用作最大数字;否则使用100.当你的程序正常工作时,可能有助于立即输出密码。
1.14. Summary
You made it! First chapters are typically the toughest because you’re getting your bearings. You’ve made at least one meaty program that incorporates several things that you haven’t seen in depth yet. You can take input from the command line or from a prompt. You can compare values and follow different code branches. Not bad for a first chapter.
你做到了!第一章通常是最难的,因为你得到了你的支持。你已经制作了至少一个丰富的程序,其中包含了一些你还没有深入见过的东西。你可以从命令行或提示中获取输入。你可以比较值并遵循不同的代码分支。对于第一章来说还不错。 == 数字
This chapter steps back from the breadth of the previous chapter to focus on the idea of numbers and their representation in your programs. Raku supports several types of numbers and works hard to keep them exact as long as it can.
本章从前一章的广度开始,重点介绍数字的概念及其在程序中的表示。 Raku 支持多种类型的数字,并且尽可能地努力保持它们的准确性。
1.15. Number Types
Not all numbers are created alike. You’ve seen whole numbers, how to do basic mathematical operations on them, and how to compare them. But whole numbers are just one of the numeric types. You can see what type a number is by calling .^name
on it:
并非所有数字都是相同的。你已经看过整数,如何对它们进行基本的数学运算,以及如何比较它们。但整数只是数字类型之一。你可以通过调用 .^name
来查看数字的类型:
% raku
> 137.^name
Int
That’s an [Int
](https://docs.raku.org/type/Int.html), short for “integer”—whole numbers, positive or negative, and zero. The compiler recognizes it because it has decimal digits; it parses it and creates the object for you. But try it with a negative number:
这是一个 [Int
](https://docs.raku.org/type/Int.html),是“整数”的缩写 - 整数,正数或负数,以及零。编译器识别它,因为它有十进制数字;它解析它并为你创建对象。但尝试使用负数:
% raku
> -137.^name
Cannot convert string to number
The minus sign isn’t actually part of the number. It’s an operator (a unary prefix one) that negates the positive number. That means that -137
isn’t a term; it’s an expression. The .^name
happens first and evaluates to Int`as before. When `-
tries to negate the type name it realizes it can’t do that and complains. You can fix the ordering problem with parentheses—things inside parentheses happen before those outside:
减号实际上不是数字的一部分。它是一个运算符(一个一元前缀)否定正数。这意味着 -137
不是一个项;这是一个表达式。 .^name
首先发生,并计算为之前的 Int
。当 -
试图否定类型名称时,它意识到它无法做到并抱怨。你可以使用括号修复顺序问题 - 括号内的事情发生在外部之前:
% raku
> (-137).^name
There are other types of numbers, some of which are shown in [Table 3-1](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch03.html#camelia-numbers-TABLE-examples).
还有其他类型的数字,其中一些如表3-1所示。
Value |
Class |
Description |
|
正整数 (whole number) |
|
|
负整数 (whole number) |
|
|
分数 |
|
|
科学计数法 |
|
|
[ |
带有实部和虚部的复数 |
EXERCISE 3.1Call .^name
on some of the other kinds of numbers from [Table 3-1](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch03.html#camelia-numbers-TABLE-examples). What other sorts of numbers does Raku support? Which ones need parentheses to group them?
练习3.1Call。^表3-1中其他一些数字的名称。 Raku支持哪些其他类型的数字?哪些人需要括号将它们分组?
1.16. Integers
The integers are the whole numbers. You’ve seen that they can be represented in many ways and in different bases:
整数是整数。你已经看到它们可以通过多种方式和不同的基础来表示:
137
-19
0x89
:7(254)
Including underscores between digits can make larger numbers easier to read. They aren’t part of the number and can only come between digits (so, not two in a row). You could separate by thousands:
在数字之间加上下划线可以使更大的数字更容易阅读。它们不是数字的一部分,只能在数字之间(因此,不是连续两个)。你可以分成几千:
123_456_789
Two hexadecimal digits represent an octet; it’s easy to see those when you have underscores between pairs ofdigits:
两个十六进制数字代表一个八位字节;当你在数字对之间有下划线时很容易看到它们:
0x89_AB_CD_EF
1.16.1. 类型约束
When you declare a variable without assigning to it there’s still “something” there. It’s a type object with the type [Any
](https://docs.raku.org/type/Any.html)—a generic type that’s the basis for most Raku objects:
当你声明一个变量但没有为其赋值时,变量里仍然存在“某些东西”。它是一个类型为[Any
](https://docs.raku.org/type/Any.html)类型的类型对象,它是大多数 Raku 对象的基础:
my $number;
put $number.^name; # Any
If you coerce [Any
](https://docs.raku.org/type/Any.html) to a Boolean value you get False
. Any type object is undefined, but this is slightly more undefined because it’s a general class.
When you want to assign to a container that holds a type object you have to replace it with a value of the same type or something based on that type. You can replace [Any
](https://docs.raku.org/type/Any.html) with almost any literal value:
如果你强转[Any
](https://docs.raku.org/type/Any.html)为布尔值,你会得到 False
。任何类型对象都是未定义的,但这稍微未定义,因为它是一个通用类。
如果要给包含类型对象的容器赋值,则必须使用相同类型的值或基于该类型的值替换它。你可以使用几乎任何字面值替换[Any
](https://docs.raku.org/type/Any.html):
my $number; # starts as Any
$number = 137;
$number = 'Hamadryas';
That’s the same as explicitly constraining the value to the [Any
](https://docs.raku.org/type/Any.html) type. Without an assignment the variable gets the type object of its constraint:
my Any $number; # starts as Any
$number = 137;
$number = 'Hamadryas';
You can be as specific as you like. If your variable should only store an integer you can use the [Int
](https://docs.raku.org/type/Int.html) type to constrain it even before you assign to it. Even without a value it knows its type:
你可以随心所欲。如果你的变量只应存储一个整数,则可以使用 [Int
](https://docs.raku.org/type/Int.html) 类型在赋值给它之前对其进行约束。即使没有值,它也知道它的类型:
my Int $number;
put $number2.^name; # Int
Whatever you assign to it must be an [Int
](https://docs.raku.org/type/Int.html) (or something derived from an [Int
](https://docs.raku.org/type/Int.html)):
无论你给它赋什么值,它必须是一个 [Int
](https://docs.raku.org/type/Int.html)(或从 [Int
](https://docs.raku.org/type/Int.html) 派生的东西):
my Int $number;
$number = 137;
$number = 'Hamadryas'; # NOPE! Error
When you try to assign something that is not the correct type you get an error:
当你尝试分配不正确类型的内容时,你会收到错误:
Type check failed in assignment to $n; expected Int but got Str
This check happens when you assign to the variable. Raku calls this “gradual typing.” You don’t have to use it until you want it, but you still have to be careful to obey it when you do use it. The compiler can’t catch all type errors before you run the program.
You can use types in your MAIN
signature too. These types apply to the command-line arguments. If you don’t supply appropriate values you’ll get an error right away:
给变量赋值时会发生此检查。 Raku 将此称为“渐进类型”。直到你需要它时才使用,但是在使用它时仍然需要小心遵守它。在运行程序之前,编译器无法捕获所有类型错误。
你也可以在 MAIN
签名中使用类型。这些类型应用于命令行参数。如果你没有提供合适的值,你将立即收到错误:
sub MAIN ( Int $n ) { ... }
EXERCISE 3.2Create a program that takes two arguments from the command line and outputs their types. Try it with numbers and text in each position. What types do you get?
When you ran your program for the previous exercise you saw the type name IntStr
. This is an allomorph—a type that’s both an [Int
](https://docs.raku.org/type/Int.html) and a [Str
](https://docs.raku.org/type/Str.html) at the same time.
All of the command-line arguments are actually text, even though some of them look like numbers. There’s a hidden val
routine that inspects the arguments and turns those that look like some sort of number into the appropriate allomorph. This type has the behavior of both numbers and strings at the same time. You might think that’s a little weird at first, but it’s one of the things that allows a language such as Raku to easily process text.
练习3.2 创建一个从命令行获取两个参数并输出其类型的程序。在每个位置尝试使用数字和文本。你得到什么类型?
当你为上一个练习运行程序时,你看到了类型名称 IntStr
。这是一个同质异形的类型,它同时是 [Int
](https://docs.raku.org/type/Int.html) 和 [Str
](https://docs.raku.org/type/Str.html)。
所有命令行参数实际上都是文本,即使它们中的一些看起来像数字。有一个隐藏的 val
例程,它检查参数并将那些看起来像某种数字的数字转换为适当的同质异形。此类型同时具有数字和字符串的行为。你可能认为一开始有点奇怪,但这是允许像 Raku 这样的语言轻松处理文本的事情之一。
1.16.2. 智能匹配
智能匹配运算符 ~~
代表了许多种比较,并为其操作数选择了正确的比较。~~
的左侧是值或变量,~~
的右侧是类型,如果值是该类型或从该类型派生的,则返回 True
:
% raku
> 1 ~~ Int
True
> 1.^mro
((Int) (Cool) (Any) (Mu))
> 1 ~~ Cool
True
> 1 ~~ Any
True
这是有效的,因为字面量是隐式创建的对象并知道它们是什么。将其与任何其他类型进行比较,即使该值可能是该类型的合法值,也会返回 False
:
% raku
> 1 ~~ Complex
False
但是,你可以使用以所需类型命名的强转方法轻松地转换数字类型(如果类型提供了一个):
% raku
To exit type 'exit' or '^D'
> 1.Complex
1+0i
> 1.Complex ~~ Complex
True
Smart matching is easy with given-when
. Two things happen with this feature. First, given
binds $_ to the value of the variable you specify. The $_ is the topic; this allows you to write some code that uses $_
to process the current thing you care about without knowing what that thing is.
Second, when
looks at the condition you supply. If there’s no explicit comparator it smart matches $_
against the value you gave it. The first when
block that is satisfied is the one that wins. A default
block (no condition!) catches it when no when
does.
The conditions for these when
s are type objects to smart match against $_:
有了 given-when
智能匹配就很容易了。这个功能发生了两件事。首先,given
将 $_ 绑定到你指定的变量的值上。 $_ 是主题;这允许你编写一些使用 $_
的代码来处理你正关心的当前事物,而不需知道那是什么东西。
其次,when
查找你提供的条件。如果没有显式的比较器,它将 $_
与你给出的值进行智能匹配。第一个满足的 when
块是获胜的块。没有时,default
块(无条件!)会捕获它。
这些 when
的条件是与 $_
智能匹配的类型对象:
given $some-number {
when Int { put 'Saw an integer' }
when Complex { put 'Saw a complex number' }
when Rat { put 'Eek! Saw a rat!' }
default { put 'Saw something' }
}
Making everything explicit, you’d get something like this mess of repeated typing:
要让一切都清楚,你会得到类似这种重复输入的东西:
given $some-number -> $_ {
when $_ ~~ Int { put 'Saw an integer' }
when $_ ~~ Complex { put 'Saw a complex number' }
when $_ ~~ Rat { put 'Eek! Saw a rat!' }
default { put 'Saw something' }
}
You can make this shorter using do
in the same way you did with if
. The last evaluated expression becomes the value of the entire given
structure:
使用 do
可以使用与 if
相同的方式缩短代码。最后计算的表达式成为整个 given
结构的值:
put 'Saw ', do given $some-number {
when Int { 'an integer' }
when Complex { 'a complex number' }
when Rat { 'a rat! Eek!' }
default { 'something' }
}
EXERCISE 3.3Using given
, create a program that reports the type of number you specify on the command line. Try it with arguments such as 17
, 17.0
, 17i
, and Hamadryas
.
There’s another interesting thing you can do with $_. A method call dot with no object to the left uses $_
as the object:
练习3.3 使用 given
,创建一个程序,报告你在命令行上指定的数字类型。尝试使用诸如 17
, 17
. 0,17i
和 Hamadryas
之类的参数。
你可以用 $_ 做另一个有趣的事情。左边没有对象的方法调用点使用 $_ 作为对象:
$_.put;
.put;
put $_.roots unless $_.is-prime;
put .roots unless .is-prime;
You can use a postfix given
to set $_
for a single statement to avoid typing out a variable multiple times. You’ll see the implicit topic much more as you go through the book:
你可以使用后缀 given
为单个语句设置 $_
,以避免多次输入同一变量。在阅读本书时,你会多次看到隐式的主题:
my $some-number = 19;
put .^name, ' ', .is-prime given $some-number;
1.17. 有理数
Raku represents nonwhole numbers as fractions using integers. You might literally represent it as a number with a decimal point (sometimes called a floating-point number), but the compiler turns that into a fraction. You can see the numerator and denominator for that reduced fraction:
Raku 使用整数表示非全数字作为分数。你可能会将其表示为带小数点的数字(有时称为浮点数),但编译器将其转换为分数。你可以看到化简后的分数的分子和分母:
% raku
> 3.1415926
3.1415926
> 3.1415926.^name
Rat
> 3.1415926.numerator
15707963
> 3.1415926.denominator
5000000
EXERCISE 3.4 Create a program that takes a single decimal number command-line argument and shows it to you as a fraction. What are the numerator and denominator?
You can add rational numbers to get another fraction; Raku does the work for you:
练习3.4 创建一个接受单个十进制数字命令行参数的程序,并将其作为分数显示给你。分子和分母是什么?
你可以添加有理数来得到另一个分数; Raku 为你完成工作:
% raku
> 1/7 + 1/3
0.476190
The .perl
method shows you how Raku thinks about it. You can see the fraction with the least common multiple in the denominator:
.perl
方法向你展示 Raku 如何思考它。你可以看到这个分数的分母中有最小公倍数:
% raku
> (1/7 + 1/3).perl
<10/21>
It didn’t divide the numbers then try to store the result as a floating-point number; that would lose accuracy. It keeps it as an exact fraction as long as it can. This means that these sums are exactly right.
Try this in your favorite programming language:
它没有除以数字,然后尝试将结果存储为浮点数;那会失去准确性。只要它可以,它就将它保持为精确的分数。这意味着这些总和是完全正确的。
用你最喜欢的编程语言试试这个:
% raku
> 0.1 + 0.2
0.3
Another way to define a [Rat
](https://docs.raku.org/type/Rat.html) is to write it as a literal fraction inside angle brackets, <>
:
定义 [Rat
](https://docs.raku.org/type/Rat.html) 的另一种方法是将其写为尖括号内的字面量分数,<>
:
% raku
> <10/21>
0.476190
> <10/21>.^name
Rat
> <10/21>.perl
<10/21>
It’s the same in a program:
它在程序中是相同的:
my $seventh = <1/7>;
my $third = <1/3>;
my $added = $seventh + $third;
put $added.perl;
You can’t do this with a variable inside the angle brackets. You’ll see what’s going on in the next chapter, but inside the <>
that’s not really a variable. The $
is a literal character:
你无法使用尖括号内的变量执行此操作。你将在下一章中看到会发生什么事情,但在 <>
里面它并不是真正的变量。在 <>
里面 $
是一个字面量字符:
% raku
> <1/$n>
1/$n
At some point the fractions will be too large and you’ll get an error. Here’s a program that adds the reciprocals of the powers of two. It uses loop
and uses ++
to make higher powers of two:
在某些时候,分数将太大,你会得到一个错误。这是一个程序,它将 2 的幂的倒数相加。它使用 loop
并使用 ++
来获得 2 的更高的幂:
my $n = 0;
my $sum = 0;
loop {
$sum += 1 / 2**$n++;
put .numerator, '/', .denominator, ' = ' given $sum;
}
You get progressively larger fractions even though this series converges on 2
. Eventually it fails because the denominator is limited to a 64-bit integer size:
即使该系列收敛于 2
,你也会逐渐获得更大的分数。最终它会失败,因为分母限制为 64 位整数:
% raku converging.p6
1/1 = 1
3/2 = 1.5
7/4 = 1.75
15/8 = 1.875
31/16 = 1.9375
63/32 = 1.96875
...
4611686018427387903/2305843009213693952 = 2
9223372036854775807/4611686018427387904 = 2
18446744073709551615/9223372036854775808 = 2
No such method 'numerator' for invocant of type 'Num'.
There’s another class that can handle this. A [FatRat
](https://docs.raku.org/type/FatRat.html) is a fraction with an arbitrarily large denominator. This is the first time you get to construct an object directly. Call the .new
method with the numerator and denominator:
还有另一个类可以处理这个问题。 [FatRat
](https://docs.raku.org/type/FatRat.html) 是具有任意大分母的分数。这是你第一次直接构造对象。使用分子和分母调用 .new
方法:
my $sum = FatRat.new: 0, 1;
If you have an existing [Rat
](https://docs.raku.org/type/Rat.html) you can turn it into a [FatRat
](https://docs.raku.org/type/FatRat.html) with a method. You’d do that when you know you are going to need it later when you do math with another [FatRat
](https://docs.raku.org/type/FatRat.html):
如果你有一个现有的 [Rat
](https://docs.raku.org/type/Rat.html),可以使用方法将其转换为 [FatRat
](FatRat
(https://docs.raku.org/type/FatRat.html) 做数学时,如果你知道以后需要它,你会这样做:
my $fatrat = <10/21>.FatRat;
When you need to add a [FatRat
](https://docs.raku.org/type/FatRat.html) to the existing one, you can construct that one in the same way:
当你需要将 [FatRat
](https://docs.raku.org/type/FatRat.html) 添加到现有的 [FatRat
](https://docs.raku.org/type/FatRat.html) 时,你可以以相同的方式构造它:
FatRat.new: 1, 2**$n++
Otherwise the program is the same, although this version will run much longer. Notice that all the fractions need to be [FatRat
](https://docs.raku.org/type/FatRat.html)s to keep it going:
否则程序是相同的,虽然这个版本将运行更长时间。请注意,所有分数都需要 [FatRat
](https://docs.raku.org/type/FatRat.html) 才能保持运行:
my $n = 0;
my $sum = FatRat.new: 0, 1;
loop {
$sum += FatRat.new: 1, 2**$n++;
put $sum.^name;
put .numerator, '/', .denominator, ' = ', $_ given $sum;
}
EXERCISE 3.5Create a program that sums the series of fractions 1, 1/2, 1/3, and so on. This is the harmonic series. Calculate the partial sum up to a denominator of 100. Output the value at each stage of the sum.
练习3.5 创建一个程序,对一系列分数 1,1/2,1/3 等求和。这是谐波系列。计算分母总和为 100 的分母。在总和的每个阶段输出值。
1.18. Imaginary and Complex Numbers
Imaginary numbers are multiples of the square root of –1. Impossible, you say? I’m not going to explain that in this book but Raku has them. If you’re an electrical engineer you’ve likely run into complex numbers when modeling certain properties.
Raku has a term for the imaginary unit; it’s i
. The number 5i
is imaginary; it’s five times the imaginary unit. Try it in the REPL:
虚数是 -1 的平方根的倍数。你说不可能吗?我不打算在本书中解释,但 Raku有它们。如果你是电气工程师,在对某些属性进行建模时可能会遇到复数。
Raku 有一个虚数单位的术语;它是 i
。数字 5i
是虚拟的;它是虚部单位的五倍。在 REPL 中尝试:
% raku
> 5i
0+5i
> 5*i
0+5i
> 5\i
0+5i
> 5\ i
0+5i
That was four ways to write the same thing. The first way puts two terms, 5
and i
, next to each other with no whitespace or separator. That works and is likely to be the way you’ll type it most of the time. The second multiplies 5
and i
to get the same result. The last two use \
to create unspace. One has no space and the other has some space.
You can’t have only whitespace between the digits and the i
, or the compiler will think that you have terms in a row (because you do):
这是写同样事情的四种方式。第一种方法是将两个项 5
和 i
放在一起,没有空格或分隔符。这很有效,很可能是你大部分时间打字的方式。第二个乘以 5
和 i
得到相同的结果。最后两个使用 \
来创建 unspace。一个没有空格,另一个有空格。
你不能只有数字和 i
之间的空格,否则编译器会认为你有连续的项(因为你这样做):
% raku
> 5 i
===SORRY!=== Error while compiling:
Two terms in a row
------> 5⏏ i
When you tried the imaginary number 5i
in the REPL you got back 0 + 5i
. That’s a real number added to an imaginary number. Taken together they form a complex number that has real and imaginary parts.
To get the real or imaginary parts of the number you can use the .re
or .im
methods, which take their short names from the common math notation:
当你在 REPL 中尝试了虚数 5i
时,你得到了 0 + 5i
。这是实数和虚数相加。总之,它们一块儿形成了一个具有实部和虚部的复数。
要获取数字的实部或虚数部分,可以使用 .re
或 .im
方法,这些方法的常用数学符号采用短名称:
% raku
> my $z = 137+9i;
137+9i
> $z.^name
Complex
> $z.re
137
> $z.im
9
You can add, subtract, and multiply [Complex
](https://docs.raku.org/type/Complex.html) numbers. Multiplication involves cross terms with the real part of one number multiplied by the imaginary part of the other:
% raku
> (5+9i) * (6+3i)
3+69i
> (5+9i) + (6+3i)
11+12i
> (5+9i) - (6+3i)
-1+6i
> (5+9i) / (6+3i)
1.26666666666667+0.866666666666667i
You can even multiply i
by itself:
你甚至可以让 i
和自身相乘:
% raku
> i*i
-1
1.19. Numbers Small and Large
Everything that doesn’t fit into the specific numeric types is in the general [Num
](https://docs.raku.org/type/Num.html) type. The number e
(the natural base) is one of those numbers:
所有不符合特定数字类型的东西都是通用的 [Num
](https://docs.raku.org/type/Num.html) 类型。数字 e
(自然基数)就是这些数字之一:
% raku
> e.^name
Num
> e
2.71828182845905
You can also use infinities. Putting all the nuances and uses aside, for this book Inf
is just something that’s larger than any integer. You’ll see it in use later:
你也可以使用无穷大。把所有的细微差别和用法放在一边,对于这本书,Inf
只是比任何整数都大的东西。你会在后面看到它在使用:
% raku
> Inf.^name
Num
> (-Inf).^name
Num
You can write numbers in exponential notation. You can specify a power of 10 after an e of either case. This is a different sort of e than the term you just saw:
你可以用指数表示法编写数字。在任一情况下,你都可以在 e 后面指定 10 的幂。这与你刚刚看到的术语不同:
6.02214e23
6.02214E23
These are the same as multiplying the number by a power of 10 that you construct explicitly:
这些与将数字乘以你显式地构造的 10 的幂相同:
6.02214 * 10**23
These numbers aren’t [Int
](https://docs.raku.org/type/Int.html)s or [Rat
](https://docs.raku.org/type/Rat.html)s, although you might be able to convert them. They are the more general [Num
](https://docs.raku.org/type/Num.html) type:
这些数字不是[整数
](有理数
(Num
(https://docs.raku.org/type/Num.html)类型:
put 1e3.^name; # 1000, but still a Num
put 1e3.Int; # 1000, but now an Int
Very small numbers have a negative power of 10:
非常小的数字具有10的负数幂:
6.626176e-34
You can use this on not-so-small numbers too:
你也可以在不那么小的数字上使用它:
7.297351e-3
EXERCISE 3.6What is 7.297351e-3 as a fraction? What’s its reciprocal?
练习3.6 7.297351e-3 作为分数是什么?它的倒数是什么?
1.20. The Numeric Hierarchy
Raku thinks about numbers relative to their “width.” [Int
](https://docs.raku.org/type/Int.html) comprises the positive and negative whole numbers and is relatively narrow. The [Rat
](https://docs.raku.org/type/Rat.html) type includes the whole numbers and some of the numbers between them (the ones that can be fractions).
[Rat
](https://docs.raku.org/type/Rat.html) is “wider” not because its endpoints are greater but because there are more numbers between the same endpoints. [FatRat
](https://docs.raku.org/type/FatRat.html) is even wider because it pushes the endpoints farther apart to contain even more numbers.
Even wider than the rationals, fat or otherwise, are plain ol’ [Num
](https://docs.raku.org/type/Num.html)s. Those include the rest of the numbers; the ones that you can’t represent as fractions. We typically call this wider set the Real`s but Raku calls them [`Num
](https://docs.raku.org/type/Num.html)s.
And when you think that you are the widest that you can go, the numbers go sideways into the plane of the [Complex
](https://docs.raku.org/type/Complex.html) numbers.
Sometimes you may want to go narrower or wider. Many Raku objects have coercer methods that can do that for you. Start with an [Int
](https://docs.raku.org/type/Int.html) and turn it into a [Complex
](https://docs.raku.org/type/Complex.html) number. This goes wider:
Raku 考虑数字相对于它们的“宽度”. [Int
](https://docs.raku.org/type/Int.html) 包含正整数和负数,并且相对较窄。 [Rat
](https://docs.raku.org/type/Rat.html) 类型包括整数和它们之间的一些数字(可以是分数的数字)。
[Rat
](https://docs.raku.org/type/Rat.html) “更宽”并不是因为它的端点更大,而是因为相同端点之间的数字更多。 [FatRat
](https://docs.raku.org/type/FatRat.html) 更宽,因为它将端点推得更远,以包含更多数字。
比有理数,fat 或其他的数字更宽的是纯粹的 [Num
](https://docs.raku.org/type/Num.html)。其中包括其他数字;那些你不能表示为分数的。我们通常称这个更广泛的集合为 [Complex
](https://docs.raku.org/type/Complex.html),但 Raku 称它们为 [Num
](https://docs.raku.org/type/Num.html) 。
当你认为自己是最宽泛的时候,数字就会横向进入[复数
](https://docs.raku.org/type/Complex.html)的平面。
有时你可能想要更窄或更宽。许多 Raku 对象都有强转方法,可以为你做到这一点。从 [Int
](https://docs.raku.org/type/Int.html) 开始并将其转换为[复数
](https://docs.raku.org/type/Complex.html)。这更广泛:
% raku
> 6.Complex
6+0i
Or start with a [Complex
](https://docs.raku.org/type/Complex.html) number and go narrower. This one can also be an [Int
](https://docs.raku.org/type/Int.html):
或者从[复数
](https://docs.raku.org/type/Complex.html)开始,然后变窄。这个也可以是 [Int
](https://docs.raku.org/type/Int.html):
% raku
> (6+0i).Int
6
You can do those coercions because you know something about the numbers. If you want the narrowest type without knowing what it is beforehand, you can use .narrow
. If you tried to convert π to an [Int
](https://docs.raku.org/type/Int.html) you wouldn’t get an error, but you wouldn’t get π. If you use .narrow
you get a [Num
](https://docs.raku.org/type/Num.html), the narrowest you can go:
你可以做那些强转,因为你对数字有所了解。如果你想要最窄的类型而不知道它是什么,你可以使用 .narrow
。如果你尝试将 π 转换为 [Int
](https://docs.raku.org/type/Int.html),则不会出现错误,但你不会得到 π。如果你使用 .narrow
你得到一个 [Num
](https://docs.raku.org/type/Num.html),你可以去的最窄的:
% raku
> (π+0i).Int
3
> (π+0i).Int == π
False
> (π+0i).narrow.^name
Num
> (π+0i).narrow == π
True
有时你不能变得更窄:
% raku
> (6+3i).narrow.^name
Complex
EXERCISE 3.7Modify the number-guessing program from the previous chapter so you have to guess a complex number. You have to decide high and low in two directions this time.
练习3.7 修改前一章中的数字猜测程序,这样你就必须猜出一个复数。这次你必须决定两个方向的高低。
1.21. 总结
That’s most of the story with numbers. You saw some of the methods you can use, and you’ll find even more in the documentation for each type. You also saw a bit about constraining variables to only the type you want, and you’ll become more sophisticated with that.
这是数字的大部分故事。你看到了一些可以使用的方法,你可以在每种类型的文档中找到更多。你还看到了一些关于将变量限制为你想要的类型的信息,并且你将变得更加复杂。
2. 字符串
Strings represent the text data in your program as [Str
](https://docs.raku.org/type/Str.html) objects. Raku’s facility with text data and its manipulation is one of its major attractions. This chapter focuses on the many ways that you can create [Str
](https://docs.raku.org/type/Str.html)s; for any job you have there’s likely a feature that makes that easy for you. Along with that you’ll see a bit about inspecting, extracting, and comparing text in preparation for loftier goals coming up.
字符串将程序中的文本数据表示为[Str
](https://docs.raku.org/type/Str.html)对象。 Raku 的文本数据和它的文本操作天赋是其主要吸引力之一。本章重点介绍可以创建[字符串
](https://docs.raku.org/type/Str.html)的多种方法;对于你的任何工作,可能有一个功能,使你的工作变得容易。除此之外,你还会看到有关检查,提取和比较文本的内容,以便为即将出现的更高目标做准备。
2.1. Literal Quoting
You can type literal text directly into your program. What you type is what the text is, and the compiler does not interpret it as anything other than exactly what you typed. You can surround literal text with half-width corner brackets, 「
and 」
:
你可以直接在程序中键入字面文本。你键入的内容是文本的内容,编译器会将其解释为你输入的内容。你可以使用半角括号`「` 和 `」`来包围字面文本:
「Literal string」
This is your first encounter with a paired delimiter. These characters mark the beginning and end of the [Str
](https://docs.raku.org/type/Str.html). There’s an opening character and a closing character that surround your text.
Any character that you use is interpreted as exactly what it is, with no special processing:
这是你第一次遇到配对分隔符。这些字符标记[字符串
](https://docs.raku.org/type/Str.html)的开头和结尾。文本周围有一个开口字符和一个闭合字符。
你使用的任何字符都被解释为它的确切含义,没有特殊处理:
「Literal '" string with \ and {} and /」
You can’t use only one of the delimiter characters in the [Str
](https://docs.raku.org/type/Str.html). These won’t work:
「 Unpaired 「 Delimiters 」
「 Unpaired 」 Delimiters 」
However, if you pair delimiters in the text the compiler will figure out if they are balanced—the opening delimiter comes first and a closing delimiter pairs with it:
但是,如果在文本中对分隔符进行配对,编译器将确定它们是否是平衡的 - 开口分隔符首先出现,并且闭合分隔符与它配对:
「 Del「i」miters 」
The Raku language is a collection of sublanguages, or slangs. Once inside a particular slang the compiler parses your source code by that slang’s rules. The quoting language is one of those slangs. |
If your literal text has corner brackets in it you can use a generalized quoting mechanism. These start with a Q
(or q
) and can get as limiting or as permissive as you like, as you’ll see in this chapter.
After the Q
you can select almost any character to be the delimiter. It can’t be a character valid in a variable name, because that would make it look like a name instead of a delimiter. The paired characters are common; the opening character has to be on the left and its closing partner has to be on the right. Perhaps you want to use square brackets instead of corner brackets. Now the 」
isn’t special because it’s not a delimiter:
Raku 语言是一个子语言或方言的集合。一旦进入特定的方言,编译器就会根据该方言的规则解析你的源代码。引用语言是其中一个方言。 |
如果你的字面文本中包含角括号,则可以使用通用引用机制。这些以 Q
(或 q
)开头,可以像你想的那样得到限制或许可,正如你将在本章中看到的那样。
在 Q
之后,你可以选择几乎任何字符作为分隔符。它不能是变量名中有效的字符,因为这会使它看起来像名称而不是分隔符。配对字符很常见;开口字符必须位于左侧,其闭合字符必须位于右侧。也许你想使用方括号而不是角括号。现在,这并不特别,因为它不是分隔符:
Q[Unpaired 」 Delimiters]
Most of the paired characters act the same:
大多数配对字符的行为相同:
Q{Unpaired 」 Delimiters}
Q<Unpaired 」 Delimiters>
Q<<Unpaired 」 Delimiters>>
Q«Works»
There’s one exception. You can’t have an open parenthesis right after the Q
because that makes it look like a subroutine call (but it’s not):
有一个例外。在 Q
之后你不能有开口圆括号,因为这使它看起来像一个子程序调用(但它不是):
Q(Does not compile)
You don’t have to use paired characters. You can use the same character for the opening and closing delimiter:
你不必使用配对字符。你可以对开口和闭合分隔符使用相同的字符:
Q/hello/
You can store a [Str
](https://docs.raku.org/type/Str.html) in a variable or output it immediately:
my $greeting = Q/Hello World!/;
put Q/Hello World!/;
And you can call methods on your [Str
](https://docs.raku.org/type/Str.html) just like you could do with numbers:
你可以在你的[[字符串
](https://docs.raku.org/type/Str.html)](https://docs.raku.org/type/Str.html)上调用方法,就像你对数字一样:
Q/Hello World!/.^name; # Str
Q/Hello World!/.put;
2.2. Escaped Strings
One step up from literal [Str
](https://docs.raku.org/type/Str.html)s are escaped strings. The single tick acts as the delimiter for these [Str
](https://docs.raku.org/type/Str.html)s. These are often called single-quoted strings:
从字面[[字符串
](https://docs.raku.org/type/Str.html)](字符串
(https://docs.raku.org/type/Str.html)的分隔符。这些通常称为单引号字符串:
% raku
> 'Hamadryas perlicus'
Hamadryas perlicus
If you want to have the single tick as a character in the [Str
](https://docs.raku.org/type/Str.html) you can escape it with a backslash. That tells the quoting slang that the next character isn’t the delimiter but belongs as literal text:
% raku
> 'The escaped \' stays in the string'
The escaped ' stays in the string
Since the \
is the escape character, you can escape it to get a literal backslash:
由于 \
是转义字符,你可以转义它以获得字面反斜杠:
% raku
> 'Escape the \\ backslash'
Escape the \ backslash
A DOS path can be quite annoying to type, but escaped and literal [Str
](https://docs.raku.org/type/Str.html)s take care of that:
DOS 路径可能非常烦人,但是转义和字面[字符串
](https://docs.raku.org/type/Str.html)负责:
% raku
> 'C:\\Documents and Settings\\Annoying\\Path'
C:\Documents and Settings\Annoying\Path
> Q/C:\Documents and Settings\Annoying\Path/
C:\Documents and Settings\Annoying\Path
If you want to use a different delimiter for an escaped string you use the lowercase q
followed by the delimiter that you want (following the same rules as for the literal quoting delimiters):
如果要对转义字符串使用不同的分隔符,请使用小写 q
后跟所需的分隔符(遵循与字面引用分隔符相同的规则):
q{Unpaired ' Delimiters}
q<Unpaired ' Delimiters>
q<<Unpaired ' Delimiters>>
q«Works»
2.2.1. Adverbs for Quoting
Adverbs modify how something works and are a big part of Raku. You’ll see more of these in [Chapter 9](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch09.html#camelia-hashes), but you’ll get a taste for them in this chapter. Adverbs start with a colon followed by letters or numbers.
All of the quoting methods you’ll see in this chapter are modifications of basic literal quoting. You use adverbs to adjust the quoting behavior.
The :q
adverb modifies Q
to become an escaping quote. There must be some whitespace after the adverb, but it’s optional after the Q
:
副词会修改某些东西的工作方式,并且是 Raku 的重要组成部分。你将在第9章中看到更多这些内容,但在本章中你将会对它们有所了解。副词以冒号开头,后跟字母或数字。
你将在本章中看到的所有引用方法都是对基本字面引用的修改。你使用副词来调整引用行为。
:q
副词修改 Q
成为转义引用。在副词之后必须有一些空格,但在 Q
之后它是可选的:
% raku
> Q:q 'This quote \' escapes \\'
This quote ' escapes \
> Q :q 'This quote \' escapes \\'
This quote ' escapes \
This form doesn’t specifically escape the single tick; it escapes the backslash and the delimiter characters. A backslash that doesn’t precede a delimiter or another backslash is interpreted as a literal backslash:
这种形式并没有特别转义单个记号;它转义了反斜杠和分隔符字符。不在分隔符或另一个反斜杠之前的反斜杠被解释为字面反斜杠:
% raku
> Q :q 「This quote \' escapes」
This quote \' escapes
> Q :q 「This quote \「 escapes」
This quote 「 escapes
> Q :q 「This quote \「\」 escapes」
This quote 「 escapes
The :single
adverb is a longer version of :q
and might help you remember what you want:
:single
副词是 :q
的较长版本,可能会帮助你记住你想要的内容:
% raku
> Q :single 'This quote \' escapes'
This quote ' escapes
Most of the time you aren’t going to work this hard. The common uses of quoting have default delimiters so you don’t even see the Q
. Even though many [Str
](https://docs.raku.org/type/Str.html)s would be more correctly represented with strict literal quoting, most people tend to use the single ticks simply because it’s easier to type. No matter which quoting method you use you get the same type of object.
大多数时候你不打算这么努力。引用的常见用法具有默认分隔符,因此你甚至不会看到 Q
.即使使用严格的字面引用更准确地表示许多[字符串
](https://docs.raku.org/type/Str.html),大多数人倾向于使用单个记号,因为它更容易键入。无论使用哪种引用方法,都可以获得相同类型的对象。
2.2.2. String Operators and Methods
Use the concatenation operator, ~
, to combine [Str
](https://docs.raku.org/type/Str.html)s. Some people call this “string addition.” The output shows the two [Str
](https://docs.raku.org/type/Str.html)s as one with nothing else between them:
使用连接运算符 ~
来组合[字符串
](字符串
(https://docs.raku.org/type/Str.html)合为一个,它们之间没有其他内容:
my $name = 'Hamadryas' ~ 'perlicus';
put $name; # Hamadryasperlicus
You could add a space yourself by putting it in one of the [Str
](https://docs.raku.org/type/Str.html)s, but you can also concatenate more than two [Str
](https://docs.raku.org/type/Str.html)s at a time:
你可以在两个[字符串
](字符串
(https://docs.raku.org/type/Str.html):
put 'Hamadryas ' ~ 'perlicus';
put 'Hamadryas' ~ ' ' ~ 'perlicus';
The join
routine glues together [Str
](https://docs.raku.org/type/Str.html)s with the first [Str
](https://docs.raku.org/type/Str.html) you give it:
join
例程将[字符串
](字符串
(https://docs.raku.org/type/Str.html)粘在一起:
my $butterfly-name = join ' ', 'Hamadryas', 'perlicus'
You can make larger [Str
](https://docs.raku.org/type/Str.html)s by repeating a [Str
](https://docs.raku.org/type/Str.html). The x
is the [Str
](https://docs.raku.org/type/Str.html) replication operator. It repeats the [Str
](https://docs.raku.org/type/Str.html) the number of times you specify. This is handy for making a text-based divider or ruler for your output:
你可以通过重复[字符串
](字符串
(https://docs.raku.org/type/Str.html)。x
是[字符串
](字符串
(https://docs.raku.org/type/Str.html)指定的次数。这对于为输出创建基于文本的分隔符或标尺很方便:
put '-' x 70;
put '.123456789' x 7;
The .chars
methods tells you how many characters are in the [Str
](https://docs.raku.org/type/Str.html):
.chars
方法告诉你[字符串
](https://docs.raku.org/type/Str.html)中有多少个字符:
put 'Hamadryas'.chars; # 9
Any [Str
](https://docs.raku.org/type/Str.html) with at least one character is True
as a Boolean, including the [Str
](https://docs.raku.org/type/Str.html) of the single character 0
:
任何具有至少一个字符的[字符串
](https://docs.raku.org/type/Str.html)都是 True
作为布尔值,包括单个字符 0
的[字符串
](https://docs.raku.org/type/Str.html):
put ?'Hamadryas'; # True
put ?'0'; # True
The empty string has no characters. It consists only of the opening delimiter and the closing delimiter. It’s False
as a Boolean:
空字符串没有字符。它仅包含开口分隔符和闭合分隔符。它作为布尔值是假的:
put ''.chars; # 0
put ?''; # False
Be careful that when you test a [Str
](https://docs.raku.org/type/Str.html) you test the right thing. A [Str
](https://docs.raku.org/type/Str.html) type object is also False
, but `.DEFINITE`can tell them apart:
小心,当你测试一个[字符串
](https://docs.raku.org/type/Str.html)你测试正确的东西。 [字符串
](https://docs.raku.org/type/Str.html)类型对象也是 False
,但 .DEFINITE
可以将它们区分开:
put ''.DEFINITE # True
put Str.DEFINITE # False
This is handy in a conditional expression where you don’t care what the [Str
](https://docs.raku.org/type/Str.html) is (empty, '0'
, or anything else) as long as it’s not a type object:
这在条件表达式中很方便,只要它不是类型对象,你不关心[字符串
](https://docs.raku.org/type/Str.html)是什么(空,'0'
或其他任何东西):
given $string {
when .DEFINITE {
put .chars ?? 'Has characters' !! 'Is empty';
}
default { put 'Type object' }
}
The .lc
method changes all the characters in a [Str
](https://docs.raku.org/type/Str.html) to lowercase, and .uc
changes them to uppercase:
.lc
方法将[字符串
](https://docs.raku.org/type/Str.html)中的所有字符更改为小写,.uc
将它们更改为大写:
put 'HaMAdRyAs'.lc; # hamadryas
put 'perlicus'.uc; # PERLICUS
The .tclc
method uses title case, lowercasing everything then capitalizing the first character of the [Str
](https://docs.raku.org/type/Str.html):
.tclc
方法使用标题大小写,小写所有内容然后大写[字符串
](https://docs.raku.org/type/Str.html)的第一个字符:
put 'hamadryas PERLICUS'.tc; # Hamadryas perlicus
EXERCISE 4.1Write a program to report the number of characters in the text you enter.
EXERCISE 4.2Modify the previous exercise to continually prompt for text and report the number of characters in your answers until you provide an empty answer.
练习4.1 编写一个程序来报告你输入的文本中的字符数。
练习4.2 修改上一个练习以不断提示文本并报告答案中的字符数,直到你提供空答案。
2.2.3. Looking Inside Strings
You can also inspect a [Str
](https://docs.raku.org/type/Str.html) to find out things about it. The .contains
method returns a Boolean value indicating whether it finds one [Str
](https://docs.raku.org/type/Str.html)—the substring—inside the target [Str
](https://docs.raku.org/type/Str.html):
你也可以检查一下[字符串
](https://docs.raku.org/type/Str.html)来找出它的相关信息。 .contains
方法返回一个布尔值,指示它是否找到一个[字符串
](字符串
(https://docs.raku.org/type/Str.html)内:
% raku
> 'Hamadryas perlicus'.contains( 'perl' )
True
> 'Hamadryas perlicus'.contains( 'Perl' )
False
Instead of parentheses you can put a colon followed by the substring to search for:
你可以使用冒号后跟子字符串来代替圆括号来搜索:
% raku
> 'Hamadryas perlicus'.contains: 'perl'
True
> 'Hamadryas perlicus'.contains: 'Perl'
False
The .starts-with
and .ends-with
methods do the same thing as .contains
but require the substring to appear at a particular location:
.starts-with
和 .ends-with
方法与 .contains
的作用相同,但要求子字符串出现在特定位置:
> 'Hamadryas perlicus'.starts-with: 'Hama'
True
> 'Hamadryas perlicus'.starts-with: 'hama'
False
> 'Hamadryas perlicus'.ends-with: 'us'
True
These methods are case sensitive. The case of each character in the substring must match the case in the target [Str
](https://docs.raku.org/type/Str.html). If it’s uppercase in the substring it must be uppercase in the target. If you want case insensitivity you canuse .fc
to make a “caseless” [Str
](https://docs.raku.org/type/Str.html). This “case folding” method is especially designed for comparisons:
这些方法区分大小写。子字符串中每个字符的大小写必须与目标[字符串
](https://docs.raku.org/type/Str.html)中的大小写匹配。如果它在子字符串中是大写的,则它在目标中必须为大写。如果你想要不区分大小写,你可以使用 .fc
来制作一个“无大小写”的[字符串
](https://docs.raku.org/type/Str.html)。这种“大小写折叠”方法专门用于比较:
> 'Hamadryas perlicus'.fc.starts-with: 'hama'
False
.fc
also knows about equivalent characters such as the ss and the sharp ß. The method doesn’t change the text; it evaluates to a new [Str
](https://docs.raku.org/type/Str.html) based on a long list of rules about equivalence defined by Unicode. You should case fold both the target and substrings if you want to allow these sorts of variations:
.fc
也知道相等的字符,如 ss 和 sharpß。该方法不会改变文本; 它基于由 Unicode 定义的关于等价的一长串规则列表来计算新的 [字符串
](https://docs.raku.org/type/Str.html)。如果要允许这些变化,你应该折叠目标字符串和子字符串串:
> 'Reichwaldstrasse'.contains: 'straße'
False
> 'Reichwaldstrasse'.fc.contains: 'straße'
False
> 'Reichwaldstrasse'.contains: 'straße'.fc
True
> 'Reichwaldstrasse'.fc.contains: 'straße'.fc
True
.substr
extracts a substring by its starting position and length inside the [Str
](https://docs.raku.org/type/Str.html). The counting starts with zero at the first character:
put 'Hamadryas perlicus'.substr: 10, 4; # perl
The .index
method tells you where it finds a substring inside the larger [Str
](https://docs.raku.org/type/Str.html) (still counting from zero), or returns Nil
if it can’t find the substring:
.index
方法告诉你它在较大的[字符串
](https://docs.raku.org/type/Str.html)内部找到一个子字符串(仍然从零开始计数),或者如果它找不到子字符串则返回 Nil
:
my $i = 'Hamadryas perlicus'.index: 'p';
put $i ?? 'Found at ' ~ $i !! 'Not in string'; # Found at 10
Use both of them together to figure out where to start:
同时使用它俩来确定从哪里开始:
my $s = 'Hamadryas perlicus';
put do given $s.index: 'p' {
when Nil { 'Not found' }
when Int { $s.substr: $_, 4 }
}
EXERCISE 4.3Repeatedly prompt for text and report if it contains the substring “Hamad”. Stop prompting if the answer has no characters (an empty answer). Can you make this work regardless of casing?
练习4.3 如果包含子字符串 “Hamad”,则重复提示文本和报告。如果答案没有字符,则停止提示(空答案)。如果没有大小写,你能做到这一点吗?
2.2.4. Normal Form Grapheme
Raku is Unicode all the way down. It works on graphemes, which most of us think of as “characters” in the everyday sense. These are the full expression of some idea, such as e, é, or ![img](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/assets/butterfly.png). It expects your source code to be UTF-8 encoded and outputs UTF-8 text. All of these work, although they each represent a different language:
Raku 一直是支持 Unicode 的。它适用于字素,我们大多数人都认为它是日常意义上的“字符”。这些是一些想法的完整表达,例如e,é,或 ![img](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/assets/butterfly.png)。它希望你的源代码是 UTF-8 编码并输出 UTF-8 文本。所有这些都有效,虽然它们各自代表不同的语言:
'көпөлөк'
'तितली'
'蝴蝶'
'Con bướm'
'tauriņš'
'πεταλούδα'
'भंबीरा'
'פרפר'
You can use emojis too:
你也可以使用表情符号:
my $string = '';
put $string;
One of the Raku “characters” might be made of up two or more entries in the Universal Character Database (UCD). Raku refers to entries in the UCD as codes and to their composition as a “character.” It’s not the best terminology. In this book, character means grapheme and code point refers to an entry in the UCD.
Why does any of that matter? The .chars
method tells you the length of the [Str
](https://docs.raku.org/type/Str.html) in graphemes. Consider the Hebrew word for “caterpillar.” It has 11 graphemes but 14 code points:
其中一个 Raku “字符”可能由通用字符数据库(UCD)中的两个或多个条目组成。 Raku 将 UCD 中的条目称为代码,将其组成称为“字符”。这不是最好的术语。在本书中,字符表示字素,而代码点表示 UCD 中的条目。
为什么这有关系? .chars
方法告诉你字素中[字符串
](https://docs.raku.org/type/Str.html)的长度。考虑希伯来语中的“caterpillar”一词。它有 11 个字素,但有 14 个代码点:
% raku
> 'קאַטערפּיללאַר'.chars
11
> 'קאַטערפּיללאַר'.codes
14
Why the different counts? There are graphemes such as אַ
that are more than one code point (in that case, the two code points are the Hebrew Aleph and patah diacritical mark). Most of the time you won’t care about this. If you do, you can get a list of the code points with .ords
:
为什么是不同的计数?像 אַ
这样的字素不止一个代码点(在这种情况下,两个代码点是希伯来语Aleph和patah变音符号)。大多数时候你不会关心这个。如果这样做,你可以用 .ords
获得的代码点列表:
> 'קאַטערפּיללאַר'.ords
(1511 1488 1463 1496 1506 1512 1508 1468 1497 1500
1500 1488 1463 1512)
2.2.5. String Comparisons
[Str
](https://docs.raku.org/type/Str.html) objects know if they are relatively greater than, less than, or the same as another [Str
](https://docs.raku.org/type/Str.html). Raku uses lexicographic comparison to go through the [Str
](https://docs.raku.org/type/Str.html)s character by character.
The numbers comparison operators are symbols, but the [Str
](https://docs.raku.org/type/Str.html)s use operators made up of letters. The eq
operator tests if the [Str
](https://docs.raku.org/type/Str.html)s are exactly equal. Case matters. Every character at each position in the [Str
](https://docs.raku.org/type/Str.html) must be exactly the same in each [Str
](https://docs.raku.org/type/Str.html):
[字符串
](https://docs.raku.org/type/Str.html) 对象知道它们是否比另一个[字符串
](https://docs.raku.org/type/Str.html)相对大,小于或相同。 Raku 使用字典比较来逐字逐句地浏览[字符串
](https://docs.raku.org/type/Str.html)。
数字比较运算符是符号,但[字符串
](https://docs.raku.org/type/Str.html)使用由字母组成的运算符。 eq
运算符测试[字符串
](https://docs.raku.org/type/Str.html)是否完全相等。大小写敏感。 [字符串
](字符串
(https://docs.raku.org/type/Str.html)中必须完全相同:
% raku
> 'Hamadryas' eq 'hamadryas'
False
> 'Hamadryas' eq 'Hamadryas'
True
The gt
operator evaluates to True
if the first [Str
](https://docs.raku.org/type/Str.html) is strictly lexicographically greater than the second (ge
allows it to be greater than or equal to the second [Str
](https://docs.raku.org/type/Str.html)). This is not a dictionary comparison, so case matters. The lowercase letters come after the uppercase ones and so are “greater”:
如果第一个[字符串
](https://docs.raku.org/type/Str.html)严格按字典顺序排列大于第二个(ge
允许它大于或等于第二个[字符串
](https://docs.raku.org/type/Str.html)),则`gt`运算符的计算结果为 True
。这不是字典比较,因此大小写敏感。小写字母位于大写字母之后,因此“更大”:
% raku
> 'Hama' gt 'hama'
False
> 'hama' gt 'Hama'
True
The uppercase letters come before the lowercase ones, so any [Str
](https://docs.raku.org/type/Str.html) that starts with a lowercase letter is greater than any [Str
](https://docs.raku.org/type/Str.html) that starts with an uppercase letter:
大写字母位于小写字母之前,因此任何以小写字母开头的[字符串
](字符串
(https://docs.raku.org/type/Str.html):
% raku
> 'alpha' gt 'Omega'
True
> 'α' gt 'Ω'
True
You can get some weird results if you compare numbers as [Str
](https://docs.raku.org/type/Str.html)s. The character 2
is greater than the character 1
, so any [Str
](https://docs.raku.org/type/Str.html) starting with 2
is greater than any [Str
](https://docs.raku.org/type/Str.html) starting with 1
:
如果将数字作为[字符串
](字符串
(字符串
(https://docs.raku.org/type/Str.html):
% raku
> '2' gt '10'
True
The lt
operator evaluates to True
if the first [Str
](https://docs.raku.org/type/Str.html) is lexicographically less than the second (le
allows it to be less than or equal to the second [Str
](https://docs.raku.org/type/Str.html)):
如果第一个[字符串
](https://docs.raku.org/type/Str.html)在字典上小于第二个(le
允许它小于或等于第二个[字符串
](https://docs.raku.org/type/Str.html)),则 lt
运算符求值为 True
:
% raku
> 'Perl 5' lt 'Raku'
True
If you don’t care about their case you can lowercase both sides with .lc
:
如果你不关心他们的大小写你可以使用 .lc
小写双方:
% raku
> 'Hamadryas'.lc eq 'hamadryas'.lc
True
This wouldn’t work for the Reichwaldstrasse example you saw previously. If you wanted to allow for equivalent representations you’d use .fc
:
这对你之前看到的 Reichwaldstrasse 例子不起作用。如果你想允许等效表示,请使用 .fc
:
% raku
> 'Reichwaldstrasse'.lc eq 'Reichwaldstraße'.lc
False
> 'Reichwaldstrasse'.fc eq 'Reichwaldstraße'.fc
True
As with numbers, you can chain the comparisons:
与数字一样,你可以链接比较:
% raku
> 'aardvark' lt 'butterfly' lt 'zebra'
True
2.2.6. Prompting for Input
You’ve already used prompt
for simple things. When you call it your program reads a single line and chops off the newline that you typed. A small modification of the program shows you what sort of type you get back:
你已经将 prompt
用于简单的提示了。当你调用它时,你的程序读取一行并切掉你键入的换行符。对程序进行一些小修改即可显示你获得的类型:
my $answer = prompt( 'What\'s your favorite animal? ' );
put '$answer is type ', $answer.^name;
put 'You chose ', $answer;
When you answer the question you get a [Str
](https://docs.raku.org/type/Str.html):
当你回答这个问题时,你会得到一个[字符串
](https://docs.raku.org/type/Str.html):
% raku prompt.p6
What's your favorite animal? Fox
$answer is type Str
You chose Fox
When you don’t type anything other than a Return the answer is still a [Str
](https://docs.raku.org/type/Str.html), but it’s an empty [Str
](https://docs.raku.org/type/Str.html):
当你没有输入除了换行符之外的任何东西时,答案仍然是一个[字符串
](字符串
(https://docs.raku.org/type/Str.html):
% raku prompt.p6
What's your favorite animal?
$answer is type Str
You chose
You end input with Control-D, which is the same as not typing anything. In that case it returns an [Any
](https://docs.raku.org/type/Any.html) type object. Notice that the line showing the type appears on the same line as the prompt text—you never typed a Return. There’s also a warning about that [Any
](https://docs.raku.org/type/Any.html) value, and finally your last line of output:
你使用 Control-D 结束输入,这与不输入任何内容相同。在这种情况下,它返回一个[Any
](https://docs.raku.org/type/Any.html) 类型的对象。请注意,显示该类型的行与提示文本显示在同一行 - 你从未键入Return。还有关于[Any
](https://docs.raku.org/type/Any.html) 值的警告,最后是你的最后一行输出:
% raku prompt.p6
What's your favorite animal? $answer is type Any
Use of uninitialized value $answer of type Any in string context.
You chose
To guard against this problem you can test $answer
. The [Any
](https://docs.raku.org/type/Any.html) type object is always False
. So is the empty [Str
](https://docs.raku.org/type/Str.html):
为了防止这个问题,你可以测试 $answer
。 [Any
](https://docs.raku.org/type/Any.html) 类型对象始终为 False
。空的[字符串
](https://docs.raku.org/type/Str.html)也是如此:
my $answer = prompt( 'What\'s your favorite animal? ' );
put do
if $answer { 'You chose ' ~ $answer }
else { 'You didn\'t choose anything.' }
prompt
takes whatever you type, including whitespace. If you put some spaces at the beginning and end that’s what shows up in the [Str
](https://docs.raku.org/type/Str.html):
prompt`接受你输入的任何内容,包括空格。如果你在开头和结尾放置一些空格,那就是[`字符串
](https://docs.raku.org/type/Str.html)中出现的空格:
% raku prompt.p6
What's your favorite animal? Butterfly
You chose Butterfly
You can see this better if you put in something to surround the answer portion of the output, such as <>
in this example:
如果你在输出的答案部分放置一些东西,你可以更好地看到这一点,例如本例中的 <>
:
my $answer = prompt( 'What\'s your favorite animal? ' );
put do
if $answer { 'You chose <', $answer, '>' }
else { 'You didn't choose anything' }
Now you can easily see the extra space in $answer
:
现在,你可以轻松地在 $answer
中看到额外的空格:
% raku prompt.p6
What's your favorite animal? Butterfly
You chose < Butterfly >
The .trim
method takes off the surrounding whitespace and gives you back the result:
.trim
方法去掉周围的空格并返回结果:
my $answer = prompt( 'What\'s your favorite animal? ' ).trim;
If you apply it to $answer
by itself it doesn’t work:
如果你将它自己应用于 $answer
那么它不起作用:
$answer.trim;
You need to assign the result to $answer
to get the updated value:
你需要将结果赋值给 $answer
以获取更新后的值:
$answer = $answer.trim;
That requires you to type $answer
twice. However, you know about binary assignment so you can shorten that to use the variable name once:
这要求你输入两次 $answer
。但是,你知道二进制赋值,因此你可以缩短它以使用变量名称一次:
$answer .= trim;
If you don’t want to remove the whitespace from both sides you can use either .trim-leading
or .trim-trailing
for the side that you want.
如果你不想从两侧删除空格,可以使用 .trim-leading
或 .trim-trailing
作为所需的一侧。
2.2.7. Number to String Conversions
You can easily convert numbers to [Str
](https://docs.raku.org/type/Str.html)s with the .Str
method. They may not look like what you started with. These look like number values but they are actually [Str
](https://docs.raku.org/type/Str.html) objects where the digits you see are characters:
你可以使用 .Str
方法轻松地将数字转换为[字符串
](字符串
(https://docs.raku.org/type/Str.html)对象,其中你看到的数字是字符:
% raku
> 4.Str
4
> <4/5>.Str
0.8
> (13+7i).Str
13+7i
The unary prefix version of ~
does the same thing:
~
的一元前缀版本做同样的事情:
% raku
> ~4
4
> ~<4/5>
0.8
> ~(13+7i)
13+7i
If you use a number in a [Str
](https://docs.raku.org/type/Str.html) operation it automatically converts it to its [Str
](https://docs.raku.org/type/Str.html) form:
% raku
> 'Hamadryas ' ~ <4/5>
Hamadryas 0.8
> 'Hamadryas ' ~ 5.5
Hamadryas 5.5
2.2.8. String to Number Conversions
Going from [Str
](https://docs.raku.org/type/Str.html)s to numbers is slightly more complicated. If the [Str
](https://docs.raku.org/type/Str.html) looks like a number you can convert it to some sort of number with the unary prefix version of +
. It converts the [Str
](https://docs.raku.org/type/Str.html) to the number of the narrowest form, which you can check with .^name
:
从[字符串
](字符串
(https://docs.raku.org/type/Str.html)看起来像一个数字,你可以使用一元前缀版本`+将其转换为某种数字。它将[`字符串
](https://docs.raku.org/type/Str.html)转换为最窄形式的数字,你可以查看 ^name
:
% raku
> +'137'
137
> (+'137').^name
Int
> +'1/2'
0.5
> (+'1/2').^name
Rat
This only works for decimal digits. You can have the decimal digits 0 to 9 and a possible decimal point followed by more decimal digits. An underscore is allowed with the same rules as for literal numbers. The conversion ignores surrounding whitespace:
这仅适用于十进制数字。你可以使用小数位数0到9以及可能的小数点后跟更多的十进制数字。允许使用与字面数相同的规则的下划线。转换忽略了周围的空格:
% raku
> +' 1234 '
1234
> +' 1_234 '
1234
> +' 12.34 '
12.34
Anything else, such as two decimal points, causes an error:
其他任何内容,例如两个小数点,都会导致错误:
> +'12.34.56'
Cannot convert string to number: trailing characters after number
When you perform numerical operations on a [Str
](https://docs.raku.org/type/Str.html) it’s automatically converted to a number:
% raku
> '2' + 3
5
> '2' + '4'
6
> '2' ** '8'
256
EXERCISE 4.4Write a program that prompts for two numbers then outputs their sum, difference, product, and quotient. What happens if you enter something that’s not a number? (You don’t need to handle any errors.)
In the previous exercise you should have been able to create a conversion error even though you didn’t have the tools to handle it. If you want to check if a [Str
](https://docs.raku.org/type/Str.html) can convert to a number you can use the val
routine. That gives you an object that does the [Numeric
](https://docs.raku.org/type/Numeric.html) role if it can convert the [Str
](https://docs.raku.org/type/Str.html). Use the smart match operator to check that it worked:
练习4.4 写一个程序,提示输入两个数字,然后输出它们的和,差,乘积和商。如果你输入的不是数字,会发生什么? (你不需要处理任何错误。)
在上一个练习中,即使你没有处理它的工具,你也应该能够创建转换错误。如果要检查[字符串
](https://docs.raku.org/type/Str.html)是否可以转换为数字,可以使用 val
例程。如果它可以转换[字符串
](https://docs.raku.org/type/Str.html),那么它将为你提供一个执行 [Numeric
](https://docs.raku.org/type/Numeric.html) 角色的对象。使用智能匹配运算符检查它是否有效:
my $some-value = prompt( 'Enter any value: ' );
my $candidate = val( $some-value );
put $candidate, ' ', do
if $candidate ~~ Numeric { ' is numeric' }
else { ' is not numeric' }
This seems complicated now because you haven’t read about interpolated [Str
](https://docs.raku.org/type/Str.html)s yet. It will be much clearer by the end of this chapter.
EXERCISE 4.5Update the previous exercise to handle nonnumeric values that would cause a conversion error. If one of the values isn’t numeric, output a message saying so.
Sometimes your text is numeric but not decimal. The .parse-base
method can convert it for you. It takes a [Str
](https://docs.raku.org/type/Str.html) that looks like a nondecimal number and turns it into a number:
现在这看起来很复杂,因为你还没有读过有关插值的[字符串
](https://docs.raku.org/type/Str.html)。到本章结尾将会更清楚。
练习4.5 更新上一个练习以处理可能导致转换错误的非数字值。如果其中一个值不是数字,则输出一条说明的消息。
有时你的文本是数字但不是小数。 .parse-base
方法可以为你转换它。它需要一个看起来像非十进制数字的[字符串
](https://docs.raku.org/type/Str.html)并将其转换为数字:
my $octal = '0755'.parse-base: 8; # 493
my $number = 'IG88'.parse-base: 36; # 860840
This is the same thing the colon form was doing in [Chapter 3](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch03.html#camelia-numbers):
这与第3章中的冒号对形式所做的相同:
:8(0755)
:36<IG88>
2.3. Interpolated Strings
You’ve taken a long path through this chapter to get to the quoting mechanism that you’re likely to use the most. An interpolated string replaces special sequences within the [Str
](https://docs.raku.org/type/Str.html) with other characters. These [Str
](https://docs.raku.org/type/Str.html)s will also make easier some of the code you’ve already seen.
Interpolated [Str
](https://docs.raku.org/type/Str.html)s use the double quote, "
, as the default delimiter and are sometimes called double-quoted strings. You need to escape the "
if you want one in the [Str
](https://docs.raku.org/type/Str.html), and you can escape the \
:
你已经通过本章走了很长的路,以了解你可能最常使用的引用机制。插值字符串用其他字符替换[字符串
](字符串
(https://docs.raku.org/type/Str.html)也会使你已经看过的一些代码变得更容易。
插值[字符串
](https://docs.raku.org/type/Str.html)使用双引号 "
作为默认分隔符,有时也称为双引号字符串。如果你想在[字符串
](https://docs.raku.org/type/Str.html)中使用双引号你需要转义 "
,你也可以转义 \
:
% raku
> "Hamadryas perlicus"
Hamadryas perlicus
> "The escaped \" stays in the string"
The escaped " stays in the string
> "Escape the \\ backslash"
Escape the \ backslash
The backslash also starts other special interpolating sequences. A \t
represents a tab character. A \n
represents a newline:
反斜杠也会启动其他特殊插值序列。 \t
表示制表符。 \n
表示换行符:
put "First line\nSecond line\nThird line";
If you want a character that’s not easy to type you can put its code number (a hexadecimal value) after \x
or inside \x[]
. Don’t use the 0x
prefix; the \x
already assumes that:
如果你想要一个不容易输入的字符,你可以在 \x
之后或在 \x[]
之内输入它的代码号(十六进制值)。不要使用 0x
前缀; \x
已经假定:
put "The snowman is \x[2603]";
Several comma-separated code numbers inside \x[]
turn into multiple characters:
\x[]
内的几个以逗号分隔的代码编号变为多个字符:
put "\x[1F98B, 2665, 1F33B]"; #
If you know the name of the character you can put that inside \c[]
. You don’t quote these names and the names are case insensitive:
如果你知道字符的名称,可以将其放在 \c[]
中。你不引起这些名称,并且名称不区分大小写:
put "\c[BUTTERFLY, BLACK HEART, TACO]"; #
Those are nice, but it’s much more handy to interpolate variables. When a double-quoted [Str
](https://docs.raku.org/type/Str.html) recognizes a sigiled variable name it replaces the variable with its value:
这些很好,但插入变量更方便。当双引号[字符串
](https://docs.raku.org/type/Str.html)识别出一个带符号的变量名时,它用它的值替换变量:
my $name = 'Hamadryas perlicus';
put "The best butterfly is $name";
The quoting slang looks for the longest possible variable name (and not the longest name actually defined). If the text after the variable name looks like it could be a variable name that’s the variable it looks for:
引用方言查找可能的最长变量名称(而不是实际定义的最长名称)。如果变量名后面的文本看起来像是一个变量名,那就是它所寻找的变量:
my $name = 'Hamadryas perlicus';
put "The best butterfly is $name-just saying!";
This is a compile-time error:
这是一个编译时错误:
Variable '$name-just' is not declared
If you need to separate the variable name from the rest of the text in the double-quoted [Str
](https://docs.raku.org/type/Str.html) you can surround the entire variable in braces:
如果你需要将变量名与双引号[字符串
](https://docs.raku.org/type/Str.html)中的其余文本分开,则可以在花括号中包围整个变量:
my $name = 'Hamadryas perlicus';
put "The best butterfly is {$name}-just saying!";
Escape a literal $
where it might look like a sigil that starts a variable name:
转义一个字面 $
,它可能看起来像一个启动变量名称的sigil:
put "I used the variable \$name";
Now here’s the powerful part. You can put any code you like inside the braces. The quoting slang will evaluate the code and replace the braces with the last evaluated expression:
现在这是强大的部分。你可以把任何你喜欢的代码放在花括号内。引用方言将计算代码并用最后计算的表达式替换花括号:
put "The sum of two and two is { 2 + 2 }";
This means that the previous programs in this chapter are much easier to type than they first appear. You can construct the [Str
](https://docs.raku.org/type/Str.html) inside the delimiters rather than using a series of separate [Str
](https://docs.raku.org/type/Str.html)s:
这意味着本章中的先前程序比首次出现时更容易键入。你可以在分隔符内构造[字符串
](字符串
(https://docs.raku.org/type/Str.html):
my $answer = prompt( 'What\'s your favorite animal? ' );
put "\$answer is type {$answer.^name}";
put "You chose $answer";
Like with the previous [Str
](https://docs.raku.org/type/Str.html)s, you can choose a different delimiter for interpolated [Str
](https://docs.raku.org/type/Str.html)s. Use qq
(double q
for double quoting) in front of the delimiter:
与之前的[字符串
](字符串
(https://docs.raku.org/type/Str.html)选择不同的分隔符。在分隔符前面使用 qq
(两个 q
表示双引号):
put qq/\$answer is type {$answer.^name}/;
The \n
is interpolated as a newline and the \t
becomes a tab:
\n
插值为换行符,\t
变为制表符:
put qq/\$answer is:\n\t$answer/;
This [Str
](https://docs.raku.org/type/Str.html) has two lines and the second one is indented:
answer is:
Hamadryas perlicus
qq//
is the same as Q
with the :qq
or :double
adverb:
qq//
与带有 :qq
或 :double
副词的 Q
相同:
put Q :qq /\$answer is type {$answer.^name}/;
put Q :double /\$answer is type {$answer.^name}/;
If you want to interpolate only part of a [Str
](https://docs.raku.org/type/Str.html) you can use \qq[]
for that part:
如果只想插入[字符串
](https://docs.raku.org/type/Str.html)的一部分,可以使用 \qq[]
作为该部分:
my $genus = 'Hamadryas';
put '$genus is \qq[$genus]';
Going the other way, you can turn off interpolation for part of a [Str
](https://docs.raku.org/type/Str.html) by making that part act like a single-quoted [Str
](https://docs.raku.org/type/Str.html) with \q[]
:
换句话说,你可以通过使该部分与 \q[]
的单引号[字符串
](字符串
(https://docs.raku.org/type/Str.html)的插值:
put "\q[$genus] is $genus";
[Table 4-1](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch04.html#camelia-strings-TABLE-backslashes) shows many other special sequences available inside a double-quoted context.
Escape sequence |
Description |
|
The ASCII bell character |
|
Backspace |
|
Carriage return |
|
Newline |
|
Tab |
|
Form feed |
|
Character by name |
|
Single quote the part inside the brackets |
|
Double quote the part inside the brackets |
|
Character by code number in hex |
EXERCISE 4.6Modify your character-counting program to show the [Str
](https://docs.raku.org/type/Str.html) as well as the number of characters it counts. For example, 'Hamadryas' has 10 characters
. You should be able to output a single interpolated [Str
](https://docs.raku.org/type/Str.html).
练习4.6 修改你的字符计数程序,以显示[字符串
](https://docs.raku.org/type/Str.html)以及它所计算的字符数。例如,'Hamadryas' 有10个字符。你应该能够输出单个插值的[字符串
](https://docs.raku.org/type/Str.html)。
2.4. Here Docs
For multiline quoting you could use the quoting you’ve seen so far, but every character between those delimiters matters. This often results in ugly outdenting:
对于多行引用,你可以使用到目前为止看到的引用,但这些分隔符之间的每个字符都很重要。这通常导致丑陋的外观:
my $multi-line = '
Hamadryas perlicus: 19
Vanessa atalanta: 17
Nymphalis antiopa: 0
';
Interpolating \n
doesn’t make it any prettier:
插入换行符 \n
不会使它更漂亮:
my $multi-line = "Hamadryas perlicus: 19\n...";
A here doc is a special way of quoting a multiline text. Specify a delimiter with the :heredoc
adverb. The[Str
](https://docs.raku.org/type/Str.html) ends when the slang finds that same [Str
](https://docs.raku.org/type/Str.html) on a line by itself:
here doc 是一种引用多行文本的特殊方式。使用 :heredoc
副词指定分隔符。当该方言在一条线上找到相同的[字符串
](字符串
(https://docs.raku.org/type/Str.html)结束:
my $multi-line = q :heredoc/END/;
Hamadryas perlicus: 19
Vanessa atalanta: 17
Nymphalis antiopa: 0
END
put $multi-line;
This also strips the same indentation it finds before the closing delimiter. The output ends up with no indention even though it had it in the literal code:
这也剥离了它在结束分隔符之前找到的相同缩进。输出最终没有缩进,即使它在字面代码中有缩进:
Hamadryas perlicus: 19
Vanessa atalanta: 17
Nymphalis antiopa: 0
The :to
adverb does the same thing as :heredoc
:
:to
副词与 :heredoc
副词的作用相同:
my $multi-line = q :to<HERE>;
Hamadryas perlicus: 19
Vanessa atalanta: 17
Nymphalis antiopa: 0
HERE
This works with the other quoting forms too:
这与其他引用形式一起使用也有效:
put Q :to/END/;
These are't special: $ \
END
put qq :to/END/;
The genus is $genus
END
2.5. Shell Strings
Shell strings are the same sort of quoting that you’ve seen so far, but they don’t construct a [Str
](https://docs.raku.org/type/Str.html) to store in your program. They create an external command to run in the shell. A shell string captures the command’s output and gives it to you. [Chapter 19](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch19.html#camelia-interprocess) covers this, but here’s something to get you started.
qx
uses the same rules as escaped [Str
](https://docs.raku.org/type/Str.html)s. The hostname command works on both Unix and Windows systems:
Shell 字符串与你到目前为止看到的引用相同,但它们不构造要存储在程序中的[字符串
](https://docs.raku.org/type/Str.html)。它们创建一个外部命令以在 shell 中运行。 shell 字符串捕获命令的输出并将其提供给你。第19章介绍了这一点,但这里有一些东西可以帮助你入门。
qx
使用与转义的[字符串
](https://docs.raku.org/type/Str.html)相同的规则。 hostname 命令适用于 Unix 和 Windows 系统:
my $uname = qx/hostname/;
put "The hostname is $uname";
put "The hostname is { qx/hostname/ }"; # quoting inside quoting
In this output there’s a blank line between the lines because it includes the newline in the normal command output:
在此输出中,行之间有一个空行,因为它包含正常命令输出中的换行符:
The hostname is hamadryas.local
The hostname is hamadryas.local
Use .chomp
to fix that. If there’s a newline on the end of the text it removes it (although put
adds its own):
使用 .chomp
来解决这个问题。如果文本末尾有换行符,则删除它(尽管 put
添加了自己的换行符):
my $uname = qx/hostname/.chomp;
put "The hostname is $uname";
put "The hostname is { qx/hostname/.chomp }";
print
doesn’t add a newline for you, so you don’t need to remove the one from the command output:
`print`不会为你添加换行符,因此你无需从命令输出中删除该换行符:
print "The hostname is { qx/hostname/ }";
qx
and qqx
are shortcuts for single and double quoting [Str
](https://docs.raku.org/type/Str.html)s with the :x
or :exec
adverbs:
qx
和 qqx
是带有 :x
或 :exec
副词的单引号和双引号[字符串
](https://docs.raku.org/type/Str.html)的快捷方式:
print Q :q :x /hostname/;
print Q :q :exec /hostname/;
print Q :single :exec /hostname/;
2.5.1. Shell Safety
In the previous examples, the shell looks through its PATH
environment variable to find the hostname command and executes the first one that it finds. Since people can set their PATH
(or something can set it for them), you might not get the command you expect. If you use an absolute path you don’t have this problem. Literal quoting is handy to avoid inadvertent escaping:
在前面的示例中,shell 查看其 PATH
环境变量以查找 hostname 命令并执行它找到的第一个命令。由于人们可以设置他们的 PATH
(或者某些东西可以为他们设置),你可能无法得到你期望的命令。如果使用绝对路径,则不会出现此问题。字面引用可以避免无意中的转义:
put Q :x '/bin/hostname';
put Q :x 'C:\Windows\System32\hostname.exe'
I won’t cover secure programming techniques here, but I do write more about these problems in Mastering Perl. Although that’s a Perl 5 book, the risks to your program are the same. |
Although you have not seen hashes yet ([Chapter 9](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch09.html#camelia-hashes)), you could change the environment for your program. If you set PATH
to the empty [Str
](https://docs.raku.org/type/Str.html) your program won’t be able to search for any programs:
我不会在这里介绍安全编程技术,但我在 Mastering Perl 中写了更多关于这些问题的内容。虽然这是一本 Perl 5书,但你的程序风险是一样的。
虽然你还没有看到哈希(第9章),但你可以更改程序的环境。如果将 PATH
设置为空[字符串
](https://docs.raku.org/type/Str.html),则程序将无法搜索任何程序:
%*ENV<PATH> = '';
print Q :x 'hostname'; # does not find this
print Q :x '/bin/hostname'; # this works
If that’s too restrictive you can set the PATH
to exactly the directories that you consider safe:
如果限制太多,你可以将 PATH
设置为你认为安全的目录:
%*ENV<PATH> = '/bin:/sbin';
print Q :x 'hostname'; # does not find this
print Q :x '/bin/hostname'; # this works
There’s also a double-quoted form of shell [Str
](https://docs.raku.org/type/Str.html)s:
还有一个双引号形式的shell [字符串
](https://docs.raku.org/type/Str.html):
my $new-date-string = '...';
my $output = qqx/date $new-date-string/
What’s in that $new-date-string
? If it descends from user data, external configuration, or something else that you don’t control, you might be in for a surprise. That could be malicious or merely accidental, so be careful:
那个 $new-date-string
中有什么?如果它来自用户数据,外部配置或你无法控制的其他内容,你可能会感到惊讶。这可能是恶意的或仅仅是偶然的,所以要小心:
my $new-date-string = '; /bin/rm -rf';
my $output = qqx/date $new-date-string/
EXERCISE 4.7Write a program to capture the output of hostname. Make it work on both Windows and Unix systems. $*DISTRO.is-win
is True
if you are on Windows and False
otherwise.
练习4.7 编写一个程序来捕获主机名的输出。使其适用于 Windows 和 Unix 系统。如果你在 Windows上,$* DISTRO.is-win
为 True
,否则为 False
。
2.6. Fancier Quoting
You can combine adverbs in generalized quoting to use just the features that you need. Suppose that you want to interpolate only things in braces but nothing else. You can use the :c
adverb:
你可以在通用引用中组合副词,以仅使用所需的功能。假设你只想在花括号中插入内容而不是其他内容。你可以使用 :c
副词:
% raku
> Q :c "The \r and \n stay, but 2 + 2 = { 2 + 2 }"
The \r and \n stay, but 2 + 2 = 4
To get only variable interpolation use the :s
adverb. No other processing happens:
要只获得变量插值,请使用 :s
副词。没有其他处理发生:
% raku
> my $name = 'Hamadryas'
Hamadryas
> Q :s "\r \n { 2 + 2 } $name"
\r \n { 2 + 2 } Hamadryas
You can combine adverbs to get any mix of features that you like. Cluster the adverbs or space them out. They work the same either way:
你可以组合副词来获得你喜欢的任何功能组合。聚集副词或将它们分开。他们的工作方式相同:
% raku
> Q :s:c "\r \n { 2 + 2 } $name"
\r \n 4 Hamadryas
> Q :s:c:b "\r \n { 2 + 2 } $name"
4 Hamadryas
> Q :s :c :b "\r \n { 2 + 2 } $name"
4 Hamadryas
The :qq
adverb is actually the combination of :s :a :h :f :c :b
. This interpolates all of the variables, the stuff in braces, and all backslash sequences. If you don’t want to interpolate everything, you can turn off an adverb. This might be easier than specifying several just to leave one out. Put a !
in front of the one to disable. :!c
turns off brace interpolation:
:qq
副词实际上是 :s :a :h :f :c :b
的组合。这会插入所有变量、花括号中的内容以及所有反斜杠序列。如果你不想插入所有内容,可以关闭副词。这可能比指定几个更简单,只留下一个。放一个 !
在需要禁用的副词前面。 :!c
关闭花括号插值:
qq :!c /No { 2+2 } interpolation/;
Selected quoting forms and adverbs are summarized in [Table 4-2](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch04.html#camelia-strings-TABLE-summary) and [Table 4-3](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch04.html#camelia-strings-TABLE-quoting_adverbs).
表4-2和表4-3总结了选定的引用形式和副词。
Short name |
Long name |
Description |
|
Literal |
Default delimiter, corner brackets |
|
Literal |
Generalized quoting with alternate delimiter |
|
Literal |
Generalized quoting with paired delimiter |
|
Escaped |
Default delimiter, single quote |
|
Escaped |
Use alternate paired delimiter |
|
Escaped |
Generalized quoting with |
|
Interpolated |
Default delimiter, double quote |
|
Interpolated |
Use alternate paired delimiter |
|
Interpolated |
Generalized quoting with |
|
Interpolated |
Generalized quoting only interpolating closures |
|
Literal |
Here doc |
|
Escaped |
Here doc |
|
Interpolated |
Here doc |
Short name |
Long name |
Description |
|
|
Execute shell command and return results |
|
|
Interpolate |
|
|
Interpolate with |
|
|
Interpolate |
|
|
Interpolate |
|
|
Interpolate |
|
|
Interpolate |
|
|
Interpolate code in |
|
|
Interpolate |
|
|
Parse result as here doc terminator |
|
|
Convert to allomorph if possible |
2.7. Summary
The quoting slang offers several ways to represent and combine text, so you can get exactly what you need in an easy fashion. Once you have the text, you have many options for looking inside the [Str
](https://docs.raku.org/type/Str.html) to find or extract parts of it. This is still early in the book, though. You’ll see more features along the way and then really have fun in [Chapter 15](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch15.html#camelia-regex1).
引用方言提供了几种表示和组合文本的方法,因此你可以轻松地获得所需的内容。获得文本后,你可以在[字符串
](https://docs.raku.org/type/Str.html)内部查找或提取部分内容。不过,这仍然是本书的早期版本。在第15章中,你将看到更多功能,变得真正有趣。
== 创建块
Blocks are the thingys that group multiple statements into a single thingy. You’ve already used some of them, based on the faith I asked you to have in the introduction. Now it’s time to look at those more closely. This chapter covers the basics and works up to simple subroutines. You’ll see just enough here to get you through the next cou‐ ple of chapters, then quite a bit more in Chapter 11.
2.8. 存储 Blocks
你可以把 Block 存储在一个变量中而不立即执行它。now 是一个内置项, 它能够给你一个 Instant。使用 := 进行绑定让右侧和左侧一样。这意味着 $block 和 Block 相同:
my $block := { now };
你不能对 $block 赋值, 因为没有涉及到容器。
你本没必要绑定到 Block 的。赋值也是可以的, 并且你可以在后面更改值:
my $block = { now };
$block = 'Hamadryas';
这不是那么有趣, 因为你可以在你能使用 now 的任何地方使用它。但是计算一个 1 分钟之后的时间的 Block 怎么样?给 now 加上 60 秒:
my $minute-later := { now + 60 };
当你执行 Block 的时候, 它的结果是最后一个被求值的表达式的值。使用 () 操作符执行 Block:
put $minute-later(); # some Instant
sleep 2;
put $minute-later(); # some Instant 62 seconds later
因为 $block 是一个对象, 你可以像方法那样调用 ():
put $minute-later.(); # some Instant
sleep 2;
put $minute-later.(); # some Instant 62 seconds later
你可以使用 callable 变量来代替标量变量;下面这些使用 & 符号:
my &hour-later := { now + 3_600 };
put &hour-later(); # some Instant
sleep 2;
put &hour-later(); # some Instant an hour later
使用 &block 形式你可以在调用的时候不带 & 符号, 甚至不带圆括号:
my &hour-ago := { now - 3_600 };
put &hour-ago(); # some Instant
sleep 2;
put hour-later(); # some Instant two seconds later
put hour-ago; # some Instant immediately
任何一种方式, Block 都不是一个子例程(即将到来的),所以你不能使用 return(稍后将详细介绍)。它不知道怎么将结果传递给调用它的代码。下面这段代码即使不起作用,它也会编译:
my $block := -> { return now };
你会得到一个运行时错误,你会在第十一章了解更多关于它的:
在任何例程外面尝试返回
2.8.1. 带参数的 Blocks
签名定义了 Block 的参数。这包含了你给予 Block 在参数上的个数(参数数量),类型和约束。
如果 Block 没有签名那么它期望零个参数。然而,如果你在 Block 中使用 $_,那么它创建了一个带有单个可选参数的签名:
my $one-arg := { put "The argument was $_" };
$one-arg(); # The argument was (with warning!)
$one-arg(5); # The argument was 5
$one-arg('Hamadryas'); # The argument was Hamadryas
如果你更改那个 $_,那么你就更改了原始值(如果它是可变的)因为隐式的签名让那个参数可写:
my $one-arg := {
put "The argument is $_";
$_ = 5;
};
my $var = 'Hamadryas';
say "\$var starts as $var";
$one-arg($var);
say "\$var is now $var";
输出显示了 Block 更改了变量的值:
$var starts as Hamadryas
The argument is Hamadryas
$var is now 5
如果你在 Block 中使用 @_,那么你可以传递零个或多个参数:
my $many-args := {
put "The argument are @_[]";
}
$many-args( 'Hamadryas', 'perlicus' );
其中 @_ 是一个 Array,但是你必须等到下一章才能看到那些能做些什么。
练习 5.3
创建一个移除尾部空白并小写化它的参数的 Block。原始值可能更改。当你正态化数据的时候你可能想使用这些东西。
隐式参数
Blocks 变得更漂亮。你可以在 Block 里面使用占位符变量(或隐式参数)来指定你需要多少个参数:
my $adding-block := { $^a + $^b }
^ 表示一个占位符变量,它告诉编译器为 Block 构建一个隐式签名。你的 Blocks 拥有和占位符值同样多的参数个数并且你必须为每一个参数提供一个实参:
my $adding-block := { $^a + $^b };
$adding-block(); # Nope - too few parameters
$adding-block( 1 ); # Nope - too few still
$adding-block( 1, 37 ); # Just right!
$adding-block( 1, 2, 3 ); # Nope - too many parameters
参数是根据它们名字的字典顺序而不是你使用他们的顺序赋值给占位符变量的。下面的这些 Blocks 把两个数相除;不同之处在于它们使用占位符变量的顺序:
my $forward-division := { $^a / $^b };
my $backward-division := { $^b / $^a };
你可以用同样的参数以同样的顺序来调用它们。即使你使用相同的占位符名并且传递相同的参数,但是你得到不同的答案:
put $forward-division( 2, 3 ); # 0.66667
put $backward-division( 2 ,3 ); # 1.5
你可以重用同一个占位符变量而不需要创建额外的参数。下面这个仍然是一个参数并且这个参数和自身相乘:
my $square := { $^a * $^a }
调用 .signature 会给你那个 Block 的 Signature 对象。使用 say 输出它会给你一个 .gist 表示:
my $square := { $^a * $^a }
say $square.signature; # ($a)
练习 5.4
创建一个使用三个占位符变量的 Block 并计算三个数中的最大的数。 max 例程能帮到你。使用不同的参数运行这个 Block。
显式签名
尖尖的箭头是签名的开始,在签名里面你可以指定你的参数。→ 和 { 之间什么都没有的话,那么你的签名拥有零个参数:
my $block := -> { put "You called this block"; };
当你调用这个 Block 的时候你不必指定参数:
put $block(); # No argument, so it works
put $block( 2 ); # Error - too many parameters
在 → 和 { 之间定义形参:
my $block := -> $a { put "You called this block with $a"; };
签名中形参的顺序决定了实参填充的顺序。如果 $b 是第一个形参,那么它就获取第一个实参。它们的词典顺序不影响:
my $block := -> $b, $a { $a / $b };
put $block( 2, 3); # 1.5
put $block( 3, 2); # 0.666667
这些参数是位置参数。还有另外一种形式的参数,其中你能指定哪个行参得到哪个实参。这些是命名参数:
my $block := -> :$b, :$a { $a / $b };
put $block( b => 3, a => 2 ); # 0.666667
put $block( a => 3, b => 2 ); # 1.5
你会在第十一章看到更多关于签名的东西,但是这些已经足够你入门了。
类型约束
形参变量可以约束它们允许的类型。下面这个 Block 数值上进行俩个值相除但是它不强制你给它传数字:
my $block := -> $b, $a { $a / $b };
$block( 1, 2 );
$block( 'Hamadryas', 'perlicus' );
第二个调用失败了:
Cannot convert string to number: base-10 number must begin with valid digits …
它在 Block 里面失败了。它根本就没到达代码里面。如果你正在做数值操作你应该只允许数字:
my $block := -> Numeric $b, Numeric $a { $a / $b };
put $block( 1, 2 );
put $block( 'Hamadryas', 'perlicus' );
第一个调用有效但是第二个调用尝试使用 Str 但是失败了:
2 Type check failed in binding to parameter '$b'; expected Numeric but got Str ("Hamadryas")
如果 Numeric 类型对你来说太宽了,选择另一种类型:
my $block := -> Int $b, Int $a { $a / $b };
这仍然有个问题,尽管。那个 Int 约束允许任何智能匹配为 Int 的东西。 Int 类型对象满足这个约束:
$block( Int, 3 ); # 调用仍旧有效
这让它通过了形参守卫然后在除法里面失败了。在类型的后面添加一个 :D 来约束参数为一个有定义的值。类型对象总是未定义的:
my $block := -> Int:D $b, Int:D $a { $a / $b };
你会在第十一章看到更多关于签名的信息。
2.9. 简单子例程
子例程是带有额外功能的代码块。代替尖尖的箭头,这里你使用 sub:
my $subroutine := sub { put "Called subroutine!" };
你以同样的方式执行它:
$subroutine();
子例程可以返回值(Block 不能)。调用 Sub 会计算一些值并在所调用的作用域里让你可见。
之前的 Blocks 处理了输出。从子例程里处理输出通常不是一种好形式,因为它做了两份工作:计算值然后输出。它不够灵活因为它决定了怎么处理值。返回值让你稍后决定:
my $subroutine := sub { return "Called subroutine!" };
put $subroutine();
你应该保存结果而不是输出结果:
my $result = $subroutine();
return 退出最里层的例程(Sub 的超类)。如果一个 Block 在某种 Routine 之内, 你可以在那个 Block 里面使用一个 return,然后你立即执行该 Block。这会立即结束该子例程:
my $subroutine := sub {
-> { # not a sub!
return "Called subroutine!"
}.(); # 立即执行
put 'This is unreachable and will never run';
};
put $subroutine(); # Called subroutine!
你很可能在使用 Block 的东西上使用它,例如 if 结构。所有的这些 Blocks 都能使用 return,因为它们在 Routine 之内,它知道如何处理它:
my $subroutine := sub {
if now.Int %% 2 { return 'Even' }
else { return 'Odd' }
};
put $subroutine();
do if 只需要一个 return:
my $subroutine := sub {
return do if now.Int %% 2 { 'Even' }
else { 'Odd' }
};
put $subroutine();
2.9.1. 命名子例程
子例程可以拥有名字。在 sub 后面指定名字。然后你可以通过子例程的名字执行子例程,和通过它的变量执行一样。它们做同样的事情因为它们实际上是同样的东西:
my $subroutine := sub show-me { return "Called subroutine!" };
put $subroutine.(); # Called subroutine!
put show-me(); # Called subroutine!
通常你会把变量也一块儿跳过:
sub show-me { return "Called subroutine!" };
put show-me(); # Called subroutine!
要定义子例程的签名,把签名放在子例程名字后面的圆括号中(这和 Block 稍微有点不同):
sub divide ( Int:D $a, Int:D $b ) { $a / $b }
put divide( 5, 7 ); # 0.714286
如果它不会使解析器产生歧义,你可以省略掉圆括号。这是同样的东西:
put divide 5, 7; # 714286
这个子例程定义是一个表达式,就像 Block 那样。如果你在闭合花括号之后还有除了末尾空格之外的其它东西,则需要分号:
sub divide ( Int:D $a, Int:D $b ) { $a / $b }; put divide( 5, 7 );
子例程默认是词法作用域的。如果你在 Block 里面定义一个子例程,那么它只存在于 Block 里面。外部作用域不知道 divide 的存在,所以这是错误的:
{
sub divide ( Int:D $a, Int:D $b ) { $a / $b }
put divide( 5, 7 );
}
put divide( 3, 2 ); # Error!
这和词法变量名拥有同样的优点:你不必知道所有其它的子例程来定义你自己的。这也意味着如果你有一个想要临时替代的子例程,你可以在你需要的作用域里创建你自己的版本:
sub divide ( Int:D $a, Int:D $b ) { $a / $b }
put divide 1, 137;
{ # a scope for the fixed version of divide
sub divide ( Numeric $b, Numeric $a ) {
put "Calling my private divide!";
$a / $b
}
put divide 1.1, 137.003;
}
2.10. Whatever Code
这一章的目的是为了接触这个遍及语言的有趣的特性。在下面几章你会需要这个特性。
Whatever
, 也就是 *,是某个东西的替身,你会在之后填充它。填充到它里面的东西决定了它应该是什么。这儿来看看它在表达式中长什么样,让某个东西加上 2:
my $sum = * + 2;
你知道这儿的 * 不是用于乘法,因为乘法需要两个操作数。所以发生了什么?编译器认出了 * 并创建了一个 WhateverCode(也叫做形实转换程序)。它是一个没有定义自己的作用域的代码段,但是不被立即执行。它最像带有一个参数的 Block:
my $sum := { $^a + 2 }
调用带有一个参数的 WhateverCode 来获取最后的值:
$sum = * + 2;
put $sum( 135 ); # 137
获取你想要两个参数。你可以使用两个 * 并且你的 WhateverCode 会接受两个参数:
my $sum = * + *;
put $sum( 135, 2 ); # 137
Whatever * 出现在很多其它有趣的结构中;这是你为什么这么早阅读本书中关于子例程的东西的原因。现在就有两个有趣的用途。
2.10.1. Subsets
WhateverCode 允许你把代码插入到语句中。你可以用它们来创建更有趣的带有 subset 和 where 的类型。首先定义一个不带约束的新类型。你告诉 subset 你想以哪个已存在的类型开始。下面创建一个和 Int 同样的类型:
subset PositiveInt of Int;
my PositiveInt $x = -5;
put $x;
这在运行时检查赋值。你放到 $x 中的类型必须是一个 PositiveInt,但是(目前为止)它和 Int 一样。 -5 是一个 Int 数, 所以这正常工作。
现在通过指定一个带有代码块儿的 where 子句来约束 Int 的合法值:
subset PositiveInt of Int where { $^a > 0 };
my PositiveInt $x = -5;
put $x;
当你给 $x 赋值时你会触发运行时类型检查。变量 $x 知道它必须是一个 PositiveInt。它接受一个可能适合 PositiveInt 的值并把它传入 where 子句中的 Block。如果那段代码计算为 True,那么变接受那个值。如果计算结果为 False,你会得到一个错误:
Type check failed in assignment to $x; expected PositiveInt but got Int (-5)
Whatever 允许你省略一些打字。* 会为你做大部分工作。它代表你想要测试的东西。这些不是完整的类型但是表现得像它们本来的那样:
subset PositiveInt of Int where * > 0;
my PositiveInt $x = -5;
put $x;
一旦你有了 subset,你就能在签名中使用它了。如果参数不是正整数你得到一个运行时错误:
subset PositiveInt of Int where * > 0;
sub add-numbers ( PositiveInt $n, PositiveInt $m ) {
$n + $m
}
put add-numbers 5, 11; # 16
put add-numbers -5, 11; # Error
你不需要定义一个显式的 subset,尽管。你可以在签名中使用 where。当你只需要约束一次时这很有用:
sub add-numbers ( $n where * > 0, $m where * > 0 ) {
$n + $m
}
随着你继续你会看到更多的 subsets;不用很多代码来限制值,它们很方便。你还没有读到关于模块的东西,但是 Subset::Common 有几个例子,你可能会决定不错。
练习 5.5
使用 subset 创建一个不允许分母为零的 divide 子例程。
2.11. 总结
这一章提供了子例程简明介绍和像代码那样的东西。有简单的 Block 以组织代码并定义作用域,还有更复杂的子例程,知道怎么样回传值给它的调用者。它们中的每一个都有复杂的方式来处理参数。这一章让你了解足够的细节,所以你可以在下面几章中使用它们。你会在第十一章看到更强大的子例程特性。
3. Positionals
Your programming career is likely to be, at its heart, about moving and transforming ordered lists of some kind. Those might be to-do lists, shopping lists, lists of web pages, or just about anything else.
The broad term for such as list is [Positional
](https://docs.raku.org/type/Positional.html). Not everything in this chapter is strictly one of those; it’s okay to pretend that they are, though. The language easily interchanges among many of the types you’ll see in this chapter, and it’s sometimes important to keep them straight. Mind their differences and their different uses to get exactly the behavior you want.
This is the first chapter where you’ll experience the laziness of the language. Instead of computing things immediately as you specify them in your code, your program will remember it needs to do something. It then only does it if you later use it. This feature allows you to have infinite lists and sequences without actually creating them.
从本质上讲,你的编程职业可能是移动和转换某种有序列表。这些可能是待办事项列表,购物清单,网页列表或其他任何内容。
列表的广义术语是 [Positional
](https://docs.raku.org/type/Positional.html)。并非本章中的所有内容都是其中之一;但是可以假装他们是。Raku 很容易在本章中看到的许多类型之间进行交换,有时候保持它们是正确的。注意他们的差异和他们的不同用途,以获得你想要的行为。
这是你将体验这门语言懒惰的第一章。在你的代码中指定它们时,你的程序将记住它需要做某事,而不是立即计算事物。它只会在你以后使用它时才会这样做。此功能允许你拥有无限的列表和序列,而无需实际创建它们。
3.1. Constructing a List
A [List
](https://docs.raku.org/type/List.html) is an immutable series of zero or more items. The simplest [List
](https://docs.raku.org/type/List.html) is the empty list. You can construct one with no arguments. The [List
](https://docs.raku.org/type/List.html) as a whole is one thingy and you can store it in a scalar:
[List
](https://docs.raku.org/type/List.html) 是零个或多个项目的不可变系列。最简单的 [List
](https://docs.raku.org/type/List.html) 是空列表。你可以构造一个没有参数的列表。你可以将列表作为一个整体存储在标量中:
my $empty-list = List.new;
put 'Elements: ', $empty-list.elems; # Elements: 0
The .elems
method returns the number of elements, which is 0
for the empty [List
](https://docs.raku.org/type/List.html). This might seem like a trivial result, but imagine those cases where you want to return no results: an empty [List
](https://docs.raku.org/type/List.html) can be just as meaningful as a nonempty one.
Instead of the call to .new
, you can use empty parentheses to do the same thing. Normally parentheses simply group items, but this is special syntax:
.elems
方法返回元素的个数,对于空列表,它返回 0
。可能这看起来像一个微不足道的结果,但想象一下你想要返回没有结果的那些情况:一个空的列表可以和非空列表一样有意义。
你可以使用空括号来执行相同的操作,而不是调用 .new
。通常括号只是对项目进行分组,但这是特殊的语法:
my $empty-list = (); # Also the empty List
There’s also a special object for that. Empty
clearly shows your intent:
还有一个特殊的对象。Empty
显示你的意图:
my $empty-list = Empty;
You can specify elements in .new
by separating the elements with commas. Both the colon and parentheses forms work:
你可以通过用逗号分隔元素来指定 .new
中的元素。冒号和括号形式都有用:
my $butterfly-genus = List.new:
'Hamadryas', 'Sostrata', 'Junonia';
my $butterfly-genus = List.new(
'Hamadryas', 'Sostrata', 'Junonia'
);
You cannot make an empty [List ](https://docs.raku.org/type/List.html) with $() : that’s just Nil .
|
The $(…)
with a list inside also constructs a [List
](https://docs.raku.org/type/List.html). The $
indicates that it is an item. This one happens to be a [List
](https://docs.raku.org/type/List.html) object. You can check the number of elements in it with .elems
:
你不能用 $()
创建一个空列表:那只是 Nil
。
带有列表的 $(…)
也构造了一个列表。 $
表示它是一个项。这恰好是一个 [List
](https://docs.raku.org/type/List.html) 对象。你可以使用 .elems
检查其中的元素数量:
my $butterfly-genus = $('Hamadryas', 'Sostrata', 'Junonia');
put $butterfly-genus.elems; # 3
Or you can leave off the $
in front of the parentheses. You still need the grouping parentheses because item assignment is higher precedence than the comma:
或者你可以去掉括号前留下的 $
。你仍然需要分组括号,因为项赋值的优先级高于逗号:
my $butterfly-genus = ('Hamadryas', 'Sostrata', 'Junonia');
put $butterfly-genus.elems; # 3
A container can be an element in a [List
](https://docs.raku.org/type/List.html). When you change the value in the container it looks like the [List
](https://docs.raku.org/type/List.html)changes, but it doesn’t actually change because the container is the [List
](https://docs.raku.org/type/List.html) item and that container itself was still the [List
](https://docs.raku.org/type/List.html) item:
列表中的元素可以是容器。当你更改容器中的值时,看起来像是列表更改了,但它实际上没有更改,因为容器是列表项,并且该容器本身仍然是列表项:
my $name = 'Hamadryas perlicus';
my $butterflies = ( $name, 'Sostrata', 'Junonia' );
put $butterflies; # (Hamadryas perlicus Sostrata Junonia)
$name = 'Hamadryas';
put $butterflies; # (Hamadryas Sostrata Junonia)
You don’t need the named variable, though. You can use an anonymous scalar container as a placeholder that you’ll fill in later. Since it has no value (or even a type), it’s an [Any
](https://docs.raku.org/type/Any.html) type object:
但是,你不需要命名变量。你可以使用匿名标量容器作为占位符,稍后你将填写。由于它没有值(甚至是类型),因此它是一个Any类型的对象:
my $butterflies = ( $, 'Sostrata', 'Junonia' );
put $butterflies; # ((Any) Sostrata Junonia)
All of this quoting and comma separating is a bit tedious, but there’s a shortcut. You can quote a list with qw
. It creates items by breaking the text apart by whitespace. This makes a three-element [List
](https://docs.raku.org/type/List.html):
所有这些引用和逗号分离有点单调乏味,但有一条捷径。你可以用 qw
引起列表。它通过用空格分隔文本来创建项目。这使得三元素列表:
my $butterfly-genus = qw<Hamadryas Sostrata Junonia>;
put 'Elements: ', $butterfly-genus.elems; # Elements: 3
qw
is another form of the generalized quoting you saw in [Chapter 4](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch04.html#camelia-strings). It uses the :w
adverb and returns a [List
](https://docs.raku.org/type/List.html). You won’t see this form much, but it’s what you’re doing here:
qw`是你在第4章中看到的另一种形式的广义引用。它使用 `:w
副词并返回一个[列表
](https://docs.raku.org/type/List.html)。你不会看到这个形式太多,但这是你在这里做的:
my $butterfly-genus = Q :w/Hamadryas Sostrata Junonia/
That’s still too much work. You can enclose the [Str
](https://docs.raku.org/type/Str.html)s in angle brackets and leave out the item quoting and the separating commas. This acts the same as qw
:
那仍然是太多的工作。你可以将[字符串
](https://docs.raku.org/type/Str.html)括在尖括号中,并省略每项的引号和逗号分隔符。这与 qw
相同:
my $butterfly-genus = <Hamadryas Sostrata Junonia>;
The <>
only works if you don’t have whitespace inside your [Str
](https://docs.raku.org/type/Str.html)s. This gives you four elements because the space between 'Hamadryas
and perlicus'
separates them:
只有在[字符串
](https://docs.raku.org/type/Str.html)中没有空格时,<>
才有效。这给你四个元素,因为 'Hamadryas
和 perlicus'
之间的空格将它们分开:
my $butterflies = < 'Hamadryas perlicus' Sostrata Junonia >;
put 'Elements: ', $butterflies.elems; # Elements: 4
Raku has thought of that too and provides a [List
](https://docs.raku.org/type/List.html) quoting mechanism with quote protection. The <<>>
keeps the thingy in quotes as one item even though it has whitespace in it:
Raku 也考虑过这一点并提供带引号保护的[列表
](https://docs.raku.org/type/List.html)引用机制。 <<>>
将引号中的东西保持为一个项,即使它里面有空格:
my $butterflies = << 'Hamadryas perlicus' Sostrata Junonia >>;
put 'Elements: ', $butterflies.elems; # Elements: 3
With the <<>>
you can interpolate a variable. After that the value of the variable is an item but isn’t linked to the original variable:
使用 <<>>
可以插入变量。之后,变量的值是一个项,但没有链接到原始变量:
my $name = 'Hamadryas perlicus';
my $butterflies = << $name Sostrata Junonia >>;
say $butterflies;
Instead of <<>>
, you can use the fancier quoting with the single-character «»
version (double angle quotes). These are sometimes called French quotes:
你可以使用单字符 «»
版本(双角引号)的更好看的引号代替 <<>>
。这些有时被称为法语引号:
my $butterflies = « $name Sostrata Junonia »;
Both of these quote-protecting forms are the same as the :ww
adverb for Q
:
这两个引号保护形式都与 Q
的 :ww
副词相同:
my $butterflies = Q :ww/ 'Hamadryas perlicus' Sostrata Junonia /;
put 'Elements: ', $butterflies.elems; # Elements: 3
Sometimes you want a [List
](https://docs.raku.org/type/List.html) where all the elements are the same. The xx
list replication operator does that for you:
有时你需要一个[列表
](https://docs.raku.org/type/List.html),其中所有元素都相同。 xx
列表复制操作符为你执行此操作:
my $counts = 0 xx 5; # ( 0, 0, 0, 0, 0 )
A [List
](https://docs.raku.org/type/List.html) interpolates into a [Str
](https://docs.raku.org/type/Str.html) like any other scalar variable:
my $butterflies = << 'Hamadryas perlicus' Sostrata Junonia >>;
put "Butterflies are: $butterflies";
The [List
](https://docs.raku.org/type/List.html) stringifies by putting spaces between its elements. You can’t tell where one element stops and the next starts:
Butterflies are: Hamadryas perlicus Sostrata Junonia
The .join
method allows you to choose what goes between the elements:
.join
方法允许你选择元素之间的内容:
my $butterflies = << 'Hamadryas perlicus' Sostrata Junonia >>;
put "Butterflies are: ", $butterflies.join: ', ';
Now the output has commas between the elements:
现在输出元素之间有逗号:
Butterflies are: Hamadryas perlicus, Sostrata, Junonia
You can combine both of these, which makes it easier to also surround the [List
](https://docs.raku.org/type/List.html) items with characters to set them off from the rest of the [Str
](https://docs.raku.org/type/Str.html):
你可以将这两者结合起来,这样可以更容易地使用字符围绕[列表
](字符串
(https://docs.raku.org/type/Str.html)的其余部分相关联:
my $butterflies = << 'Hamadryas perlicus' Sostrata Junonia >>;
put "Butterflies are: /{$butterflies.join: ', '}/";
If you needed to parse this [Str
](https://docs.raku.org/type/Str.html) in some other program you’d know to grab the elements between the slashes:
如果你需要在其他程序中解析这个[字符串
](https://docs.raku.org/type/Str.html)你知道要抓取斜杠之间的元素:
Butterflies are: /Hamadryas perlicus, Sostrata, Junonia/
EXERCISE 6.1Write a program that takes two arguments. The first is a [Str
](https://docs.raku.org/type/Str.html) and the second is the number of times to repeat it. Use xx
and .join
to output the text that number of times on separate lines.
练习6.1 编写一个带有两个参数的程序。第一个是[字符串
](https://docs.raku.org/type/Str.html),第二个是重复它的次数。使用 xx
和 .join
在单独的行上输出该次数的文本。
3.1.1. Iterating All the Elements
Iteration is the repetition of a set of operations for each element of a collection. The for
control structure iterates through each element of a [List
](https://docs.raku.org/type/List.html) and runs its [Block
](https://docs.raku.org/type/Block.html) once for each element as the topic. You can use the .List
method to treat the one thing in your scalar variable (your [List
](https://docs.raku.org/type/List.html)) as its individual elements:
迭代是对集合的每个元素重复一组操作。 for
控制结构遍历[列表
](Block
(https://docs.raku.org/type/Block.html)。你可以使用 .List
方法将标量变量([列表
](https://docs.raku.org/type/List.html))中的一个元素视为其各个元素:
for $butterfly-genus.List {
put "Found genus $_";
}
You get one line per element:
每个元素得到一行:
Found genus Hamadryas
Found genus Sostrata
Found genus Junonia
Although I tend to call these things [Positional ](https://docs.raku.org/type/Positional.html)s there is actually a separate role for [Iterable ](https://docs.raku.org/type/Iterable.html)s that does the magic to make for work. The [Positional ](https://docs.raku.org/type/Positional.html)s I present in this book also do the [Iterable ](https://docs.raku.org/type/Iterable.html) role, so I don’t distinguish them even though I’m strictly wrong.
|
Calling .List
is a bit annoying though, so there’s a shortcut for it. Prefix the variable with @
to do the same thing:
虽然我倾向于将这些东西称为“[Positional
](Iterable
(Iterable
(https://docs.raku.org/type/Iterable.html)角色,所以即使我严重错误,我也不区分它们。
调用 .List
虽然有点烦人,所以有一个快捷方式。使用 @
前缀变量来执行相同的操作:
for @$butterfly-genus {
put "Found genus $_";
}
Skip the $
sigil altogether and use the @
sigil to store a [List
](https://docs.raku.org/type/List.html) in a variable:
完全跳过 $
sigil并使用 @
sigil 将 [List
](https://docs.raku.org/type/List.html) 存储在变量中:
my @butterfly-genus = ('Hamadryas', 'Sostrata', 'Junonia');
for @butterfly-genus {
put "Found genus $_";
}
This is actually different from the item assignment you’ve seen before. It’s a list assignment where the =
operator has a lower precedence:
这实际上与你之前看到的项赋值不同。这是一个列表赋值,其中 =
运算符的优先级较低:
my @butterfly-genus = 'Hamadryas', 'Sostrata', 'Junonia';
Why would you choose $
or @
? Assigning to $butterfly-genus
gives you a [List
](https://docs.raku.org/type/List.html) and all the restrictions of that type. You can’t add or remove elements. You can change the values inside a container but not the container itself. What do you get when you assign this way?
你为什么选择 $
或 @
? 赋值给` $butterfly-genus` 会给你一个[列表
](https://docs.raku.org/type/List.html)以及该类型的所有限制。你无法添加或删除元素。你可以更改容器内的值,但不能更改容器本身。当你指定这种方式时你会得到什么?
my @butterfly-genus = 'Hamadryas', 'Sostrata', 'Junonia';
put @butterfly-genus.^name; # Array
You get an [Array
](https://docs.raku.org/type/Array.html), which you’ll see more of later in this chapter. An [Array
](https://docs.raku.org/type/Array.html) relaxes all those restrictions. It allows you to add and remove elements and change values. Choose the type that does what you want. If you want the data to stay the same, choose the one that can’t change.
This looks a little better with interpolation, which means you’re less likely to forget explicit whitespace around words:
你得到一个[数组
](数组
(https://docs.raku.org/type/Array.html) 放宽了所有这些限制。它允许你添加和删除元素并更改值。选择满足你需求的类型。如果你希望数据保持不变,请选择无法更改的数据。
插值看起来好一点,这意味着你不太可能忘记单词周围的显式空格:
for @butterfly-genus {
put "$_ has {.chars} characters";
}
You’ll often want to give your variable a meaningful name. You can use a pointy [Block
](https://docs.raku.org/type/Block.html) to name your parameter instead of using the topic variable, $_
:
你经常希望为变量赋予有意义的名称。你可以使用尖头[块
](https://docs.raku.org/type/Block.html)来命名参数,而不是使用主题变量 $_
:
for @butterfly-genus -> $genus {
put "$genus has {$genus.chars} characters";
}
That looks a lot like the definition of a subroutine with → { … }
, because that’s what it is. That parameter is lexical to that [Block
](https://docs.raku.org/type/Block.html) just as it would be in a subroutine.
If your [Block
](https://docs.raku.org/type/Block.html) has more than one parameter, then the for
takes as many elements as it needs to fill in all of them. This goes through the [List
](https://docs.raku.org/type/List.html) by twos:
这看起来很像是带有 → { … }
的子程序的定义,因为它就是它的本质。该参数对于该[块
](https://docs.raku.org/type/Block.html)是词法的,就像它在子例程中一样。
如果你的[Block
](https://docs.raku.org/type/Block.html)有多个参数,那么 for
需要尽可能多的元素来填充所有这些参数。这每俩个元素遍历[列表
](https://docs.raku.org/type/List.html):
my @list = <1 2 3 4 5 6 7 8>;
for @list -> $a, $b {
put "Got $a and $b";
}
Each iteration of the [Block
](https://docs.raku.org/type/Block.html) takes two elements:
[Block
](https://docs.raku.org/type/Block.html) 的每次迭代都接收两个元素:
Got 1 and 2
Got 3 and 4
Got 5 and 6
Got 7 and 8
Ensure that you have enough elements to fill all of the parameters or you’ll get an error. Try that bit of code with one less element to see what happens!
You can use placeholder variables in your [Block
](https://docs.raku.org/type/Block.html), but in that case you don’t want to use a pointy [Block
](https://docs.raku.org/type/Block.html), which would already create a signature for you. Using placeholder variables also works:
确保你有足够的元素来填充所有参数,否则你将收到错误。用少一个元素尝试那些代码来看看会发生什么!
你可以在块中使用占位符变量,但在这种情况下,你不希望使用尖头块,这会为你创建签名。使用占位符变量也有效:
my @list = <1 2 3 4 5 6 7 8>;
for @list {
put "Got $^a and $^b";
}
READING LINES OF INPUT
The lines
routine reads lines of input from the files you specify on the command line, or from standard input if you don’t specify any. You’ll read more about this in [Chapter 8](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch08.html#camelia-files) but it’s immediately useful with for
:
lines
例程从你在命令行中指定的文件中读取输入行,如果未指定任何文件,则从标准输入读取。你将在第8章中详细了解这一点,但它对以下内容非常有用:
for lines() {
put "Got line $_";
}
Your programs reads and reoutputs all of the lines from all of the files. The line ending was autochomped; it was automatically stripped from the value because that’s probably what you wanted. The put
adds a line ending for you:
你的程序会读取并重新输出所有文件中的所有行。换行符是自动切除的;它会自动从值中删除,因为这可能是你想要的。 put
为你添加换行符:
% raku your-program.p6 file1.txt file2.txt
Got line ...
Got line ...
...
You need those parentheses even without an argument. The lines
routine can take an argument that tells it how many lines to grab:
即使没有参数,你也需要这些圆括号。lines
例程可以接收一个参数来告诉它要抓取多少行:
for lines(17) {
put "Got line $_";
}
You can break the lines into “words.” This takes a [Str
](https://docs.raku.org/type/Str.html) (or something that can turn into a [Str
](https://docs.raku.org/type/Str.html)) and gives you back the nonwhitespace chunks as separate elements:
你可以将这些行分解成“单词”。这接收一个[字符串
](https://docs.raku.org/type/Str.html) (或者可以变成[字符串
](https://docs.raku.org/type/Str.html)的东西),并将非空白块作为单独的元素返回:
say "Hamadryas perlicus sixus".words; # (Hamadryas perlicus sixus)
put "Hamadryas perlicus sixus".words.elems; # 3
Combine this with lines
to iterate one word at a time:
将其与 lines
组合以一次迭代一个单词:
for lines.words { ... }
The .comb
method takes it one step further by breaking it into characters:
.comb
方法通过将其分解为字符更进一步:
for lines.comb { ... }
You’ll see more about .comb
in [Chapter 16](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch16.html#camelia-regex2), where you’ll learn how to tell it to divide up the [Str
](https://docs.raku.org/type/Str.html).
With those three things you can implement your own wc program:
你将在第16章中看到更多关于 .comb
的内容,在那里你将学习如何划分[字符串
](https://docs.raku.org/type/Str.html)。
有了这三个东西你可以实现自己的 wc 程序:
for lines() {
state $lines = 0;
state $words = 0;
state $chars = 0;
$lines++;
$words += .words;
$chars += .comb;
LAST {
put "lines: $lines\nwords: $words\nchars: $chars";
}
}
The character count with this version doesn’t count all of the characters because the line ending was automatically removed.
EXERCISE 6.2Read the lines from the files you specify on the command line. Output each line prefixed by the line number. At the end of each line show the number of “words” in the line.
EXERCISE 6.3Output all of the lines of the butterfly census file (from [https://www.learningraku.com/downloads/](https://www.learningraku.com/downloads/)) that contain the genus Pyrrhogyra. How many lines did you find? If you don’t want to use that file try something else you have lying around.
此版本的字符计数不会计算所有字符,因为换行符会自动删除。
练习6.2 从命令行中指定的文件中读取行。输出以行号为前缀的每一行。在每行的末尾显示行中“单词”的数量。
练习6.3 输出包含 Pyrrhogyra 属的蝴蝶人口普查文件(来自https://www.learningraku.com/downloads/)的所有行。你找到了多少行?如果你不想使用该文件,请尝试其他的东西。
3.2. Ranges
A [Range
](https://docs.raku.org/type/Range.html) specifies the inclusive bounds of possible values without creating all of the items that would be in that [List
](https://docs.raku.org/type/List.html). A [Range
](https://docs.raku.org/type/Range.html) can be infinite because it doesn’t create all the elements; a [List
](https://docs.raku.org/type/List.html) would take up all your memory.
Create a [Range
](https://docs.raku.org/type/Range.html) with ..
and your bounds on either side:
[Range
](https://docs.raku.org/type/Range.html) 指定可能值的包含范围,而不创建该列表中的所有项。[Range
](https://docs.raku.org/type/Range.html) 可以是无限的,因为它不会创建所有元素;[列表
](https://docs.raku.org/type/List.html) 会占用你所有的内存。
使用 ..
创建一个 [Range
](https://docs.raku.org/type/Range.html),并在两边创建边界:
my $digit-range = 0 .. 10;
my $alpha-range = 'a' .. 'f';
If the lefthand value is larger than the righthand value you still get a [Range
](https://docs.raku.org/type/Range.html), but it will have no elements and you won’t get a warning:
如果左手值大于右手值,你仍然得到一个 [Range
](https://docs.raku.org/type/Range.html),但它没有元素,你不会收到警告:
my $digit-range = 10 .. 0;
put $digit.elems; # 0
You can exclude one or both endpoints with ^
on the appropriate side of the ..
operator. Some people call thesethe cat ears:
你可以在 ..
运算符的适当一侧使用 ^
排除一个或两个端点。有人称这些是猫耳朵:
my $digit-range = 0 ^.. 10; # exclude 0 ( 1..10 )
my $digit-range = 0 ..^ 10; # exclude 10 ( 0..9 )
my $digit-range = 0 ^..^ 10; # exclude 0 and 10 ( 1..9 )
As a shortcut for a numeric range starting from 0, use the ^
and the upper (exclusive) bound. This is very common Raku code:
作为从 0 开始的数字范围的快捷方式,使用 ^
和上限(不包括)。这是非常常见的 Raku 代码:
my $digit-range = ^10; # Same as 0 ..^ 10
This gives you the values 0
to 9
, which is 10 values altogether even though 10
is not part of the range.
EXERCISE 6.4How many items are in the range from 'aa'
to 'zz'
? How many from 'a'
to 'zz'
?
A [Range
](https://docs.raku.org/type/Range.html) knows its bounds. To see all of the values it would produce you can use .List
to turn it into a list. Be aware that if your [Range
](https://docs.raku.org/type/Range.html) is very large you might suddenly hog most of the memory on your system, so this isn’t something you’d normally want to do. It’s nice for debugging though:
这将为你提供值 0
到 9
,即使 10
不是 [Range
](https://docs.raku.org/type/Range.html) 的一部分,也是 10
个值。
练习6.4 'aa'
到 'zz'
[Range
](https://docs.raku.org/type/Range.html)内有多少项?从 'a'
到 'zz'
有多少?
[Range
](https://docs.raku.org/type/Range.html) 知道它的界限。要查看它将生成的所有值,你可以使用 .List
将其转换为列表。请注意,如果你的[Range
](https://docs.raku.org/type/Range.html) 非常大,你可能会突然占用系统上的大部分内存,因此这通常不是你想要做的事情。虽然调试很好:
% raku
> my $range = 'a' .. 'f';
"a".."f"
> $range.elems
6
> $range.List
(a b c d e f)
EXERCISE 6.5Show all of the spreadsheet cell addresses from B5
to F9
.
A smart match against a [Range
](https://docs.raku.org/type/Range.html) checks if a value is between the [Range
](https://docs.raku.org/type/Range.html)’s bounds:
练习6.5 显示从 B5
到 F9
的所有电子表格单元格地址。
针对 [Range
](https://docs.raku.org/type/Range.html) 的智能匹配检查值是否在 [Range
](https://docs.raku.org/type/Range.html) 的边界之间:
% raku
> 7 ~~ 0..10
True
> 11 ~~ ^10
False
A [Range
](https://docs.raku.org/type/Range.html) isn’t a [List
](https://docs.raku.org/type/List.html), though. Any value between the bounds is part of the [Range
](https://docs.raku.org/type/Range.html), even if it’s not a value that you would get if you listified the [Range
](https://docs.raku.org/type/Range.html):
但是,[Range
](https://docs.raku.org/type/Range.html) 不是[列表
](Range
(Range
(https://docs.raku.org/type/Range.html)列表化时得到的值:
% raku
> 1.37 ~~ 0..10
True
> 9.999 ~~ 0..10
True
> -137 ~~ -Inf..Inf # infinite range!
True
Excluding the endpoint doesn’t mean that the last element is the next-lowest integer. Here, it’s the exact value 10
that’s excluded; everything positive and less than 10
is still in the [Range
](https://docs.raku.org/type/Range.html):
排除端点并不意味着最后一个元素是下一个最小的整数。在这里,它被排除在外的确切值 10
;一切积极且小于 10
的东西仍然在范围内:
% raku
> 9.999 ~~ ^10
True
This is quite different from the listified version!
这与listified版本完全不同!
3.2.1. The @ Coercer
A [Range
](https://docs.raku.org/type/Range.html) isn’t a [List
](https://docs.raku.org/type/List.html). In some situations it acts like separate elements instead of merely bounds but in others it maintains its rangeness. Usually that works because something implicitly coerces it for you.
Start with a [Range
](https://docs.raku.org/type/Range.html). Output it using put
and say
. These show you different representations because their text representations of the object are different: put
uses .Str
and say
uses .gist
:
[Range
](列表
(https://docs.raku.org/type/List.html)。在某些情况下,它的作用类似于单独的元素,而不仅仅是边界,但在其他情况下,它保持其范围。通常这是有效的,因为某些内容会暗中强转它。
从 [Range
](https://docs.raku.org/type/Range.html) 开始。用 put
和 say
输出它。这些显示了不同的表示形式,因为它们的对象的文本表示是不同的:put
使用 .Str
, say
使用 .gist
:
my $range = 0..3;
put $range.^name; # Range
say $range; # 0..3
put $range; # 0 1 2 3
This distinction in the representation of the object is important. When you see say
in this book it’s because I want to show you .gist
because that’s closer to a summary of the object.
You can make that a [List
](https://docs.raku.org/type/List.html) by coercing it with the .List
method:
这种对象表示的区别很重要。当你看到本书中的说法时,这是因为我想向你展示 .gist
,因为它更接近于对象的摘要。
你可以通过使用 .List
方法强制它来创建 [列表
](https://docs.raku.org/type/List.html):
my $list = $range.List;
put $list.^name; # List
say $list; # (0 1 2 3)
Which one of these you have matters. A [List
](https://docs.raku.org/type/List.html) works differently in a smart match because the element must be part of the [List
](https://docs.raku.org/type/List.html):
你有哪些重要事项。 [List
](https://docs.raku.org/type/List.html) 在智能匹配中的工作方式不同,因为该元素必须是[列表
](https://docs.raku.org/type/List.html)的一部分:
put "In range? ", 2.5 ~~ $range; # True
put "In list? ", 2.5 ~~ $list; # False
Instead of typing .List
everywhere that you want something treated as such, you can use the prefix list context operator, @
, just like you’ve seen with the context operators +
and ~
:
你可以使用前缀列表上下文运算符 @
,而不是在你想要的上下文运算符 +
和 ~
中使用。
my $range = 0..3;
put "In range? ", 2.5 ~~ $range; # True (Range object)
put "In .List? ", 2.5 ~~ $range.List; # False (List object)
put "In @? ", 2.5 ~~ @$range; # False (List object)
Later you’ll use the @
sigil for [Array
](https://docs.raku.org/type/Array.html) variables. For now it’s a convenient way to treat something like a [List
](https://docs.raku.org/type/List.html).
稍后你将对[数组
](https://docs.raku.org/type/Array.html)变量使用 @
sigil。现在,它是一种方便的方式来处理像[列表
](https://docs.raku.org/type/List.html)这样的东西。
3.3. Sequences
A sequence, [Seq
](https://docs.raku.org/type/Seq.html), knows how to make a future [List
](https://docs.raku.org/type/List.html). It’s similar to a [List
](https://docs.raku.org/type/List.html) but it’s lazy. It knows where its values will come from and defers producing them until you actually need them.
序列 [Seq
](https://docs.raku.org/type/Seq.html) 知道如何创建未来的 [List
](https://docs.raku.org/type/List.html)。它类似于 [List
](https://docs.raku.org/type/List.html),但它很惰性的。它知道它的值来自哪里,并推迟生产它们,直到你真正需要它们为止。
A [Seq ](https://docs.raku.org/type/Seq.html) isn’t really a [Positional ](https://docs.raku.org/type/Positional.html) but it has a way to fake it. Rather than explain that I’m going to fake it too.
|
You call .reverse
on a [List
](https://docs.raku.org/type/List.html) to flip the list around. When you call [List
](https://docs.raku.org/type/List.html) methods it just works:
[Seq
](https://docs.raku.org/type/Seq.html) 不是真正的 [Positional
](https://docs.raku.org/type/Positional.html) ,但它有办法伪造它。多说无意,我也会伪造它。
你在列表上调用 .reverse
来翻转列表。当你调用[列表
](https://docs.raku.org/type/List.html)方法时,它只是起作用:
my $countdown = <1 2 3 4 5>.reverse;
put $countdown.^name; # Seq
put $countdown.elems; # 5
The result isn’t actually a [Seq
](https://docs.raku.org/type/Seq.html), but in most common cases that isn’t important. The things that try to use it as a [List
](https://docs.raku.org/type/List.html) will get what they expect, and there’s no immediate need to create another [List
](https://docs.raku.org/type/List.html) when the [Seq
](https://docs.raku.org/type/Seq.html) knows the values from the original one.
However, calling .eager
converts the [Seq
](https://docs.raku.org/type/Seq.html) to a [List
](https://docs.raku.org/type/List.html):
结果实际上不是 [Seq
](列表
(Seq
(List
(https://docs.raku.org/type/List.html)。
但是,调用 .eager
会将 [Seq
](https://docs.raku.org/type/Seq.html) 转换为 [List
](https://docs.raku.org/type/List.html):
my $countdown = <1 2 3 4 5>.reverse.eager;
put $countdown.^name; # Seq
put $countdown.elems; # 5
If you assign the [Seq
](https://docs.raku.org/type/Seq.html) to a variable with the @
sigil the [Seq
](https://docs.raku.org/type/Seq.html) also turns into a [List
](https://docs.raku.org/type/List.html). This is an eager assignmentto an [Array
](https://docs.raku.org/type/Array.html) (coming up soon):
如果将 [Seq
](https://docs.raku.org/type/Seq.html) 赋值给带有 @
sigil的变量,[Seq
](https://docs.raku.org/type/Seq.html) 也会变成 [List
](数组
(https://docs.raku.org/type/Array.html)的急切赋值(即将推出):
my @countdown = <1 2 3 4 5>.reverse;
put @countdown.^name; # Array
put @countdown.elems; # 5
The .pick
method chooses a random element from a [List
](https://docs.raku.org/type/List.html):
.pick
方法从 [List
](https://docs.raku.org/type/List.html) 中选择一个随机元素:
my $range = 0 .. 5;
my $sequence = $range.reverse;
say $sequence.^name; # Seq;
put $sequence.pick; # 5 (or maybe something else)
By default you can only iterate through a [Seq
](https://docs.raku.org/type/Seq.html) once. You use an item, then move on to the next one. This means that a [Seq
](https://docs.raku.org/type/Seq.html) needs to know how to make the next element, and once it uses it it can discard it—it doesn’t remember past values. If you try to use the [Seq
](https://docs.raku.org/type/Seq.html) after it’s gone through all of its elements you get an error:
默认情况下,你只能迭代一次 [Seq
](https://docs.raku.org/type/Seq.html)。你使用项,然后转到下一项。这意味着 [Seq
](https://docs.raku.org/type/Seq.html) 需要知道如何制作下一个元素,一旦它使用它就可以丢弃它 - 它不记得过去的值。如果你尝试使用 [Seq
](https://docs.raku.org/type/Seq.html) 后,它会遇到所有元素,则会出现错误:
put $sequence.pick; # 3 (or maybe something else)
put $sequence; # Error
The error tells you what to do:
错误告诉你该怎么做:
This Seq has already been iterated, and its values consumed
(you might solve this by adding .cache on usages of the Seq, or
by assigning the Seq into an array)
Adding .cache
remembers the elements of the [Seq
](https://docs.raku.org/type/Seq.html) so you can reuse them. After the .pick
there’s no error:
添加 .cache
会记住 [Seq
](https://docs.raku.org/type/Seq.html) 的元素,因此你可以重用它们。在 .pick
之后没有错误:
my $range = 0 .. 5;
my $sequence = $range.reverse.cache;
say $sequence.^name; # Seq;
put $sequence.pick; # 5 (or maybe something else)
put $sequence; # 5 4 3 2 1 0
This isn’t something you want to do carelessly, though. Part of the benefit of the [Seq
](https://docs.raku.org/type/Seq.html) is the memory saving it provides by not duplicating data unless it needs to.
不过,这不是你想要做的事情。 [Seq
](https://docs.raku.org/type/Seq.html) 的部分好处是它提供的内存节省,除非需要,否则不会复制数据。
3.3.1. Infinite Lazy Lists
The [Seq
](https://docs.raku.org/type/Seq.html) has to make all of the elements to .pick
one of them. Once it does that it forgets them and doesn’t have a way to make more elements. Raku does this to support infinite lazy lists. You make these with the triple-dot sequence operator, …
. By binding to the [Seq
](https://docs.raku.org/type/Seq.html) you give it a name without immediately reducing it to its values:
[Seq
](https://docs.raku.org/type/Seq.html) 必须使所有元素能 .pick
其中之一。一旦它这样做就会忘记它们,并且没有办法制作更多的元素。 Raku这样做是为了支持无限的惰性列表。你使用三点序列运算符 …
来制作它们。通过绑定到 [Seq
](https://docs.raku.org/type/Seq.html),你给它一个名字,而不是立即将它减少到它的值:
my $number-sequence := 1 ... 5;
That’s the integers from 1 to 5. The [Seq
](https://docs.raku.org/type/Seq.html) looks at the start and figures out how to get to the end. That sequence is easy; it adds a whole number.
You can make an exclusive endpoint (but not an exclusive startpoint). This [Seq
](https://docs.raku.org/type/Seq.html) is the integers from 1 to 4:
这是从 1 到 5 的整数。 [Seq
](https://docs.raku.org/type/Seq.html) 查看开始并找出如何到达终点。这个序列很简单;它增加了一个整数。
你可以创建一个独占端点(但不是一个独占的起点)。此 [Seq
](https://docs.raku.org/type/Seq.html) 是 1 到 4 的整数:
my $exclusive-sequence := 1 ...^ 5;
A [Range
](https://docs.raku.org/type/Range.html) can’t count down, but a [Seq
](https://docs.raku.org/type/Seq.html) can. This one subtracts whole numbers:
[Range
](https://docs.raku.org/type/Range.html) 不能倒数,但是 [Seq
](https://docs.raku.org/type/Seq.html) 可以。这一个减去整数:
my $countdown-sequence := 5 ... 1;
The same thing works for letters:
同样的事情适用于字母:
my $alphabet-sequence := 'a' ... 'z';
You can tell the [Seq
](https://docs.raku.org/type/Seq.html) how to determine the next element. You can specify more than one item for the start to give it the pattern:
你可以告诉 [Seq
](https://docs.raku.org/type/Seq.html) 如何确定下一个元素。你可以为开头指定多个项目以为其指定模式:
my $s := 0, 1, 2 ... 256; # 257 numbers, 0 .. 256
This is the series of whole numbers from 0 to 256. That’s the easiest pattern there. But add a 4 after the 2 and it’s a different series. Now it’s the powers of 2:
这是一系列从 0 到 256 的整数。这是最简单的模式。但是在 2 之后添加 4,这是一个不同的系列。现在它是 2 的幂:
my $s := 0, 1, 2, 4 ... 256; # powers of 2
say $s; # (0 1 2 4 8 16 32 64 128 256)
The …
can figure out arithmetic or geometric series. But it gets better. If you have a more complicated series you can give it a rule to make the next item. That rule can be a [Block
](https://docs.raku.org/type/Block.html) that grabs the previous argument and transforms it. Here it adds 0.1 to the previous element until it gets to 1.8. You couldn’t do this with a [Range
](https://docs.raku.org/type/Range.html):
…
可以算出算术或几何系列。但它变得更好。如果你有一个更复杂的系列,你可以给它一个规则来制作下一个项。该规则可以是一个阻止前一个参数并对其进行转换的块。这里它将前一个元素加 0.1,直到达到 1.8。你无法使用 [Range
](https://docs.raku.org/type/Range.html) 执行此操作:
my $s := 1, { $^a + 0.1 } ... 1.8;
say $s; # (1 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8)
If you have more than one positional parameter in your [Block
](https://docs.raku.org/type/Block.html) it looks farther back in the series. Here are the Fibonacci numbers up to 21:
如果你的 [Block
](https://docs.raku.org/type/Block.html) 中有多个位置参数,它会在系列中看起来更远。以下是最多 21 的斐波那契数字:
my $s := 1, 1, { $^a + $^b } ... 21;
say $s; # (1 1 2 3 5 8 13 21)
The [Seq
](https://docs.raku.org/type/Seq.html) only ends when it creates an item that is exactly equal to the endpoint. If you change that to 20
you get an infinite series and your program hangs while it creates every element so it can count them:
[Seq
](https://docs.raku.org/type/Seq.html) 仅在创建与端点完全相同的项目时结束。如果将其更改为 20
,则会得到一个无限系列,并且程序会在创建每个元素时挂起,以便计算它们:
my $s := 1, 1, { $^a + $^b } ... 20;
say $s.elems; # never gets an answer but keeps trying
Instead of a literal endpoint you can give it a [Block
](https://docs.raku.org/type/Block.html). The [Seq
](https://docs.raku.org/type/Seq.html) stops when the [Block
](https://docs.raku.org/type/Block.html) evaluates to True
(but keeps the element that makes it True
):
而不是字面端点,你可以给它一个[Block
](Block
(https://docs.raku.org/type/Block.html)计算为 True
时,[Seq
](https://docs.raku.org/type/Seq.html) 停止(但保持使其为 True
的元素):
my $s := 1, 1, { $^a + $^b } ... { $^a > 20 };
say $s.elems; # (1 1 2 3 5 8 13 21)
Those [Block
](https://docs.raku.org/type/Block.html)s are unwieldy, but you know that you can shorten them with [Whatever
](https://docs.raku.org/type/Whatever.html)s. Do the endpoint first:
那些 [Block
](https://docs.raku.org/type/Block.html) 很笨重,但你知道你可以用 [Whatever
](https://docs.raku.org/type/Whatever.html) 缩短它们。首先执行端点:
my $s := 1, 1, { $^a + $^b } ... * > 20;
You can reduce the first [Block
](https://docs.raku.org/type/Block.html) with two [Whatever
](https://docs.raku.org/type/Whatever.html)s. That [WhateverCode
](https://docs.raku.org/type/WhateverCode.html) sees two `*`s and knows it needs two elements:
你可以使用两个 [Whatever
](https://docs.raku.org/type/Whatever.html) 减少第一个 [Block
](https://docs.raku.org/type/Block.html)。 [WhateverCode
](https://docs.raku.org/type/WhateverCode.html) 看到两个 *
并知道它需要两个元素:
my $s := 1, 1, * + * ... * > 20;
That stops the Fibonacci numbers at 21. What if you wanted all of the Fibonacci numbers? The [Whatever
](https://docs.raku.org/type/Whatever.html) by itself can be the endpoint and in that context it is never True
; this series never ends:
这会阻止斐波那契数字为 21.若你想要所有斐波那契数字怎么办? [Whatever
](https://docs.raku.org/type/Whatever.html) 本身可以是端点,在这种情况下它永远不会是真的;这个系列永远不会结束
my $s := 1, 1, * + * ... *;
This is one of the reasons .gist
exists. It gives a summary of the object. It knows that this is an infinite [Seq
](https://docs.raku.org/type/Seq.html) so it doesn’t try to represent it:
这是其中一个原因 .gist
存在。它给出了对象的摘要。它知道这是一个无限的 [Seq
](https://docs.raku.org/type/Seq.html) 所以它不会试图表示它:
put $s.gist; # (...)
say $s; # (...), .gist implicitly
That’s it. That’s the heart of [Seq
](https://docs.raku.org/type/Seq.html). It can produce an infinite number of values but it doesn’t do it immediately. It knows the pattern to get to the next one.
Recall that a [Seq
](https://docs.raku.org/type/Seq.html) doesn’t remember all the values. Once it goes through them it doesn’t store them or regenerate them. In this example it reverses the list and exhausts the series. That’s all in the first put
. There’s nothing left for the second put
:
而已。这是 [Seq
](https://docs.raku.org/type/Seq.html) 的核心。它可以产生无限数量的值,但它不会立即执行。它知道进入下一个模式的模式。
回想一下 [Seq
](https://docs.raku.org/type/Seq.html) 不记得所有的值。一旦它通过它们就不会存储它们或重新生成它们。在此示例中,它会反转列表并耗尽系列。这是第一次投入。第二次放置没有任何东西:
my $s := 1 ... 5;
put $s.reverse; # (5 4 3 2 1)
put $s; # Error
You get this error:
你收到这个错误:
This Seq has already been iterated, and its values consumed
(you might solve this by adding .cache on usages of the Seq, or
by assigning the Seq into an array)
The error tells you what to do. You can call .cache
on a [Seq
](https://docs.raku.org/type/Seq.html) to force it to remember the values, even if this will eat up all of your memory:
该错误告诉你该怎么做。你可以在 [Seq
](https://docs.raku.org/type/Seq.html) 上调用 .cache
来强制它记住这些值,即使这会占用你所有的内存:
my $s := 1 ... 5;
put $s.cache.reverse; # 5 4 3 2 1
put $s; # 1 2 3 4 5
Should you need to treat a [Seq
](https://docs.raku.org/type/Seq.html) as a [List
](https://docs.raku.org/type/List.html), coerce it with @
. This generates all of its values:
如果你需要将 [Seq
](https://docs.raku.org/type/Seq.html) 视为[列表
](https://docs.raku.org/type/List.html),请使用 @
强制它。这会生成所有值:
my $s = ( 1 ... 5 );
put $s.^name; # Seq
my $list-from-s = @$s;
put $list-from-s.^name; #List
Most of the time a [Seq
](https://docs.raku.org/type/Seq.html) will act like a [List
](https://docs.raku.org/type/List.html), but sometimes you need to give some hints.
大多数情况下,[Seq
](https://docs.raku.org/type/Seq.html) 会像 [List
](https://docs.raku.org/type/List.html) 一样,但有时你需要提供一些提示。
3.3.2. Gathering Values
The previous [Seq
](https://docs.raku.org/type/Seq.html)s could easily compute their next values based on the ones that came before. That’s not always the case. A gather
with a [Block
](https://docs.raku.org/type/Block.html) returns a [Seq
](https://docs.raku.org/type/Seq.html). When you want the next value the gather
runs the code. A take
produces a value. Here’s the same thing as 1 … 5
using gather
:
之前的 [Seq
](https://docs.raku.org/type/Seq.html) 可以根据之前的 [Seq
](https://docs.raku.org/type/Seq.html) 轻松计算下一个值。情况并非总是如此。带有块的聚集返回 [Seq
](https://docs.raku.org/type/Seq.html)。当你想要下一个值时,聚集会运行代码。拍摄会产生一个价值。这与使用 gather
的 1 … 5
相同:
my $seq := gather {
state $previous = 0;
while $previous++ < 5 { take $previous }
}
say $seq;
Each time the code encounters a take
it produces a value, then waits until the next time something asks for a value. The [Seq
](https://docs.raku.org/type/Seq.html) stops when the code gets to the end of the gather
[Block
](https://docs.raku.org/type/Block.html). In this example, the while
[Block
](https://docs.raku.org/type/Block.html)runs once for each access to the [Seq
](https://docs.raku.org/type/Seq.html).
You don’t need the braces for the [Block
](https://docs.raku.org/type/Block.html) if the statement fits on one line. This is an infinite [Seq
](https://docs.raku.org/type/Seq.html):
每次代码遇到一个 take
它产生一个值,然后等到下一次有东西要求一个值。当代码到达 gather
[`块`](https://docs.raku.org/type/Block.html)的末尾时, [`Seq`](https://docs.raku.org/type/Seq.html) 停止。在这个例子中,`while` [`Block`](https://docs.raku.org/type/Block.html) 每次访问 [`Seq`](https://docs.raku.org/type/Seq.html) 一次。
如果语句适合一行,则不需要[Block
](https://docs.raku.org/type/Block.html)的大括号。这是一个无限的 [Seq
](https://docs.raku.org/type/Seq.html):
my $seq := gather take $++ while 1;
Those are easily done with the tools you already had. What about a random [Seq
](https://docs.raku.org/type/Seq.html) of random values? This gather
keeps choosing one value from @array
, forever:
使用你已有的工具可以轻松完成这些工作。随机值的随机 [Seq
](https://docs.raku.org/type/Seq.html) 怎么样?这个 gather
永远从 @array
中选择一个值:
my @array = <red green blue purple orange>;
my $seq := gather take @array.pick(1) while 1;
Here’s a gather
that provides only the lines of input with eq
in them. It doesn’t have to wait for all of the input to start producing values. And since the [Seq
](https://docs.raku.org/type/Seq.html) controls access to the lines, you don’t need to use or store them right away:
这是一个 gather
,它只提供带有 eq
的输入行。它不必等待所有输入开始生成值。由于 [Seq
](https://docs.raku.org/type/Seq.html) 控制对行的访问,因此你无需立即使用或存储它们:
my $seq := gather for lines() { next unless /eq/; take $_ };
for $seq -> $item {
put "Got: $item";
}
You can store these in a [Positional
](https://docs.raku.org/type/Positional.html) without being eager:
你可以将它们存储在一个 [Positional
](https://docs.raku.org/type/Positional.html) 而不是急切的:
my @seq = lazy gather for lines() { next unless /eq/; take $_ };
for @seq -> $item {
put "Got: $item";
}
It doesn’t matter how you create the [Seq
](https://docs.raku.org/type/Seq.html). Once you have it you can use it and pass it around like any other sequence.
EXERCISE 6.6Use gather
and take
to produce an infinite cycle of alternating values from an [Array
](https://docs.raku.org/type/Array.html) of color names. When you get to the end of the array, go back to the beginning and start again.
你如何创建 [Seq
](https://docs.raku.org/type/Seq.html) 并不重要。一旦你拥有它,你可以使用它并像任何其他序列一样传递它。
练习6.6 使用 gather
和 take
从颜色名称数组中产生无限循环的交替值。当你到达数组的末尾时,回到开头并重新开始。
3.4. Single-Element Access
You can extract a particular element by its position in the object, whether that’s a [List
](https://docs.raku.org/type/List.html), [Range
](https://docs.raku.org/type/Range.html), [Seq
](https://docs.raku.org/type/Seq.html), or other type of [Positional
](https://docs.raku.org/type/Positional.html) thingy. Each position has an index
that’s a positive integer (including 0). To get the element, append [
POSITION]
to your thingy:
你可以通过它在对象中的位置来提取特定元素,无论是 [List
](https://docs.raku.org/type/List.html), [Range
](https://docs.raku.org/type/Range.html), [Seq
](https://docs.raku.org/type/Seq.html) 还是其他类型的 [Positional
](https://docs.raku.org/type/Positional.html) thingy。每个位置都有一个正整数(包括0)的索引。要获取元素,请将[POSITION]附加到你的东西:
my $butterfly-genus = <Hamadryas Sostrata Junonia>;
my $first-butterfly = $butterfly-genus[0];
put "The first element is $first-butterfly";
[
POSITION]`is a postcircumfix operator. Operators are actually methods ([Chapter 12](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch12.html#camelia-classes)), so you can use the method dot between the object and the `[
POSITION]
(although you mostly won’t):
[`POSITION]是一个 postcircumfix 运算符。操作符实际上是方法(第12章),因此你可以使用对象和
[` POSITION ]
之间的方法点(尽管你通常不会):
my $first-butterfly = $butterfly-genus.[0];
You can interpolate either form in double-quoted [Str
](https://docs.raku.org/type/Str.html)s:
你可以在双引号[字符串
](https://docs.raku.org/type/Str.html)中插入任一形式:
put "The first butterfly is $butterfly-genus[0]";
put "The first butterfly is $butterfly-genus.[0]";
Since the index counts from zero the last position is one less than the number of elements. The .end
methodknows that position:
由于索引从零开始计数,因此最后一个位置比元素数少一个。 .end
方法知道该位置:
my $end = $butterfly-genus.end; # 2
my $last-butterfly = $butterfly-genus[$end]; # Junonia
If the thingy happens to be a lazy list you’ll get an error trying to find its end element; you can check if it is with .is-lazy
and perhaps do something different in that case:
如果thingy恰好是一个懒惰的列表,你会在尝试找到它的结束元素时遇到错误;你可以检查它是否与 .is-lazy
一起,并且可能在这种情况下做一些不同的事情:
my $butterfly-genus = <Hamadryas Sostrata Junonia>;
$butterfly-genus = (1 ... * );
put do if $butterfly-genus.is-lazy { 'Lazy list!' }
else {
my $end = $butterfly-genus.end;
$butterfly-genus[$end]
}
If you specify a position less than 0
you get an error. If you try to do it with a literal value the error message tells you that you’ve carried a habit over from a different language:
如果指定小于 0
的位置,则会出现错误。如果你尝试使用文字值来执行此操作,则错误消息会告诉你,你已经习惯使用其他语言:
$butterfly-genus[-1]; # fine in Perl 5, but error in Raku!
The error message tells you to use *-1
instead, which you’ll read more about in just a moment:
错误消息告诉你使用 *-1
代替,你将在稍后阅读更多信息:
Unsupported use of a negative -1 subscript to index from the end;
in Raku please use a function such as *-1
But if you’ve put that index in a variable, perhaps as the result of poor math, you get a different error:
但是如果你把这个索引放在变量中,也许是因为数学不好,你会得到一个不同的错误:
my $end = -1;
$butterfly-genus[$i];
This time it tells you that you are out of bounds:
这次它告诉你你已经出界了:
Index out of range. Is: -1, should be in 0..^Inf
This doesn’t work the same way on the other side. If you try to access an element beyond the last one, you get back Nil
with no error message:
这在另一方面不起作用。如果你尝试访问超出最后一个元素的元素,则会返回 Nil
而不显示任何错误消息:
my $end = $butterfly-genus.end;
$butterfly-genus[$end + 1]; # Nil!
Curiously, though, you can’t use Nil
to tell if you specified a wrong position because Nil
can be an element of a [List
](https://docs.raku.org/type/List.html):
但奇怪的是,你不能使用 Nil
来判断你是否指定了错误的位置,因为 Nil
可以是 [List
](https://docs.raku.org/type/List.html) 的一个元素:
my $has-nil = ( 'Hamadryas', Nil, 'Junonia', Nil );
my $butterfly = $has-nil.[3]; # works, but still Nil!
You can also put almost any code you like inside the square brackets. It should evaluate to an [Int
](https://docs.raku.org/type/Int.html), but if it doesn’t the operator will try to convert it to one. You can skip the $end
variable you’ve used so far and use`.end` directly:
你也可以将几乎任何你喜欢的代码放在方括号内。它应该评估为 [Int
](https://docs.raku.org/type/Int.html),但如果不是,则运算符将尝试将其转换为1。你可以跳过你到目前为止使用的 $end
变量并直接使用 .end
:
my $last-butterfly = $butterfly-genus[$butterfly-genus.end];
If you wanted the next-to-last element, you could subtract one:
如果你想要倒数第二个元素,你可以减去一个:
my $next-to-last = $butterfly-genus[$butterfly-genus.end - 1];
This way of counting from the end is quite tedious though, so there’s a shorter way to do it. A [Whatever
](https://docs.raku.org/type/Whatever.html) starinside the []
is the number of elements in the list (not the last index!). That * is one greater than the last position. Subtract 1
from *
to get the index for the last element:
这种从末尾计算的方式相当繁琐,所以有一个更短的方法来做到这一点。 [Whatever
](https://docs.raku.org/type/Whatever.html),是列表中元素的数量(不是最后一个索引!)。那个*比最后一个位置大一个。从*中减去1以获取最后一个元素的索引:
my $last-butterfly = $butterfly-genus[*-1];
To get the next-to-last element, subtract one more:
要获得倒数第二个元素,再减去一个:
my $next-to-last = $butterfly-genus[*-2];
If you subtract more than the number of elements, you’ll get Nil
(rather than an out-of-index error like you would without the *
).
If you have a [Seq
](https://docs.raku.org/type/Seq.html) it will create whatever items it needs to get to the one that you ask for. The triangle numbers add the index of the element to the previous number to get the next number in the series. If you want the fifth one ask for that index:
如果你减去超过元素的数量,你将得到 Nil
(而不是像你没有 *
那样的索引之外的错误)。
如果你有一个 [Seq
](https://docs.raku.org/type/Seq.html),它将创建你需要的任何物品到你要求的那个。三角形数字将元素的索引添加到前一个数字,以获得系列中的下一个数字。如果你想要第五个请求该索引:
my $triangle := 0, { ++$ + $^a } ... *;
say $triangle[4];
EXERCISE 6.7The squares of numbers is the sequence where you add 2n–1 to the previous value. n is the position in the sequence. Use the sequence operator …
to compute the square of 25.
练习6.7 数字的平方是将 2n-1加到前一个值的序列。 n 是序列中的位置。使用序列运算符 …
来计算 25 的平方。
3.4.1. Changing a Single Element
If your [List
](https://docs.raku.org/type/List.html) element is a container you can change its value. Previously you used an anonymous scalar container as a placeholder in one of your lists:
my $butterflies = ( $, 'Sostrata', 'Junonia' );
say $butterflies; # ((Any) perlicus Sostrata Junonia)
You can’t change the container, but you can change the value that’s in the container:
你无法更改容器,但可以更改容器中的值:
$butterflies.[0] = 'Hamadryas';
say $butterflies; # (Hamadryas Sostrata Junonia)
If you try to change an item that is not a container you get an error:
如果你尝试更改不是容器的项目,则会收到错误消息:
$butterflies.[1] = 'Ixias';
The error tells you that the element there is something that you cannot change:
该错误告诉你该元素有一些你无法更改的内容:
Cannot modify an immutable Str (...)
3.4.2. Multiple-Element Access
You can access multiple elements at the same time. A slice specifies more than one index in the brackets:
你可以同时访问多个元素。切片在括号中指定多个索引:
my $butterfly-genus = <Hamadryas Sostrata Junonia>;
my ( $first, $last ) = $butterfly-genus[0, *-1];
put "First: $first Last: $last";
Notice that you can declare multiple variables at the same time by putting them in parentheses after the my
. Since that’s not a subroutine call you still need a space after my
. The output shows the first and last elements:
请注意,你可以通过将多个变量放在 my
之后的括号中来同时声明多个变量。因为这不是一个子程序调用,你仍需要一个空格。输出显示第一个和最后一个元素:
First: Hamadryas Last: Junonia
The indices can come from a [Positional
](https://docs.raku.org/type/Positional.html). If you’ve stored that in a scalar variable you have to coerce or flatten it:
指数可以来自一个 [Positional
](https://docs.raku.org/type/Positional.html)。如果你将它存储在标量变量中,你必须强制或压扁它:
put $butterfly-genus[ 1 .. *-1 ]; # Sostrata Junonia
my $indices = ( 0, 2 );
put $butterfly-genus[ @$indices ]; # Hamadryas Junonia
put $butterfly-genus[ |$indices ]; # Hamadryas Junonia
my @positions = 1, 2;
put $butterfly-genus[ @positions ]; # Sostrata Junonia
Assigning to multiple elements works the same way inside the brackets. However, the elements must be mutable. If they aren’t containers you won’t be able to change them:
分配给多个元素在括号内的工作方式相同。但是,元素必须是可变的。如果它们不是容器,你将无法更改它们:
my $butterfly-genus = ( $, $, $ );
$butterfly-genus[ 1 ] = 'Hamadryas';
$butterfly-genus[ 0, *-1 ] = <Gargina Trina>;
put $butterfly-genus;
You can fix that by using an [Array
](https://docs.raku.org/type/Array.html), which you’re about to read more about. The [Array
](https://docs.raku.org/type/Array.html) automatically containerizes its elements:
你可以通过使用一个[数组
](https://docs.raku.org/type/Array.html)来修复它,你将要阅读更多。 [数组
](https://docs.raku.org/type/Array.html)自动容纳其元素:
my @butterfly-genus = <Hamadryas Sostrata Junonia>;
@butterfly-genus[ 0, *-1 ] = <Gargina Trina>;
put @butterfly-genus;
3.5. Arrays
You can’t change a [List
](https://docs.raku.org/type/List.html). Once constructed it is what it is and keeps the same number of elements. You can’t add or remove any elements. Unless the item is a container, each [List
](https://docs.raku.org/type/List.html) item’s value is fixed.
[Array
](https://docs.raku.org/type/Array.html)s are different. They containerize every item so that you can change any of them, and the [Array
](https://docs.raku.org/type/Array.html) itself is a container. You could start with the [Array
](https://docs.raku.org/type/Array.html) class to make an object:
my $butterfly-genus = Array.new: 'Hamadryas', 'Sostrata', 'Junonia';
You’ll probably never see that, though. Instead, you can use square brackets to make an [Array
](https://docs.raku.org/type/Array.html). Each item in the [Array
](https://docs.raku.org/type/Array.html) becomes a container even if it didn’t start as one:
但是你可能永远都看不到。相反,你可以使用方括号来制作[数组
](https://docs.raku.org/type/Array.html)。 [数组
](https://docs.raku.org/type/Array.html)中的每个项目都成为一个容器,即使它没有以一个方式启动:
my $butterfly-genus = ['Hamadryas', 'Sostrata', 'Junonia'];
Since every item is a container you can change any value by assigning to it through a single-element access:
由于每个项目都是容器,因此你可以通过单元素访问权限分配任何值:
$butterfly-genus.[1] = 'Paruparo';
say $butterflies; # [Hamadryas Paruparo Junonia]
This new behavior gets its own sigil, the @
(which looks a bit like an a for Array). When you assign a listy thing to an @
variable you get an [Array
](https://docs.raku.org/type/Array.html):
这个新行为得到了自己的印记,@
(看起来有点像一个数组)。当你为 @
变量分配一个listy的东西时,你得到一个数组:
my @butterfly-genus = <Hamadryas Sostrata Junonia>;
put @butterfly-genus.^name; # Array
The =
here is the list assignment operator you met earlier. Since you have an [Array
](https://docs.raku.org/type/Array.html) on the left side of the operator the =
knows it’s the list variety. That one is lower precedence than the comma, so you can leave off the grouping parentheses you’ve been using so far:
这里的 =
是你之前遇到的列表赋值运算符。由于运算符左侧有一个数组,因此 =
知道它是列表种类。那个优先级低于逗号,所以你可以不用你到目前为止使用的分组括号:
my @butterfly-genus = 1, 2, 3;
EXERCISE 6.8You’ve already used an [Array
](https://docs.raku.org/type/Array.html) that you haven’t seen. @*ARGS
is the collection of [Str
](https://docs.raku.org/type/Str.html)s that you’ve specified on the command line. Output each element on its own line.
练习6.8 你已经使用过一个你没见过的[数组
](https://docs.raku.org/type/Array.html)。 @*ARGS
是你在命令行中指定的[字符串
](https://docs.raku.org/type/Str.html)集合。输出每个元素在自己的行上。
3.5.1. Constructing an Array
There’s a hidden list assignment here that makes this possible. In its expanded form there are a couple of steps. Greatly simplified, the [Array
](https://docs.raku.org/type/Array.html) sets up a scalar container for the number of items it will hold and binds to that:
这里有一个隐藏的列表分配,这使得这成为可能。在其扩展形式中,有几个步骤。大大简化了,[Array
](https://docs.raku.org/type/Array.html) 为它将保持并绑定到的项目数量设置了一个标量容器:
my @butterfly-genus := ( $, $, $ ); # binding
Then it assigns the items in the incoming list to the containers in the [Array
](https://docs.raku.org/type/Array.html):
然后它将传入列表中的项目分配给 [Array
](https://docs.raku.org/type/Array.html) 中的容器:
@butterfly-genus = <Hamadryas Sostrata Junonia>;
You don’t need to do any of this yourself because it happens automatically when you assign to an [Array
](https://docs.raku.org/type/Array.html) (the @`variable). [`Array
](https://docs.raku.org/type/Array.html) items are always containers, and the [Array
](https://docs.raku.org/type/Array.html) itself is a container.
The square brackets construct an [Array
](https://docs.raku.org/type/Array.html) (and it’s the square brackets that index [Array
](https://docs.raku.org/type/Array.html)s). You can assign to a scalar or [Array
](https://docs.raku.org/type/Array.html) variable:
你不需要自己执行任何操作,因为它在你分配给数组(@variable
)时会自动发生。数组项始终是容器,[Array
](https://docs.raku.org/type/Array.html)本身是容器。
方括号构造一个数组(它是索引数组的方括号)。你可以分配标量或数组变量:
my $array = [ <Hamadryas Sostrata Junonia> ];
put $array.^name; # Array
put $array.elems; # 3
put $array.join: '|'; # Hamadryas|Sostrata|Junonia
my @array = [ <Hamadryas Sostrata Junonia> ];
put @array.^name; # Array
put @array.elems; # 3
put @array.join: '|'; # Hamadryas|Sostrata|Junonia
If you are going to assign to @array
you don’t need the brackets, though. This is the same thing:
但是,如果要分配给 @array
,则不需要括号。这是一回事:
my @array = <Hamadryas Sostrata Junonia>;
put @array.^name; # Array
put @array.elems; # 3
The brackets are handier when you want to skip the variable. You would do this for temporary data structures or subroutine arguments. You’ll see more of those as you go on.
如果要跳过变量,括号更方便。你可以为临时数据结构或子例程参数执行此操作。随着你的继续,你会看到更多这些。
3.5.2. Interpolating Arrays
A double-quoted [Str
](https://docs.raku.org/type/Str.html) can interpolate single or multiple elements of a [Positional
](https://docs.raku.org/type/Positional.html) or even all the elements. Use the brackets to select the elements that you want:
my $butterflies = <Hamadryas Sostrata Junonia>;
put "The first butterfly is $butterflies[0]";
put "The last butterfly is $butterflies[*-1]";
put "Both of those are $butterflies[0,*-1]";
put "All the butterflies are $butterflies[]";
When it interpolates multiple elements it inserts a space between the elements:
当它插入多个元素时,它会在元素之间插入一个空格:
The first butterfly is Hamadryas
The last butterfly is Junonia
Both of those are Hamadryas Junonia
All the butterflies are Hamadryas Sostrata Junonia
You can interpolate [Range
](https://docs.raku.org/type/Range.html)s too:
你也可以插入 [Range
](https://docs.raku.org/type/Range.html):
my $range = 7 .. 13;
put "The first is $range[0]"; # The first is 7
put "The last is $range[*-1]"; # The last is 13
put "All are $range"; # All are 7 8 9 10 11 12 13
The other [Positional
](https://docs.raku.org/type/Positional.html)s behave similarly based on how they generate their elements.
其他 [Positional
](https://docs.raku.org/type/Positional.html) 基于它们如何生成元素的行为类似。
3.5.3. Array Operations
Since the [Array
](https://docs.raku.org/type/Array.html) is a container you can change it. Unlike with a [List
](https://docs.raku.org/type/List.html), you can add and remove items. The .shift
method removes the first item from the [Array
](https://docs.raku.org/type/Array.html) and gives it back to you. That item is no longer in the [Array
](https://docs.raku.org/type/Array.html):
由于 [Array
](https://docs.raku.org/type/Array.html) 是容器,你可以更改它。与 [List
](https://docs.raku.org/type/List.html) 不同,你可以添加和删除项目。 .shift
方法从数组中删除第一个项目并将其返回给你。该项不再在数组中:
my @butterfly-genus = <Hamadryas Sostrata Junonia>;
my $first-item = @butterfly-genus.shift;
say @butterfly-genus; # [Sostrata Junonia]
say $first-item; # Hamadryas
If the [Array
](https://docs.raku.org/type/Array.html) is empty you get a [Failure
](https://docs.raku.org/type/Failure.html), but you won’t learn about those until [Chapter 7](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch07.html#camelia-failures). You don’t get an immediate error; the error shows up when you try to use it later:
如果[数组
](https://docs.raku.org/type/Array.html)为空,则会出现 [Failure
](https://docs.raku.org/type/Failure.html),但在第7章之前你将不会了解这些故障。你没有立即收到错误;稍后尝试使用时会出现错误:
my @array = Empty;
my $element = @array.shift;
put $element.^name; # Failure (soft exception)
That error is False
but won’t complain when it’s in a conditional:
该错误是 False
的,但是当它处于条件状态时不会抱怨:
while my $element = @array.shift { put $element }
The .pop
method removes the last item:
.pop
方法删除最后一项:
my @butterfly-genus = <Hamadryas Sostrata Junonia>;
my $first-item = @butterfly-genus.pop;
say @butterfly-genus; # [Hamadryas Sostrata]
say $first-item; # Junonia
To add one or more items to the front of the list, use .unshift
. One top-level item becomes one element in the [Array
](https://docs.raku.org/type/Array.html):
要将一个或多个项添加到列表的前面,请使用 .unshift
。一个顶级项目成为数组中的一个元素:
my @butterfly-genus = Empty;
@butterfly-genus.unshift: <Hamadryas Sostrata>;
say @butterfly-genus; # [Hamadryas Sostrata]
.push
adds a list of items to the end of the list:
`.push`将一个项列表添加到列表的末尾:
@butterfly-genus.push: <Junonia>;
say @butterfly-genus; # [Hamadryas Sostrata Junonia]
With .splice
you can add elements to or remove them from anywhere in the [Array
](https://docs.raku.org/type/Array.html). It takes a starting index, a length, and the items to remove from the list. It gives you the elements it removed:
使用 .splice
,你可以在[Array
](https://docs.raku.org/type/Array.html)中的任何位置添加元素或从中删除元素。它需要一个起始索引,一个长度以及要从列表中删除的项目。它为你提供了删除的元素:
my @butterfly-genus = 1 .. 10;
my @removed = @butterfly-genus.splice: 3, 4;
say @removed; # [4 5 6 7]
say @butterfly-genus; # [1 2 3 8 9 10]
You can give .splice
items to replace those that you removed:
你可以提供 .splice
项目来替换你删除的项目:
my @butterfly-genus = 1 .. 10;
my @removed = @butterfly-genus.splice: 5, 2, <a b c>;
say @removed; # [6 7]
say @butterfly-genus; # [1 2 3 4 5 a b c 8 9 10]
If the length is 0 you don’t remove anything, but you can still insert items. You get an empty [Array
](https://docs.raku.org/type/Array.html) back:
如果长度为 0,则不会删除任何内容,但仍可以插入项目。你得到一个空[数组
](https://docs.raku.org/type/Array.html):
my @butterfly-genus = 'a' .. 'f';
my @removed = @butterfly-genus.splice: 5, 0, <X Y Z>;
say @removed; # []
say @butterfly-genus; # [a b c d e X Y Z f]
Each of these [Array
](https://docs.raku.org/type/Array.html) methods have routine versions:
my $first = shift @butterfly-genus;
my $last = pop @butterfly-genus;
unshift @butterfly-genus, <Hamadryas Sostrata>;
push @butterfly-genus, <Junonia>
splice @butterfly-genus, $start-pos, $length, @elements;
EXERCISE 6.9Start with an [Array
](https://docs.raku.org/type/Array.html) that holds the letters from a to f. Use the [Array
](https://docs.raku.org/type/Array.html) operators to move those elements to a new [Array
](https://docs.raku.org/type/Array.html) that will have the same elements in reverse order.
EXERCISE 6.10Start with the [Array
](https://docs.raku.org/type/Array.html) that holds the letters from a to f. Use only .splice
to make these changes: remove the first element, remove the last element, add a capital A to the front of the list, and add a capital F to the end of the list.
练习6.9 启动一个包含从 a 到 f 的字母的[数组
](数组
(数组
(https://docs.raku.org/type/Array.html),它将以相反的顺序具有相同的元素。
EXERCISE 6.10 从包含 a 到 f 的字母的[数组
](https://docs.raku.org/type/Array.html)开始。仅使用 .splice
进行这些更改:删除第一个元素,删除最后一个元素,将大写 A 添加到列表的前面,然后将大写字母 F 添加到列表的末尾。
3.5.4. Lists of Lists
A [List
](https://docs.raku.org/type/List.html) can be an element of another [List
](https://docs.raku.org/type/List.html) (or [Seq
](https://docs.raku.org/type/Seq.html)). Depending on your previous language experience your reaction to this idea will be either “Of course!” or “This is so wrong!”
The .permutations
method produces a [Seq
](https://docs.raku.org/type/Seq.html) of sublists where each one represents a unique ordering of all the elements of the original:
[List
](https://docs.raku.org/type/List.html) 可以是另一个 [List
](https://docs.raku.org/type/List.html)(或 [Seq
](https://docs.raku.org/type/Seq.html))的元素。根据你之前的语言经验,你对此想法的反应将是“当然!”或“这是错误的!”
.permutations
方法生成一个 [Seq
](https://docs.raku.org/type/Seq.html) 的子列表,其中每个子列表代表原始元素的唯一排序:
my $list = ( 1, 2, 3 );
say $list.permutations;
put "There are {$list.permutations.elems} elements";
The output shows a [List
](https://docs.raku.org/type/List.html) of [List
](https://docs.raku.org/type/List.html)s where each element is another [List
](https://docs.raku.org/type/List.html):
输出显示[列表
](列表
(列表
(https://docs.raku.org/type/List.html):
((1 2 3) (1 3 2) (2 1 3) (2 3 1) (3 1 2) (3 2 1))
There are 6 elements
You can make these directly. This [List
](https://docs.raku.org/type/List.html) has two elements, both of which are [List
](https://docs.raku.org/type/List.html)s:
你可以直接制作这些。此[列表
](列表
(https://docs.raku.org/type/List.html):
my $list = ( <a b>, <1 2> );
put $list.elems; # 2
say $list; # ((a b) (1 2))
You can explicitly create the sublists with parentheses:
你可以使用括号显式创建子列表:
my $list = ( 1, 2, ('a', 'b') );
put $list.elems; # 3
say $list; # (1 2 (a b))
You can separate sublists with semicolons. Elements between ;
end up in the same sublist, although sublists of a single element are just that element:
你可以使用分号分隔子列表。;
之间的元素最终在同一个子列表中,尽管单个元素的子列表只是该元素:
my $list = ( 1; 'Hamadryas'; 'a', 'b' );
put $list.elems; # 3
say $list; # (1 2 (a b))
put $list.[0].^name; # Int
put $list.[1].^name; # Str
put $list.[*-1].^name; # List
3.5.5. Flattening Lists
You may be more comfortable with flat [List
](https://docs.raku.org/type/List.html)s, if you want a bunch of elements with no structure. .flat
extracts all the elements of the sublist and makes it a single-level simple list. The flat [List
](https://docs.raku.org/type/List.html) created here has four elements instead of three:
如果你想要一堆没有结构的元素,你可能会更喜欢平面[列表
](https://docs.raku.org/type/List.html)。 .flat
提取子列表的所有元素,并使其成为单级简单列表。这里创建的平面[列表
](https://docs.raku.org/type/List.html)有四个元素而不是三个:
my $list = ( 1, 2, ('a', 'b') );
put $list.elems; # 3
my $flat = $list.flat;
put $flat.elems; # 4
say $flat; # (1 2 a b)
This works all the way down into sublists of sublists (of sublists…). Here, the last element is a sublist that has a sublist. The flat [List
](https://docs.raku.org/type/List.html) ends up with six elements:
这一直有效地进入子列表(子列表……)的子列表中。这里,最后一个元素是一个具有子列表的子列表。平面[列表
](https://docs.raku.org/type/List.html)最终有六个元素:
my $list = ( 1, 2, ('a', 'b', ('X', 'Z') ) );
put $list.elems; # 3
my $flat = $list.flat;
put $flat.elems; # 6
say $flat; # (1 2 a b X Z)
Sometimes you don’t want a sublist to flatten. In that case you can itemize it by putting a $
in front of the parentheses. An itemized element resists flattening:
有时你不希望子列表变平。在这种情况下,你可以通过在圆括号前加一个 $
来项化它。项化的元素抵制展平:
my $list = ( 1, 2, ('a', 'b', $('X', 'Z') ) );
put $list.elems; # 3
my $flat = $list.flat;
put $flat.elems; # 5
say $flat; # (1 2 a b (X Z))
A [List
](https://docs.raku.org/type/List.html) held in a scalar variable is already itemized and does not flatten:
标量变量中保存的[列表
](https://docs.raku.org/type/List.html)已项化,并未展平:
my $butterfly-genus = ('Hamadryas', 'Sostrata', 'Junonia');
my $list = ( 1, 2, ('a', 'b', $butterfly-genus ) );
my $flat = $list.flat;
say $flat; # (1 2 a b (Hamadryas Sostrata Junonia))
Then what do you do to un-itemize something? You can use the prefix |
to flatten it. This decontainerizes thethingy:
那么你怎么做才能不项化一些事情呢?你可以使用前缀 |
压扁它。这使得这些东西解容器化了:
my $butterfly-genus = ('Hamadryas', 'Sostrata', 'Junonia');
my $list = ( 1, 2, ('a', 'b', |$butterfly-genus ) );
my $flat = $list.flat;
put $flat.elems; # 7
say $flat; # (1 2 a b Hamadryas Sostrata Junonia)
The |
takes certain types ([Capture
](https://docs.raku.org/type/Capture.html), [Pair
](https://docs.raku.org/type/Pair.html), [List
](https://docs.raku.org/type/List.html), [Map
](https://docs.raku.org/type/Map.html), and [Hash
](https://docs.raku.org/type/Hash.html)) and flattens them. You’ll see more of this in [Chapter 11](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch11.html#camelia-subroutines). It actually creates a [Slip
](https://docs.raku.org/type/Slip.html), which is a type of [List
](https://docs.raku.org/type/List.html) that automatically flattens into an outer list. You could coerce your [List
](https://docs.raku.org/type/List.html) with .Slip
to get the same thing:
|
接收某些类型([Capture
](Pair
(https://docs.raku.org/type/Pair.html), [List
](https://docs.raku.org/type/List.html), [Map
](https://docs.raku.org/type/Map.html), and [Hash
](https://docs.raku.org/type/Hash.html)并展平它们。你将在第11章中看到更多内容。它实际上创建了一个 [Slip
](https://docs.raku.org/type/Slip.html),它是一种 [List
](https://docs.raku.org/type/List.html),可以自动展平到外部列表中。你可以使用 .Slip
强制你的 [List
](https://docs.raku.org/type/List.html) 来获得相同的东西:
my $list = ( 1, 2, ('a', 'b', $butterfly-genus.Slip ) );
Now the elements in $butterfly-genus
are at the same level as the other elements in its sublist:
现在 $butterfly-genus
中的元素与其子列表中的其他元素处于同一级别:
(1 2 (a b Hamadryas Sostrata Junonia))
The slip
routine does the same thing:
slip
例程做同样的事情:
my $list = ( 1, 2, ('a', 'b', slip $butterfly-genus ) );
These [Slip
](https://docs.raku.org/type/Slip.html)s will be handy later in this chapter.
3.5.6. Interesting Sublists
Here’s something quite useful. The .rotor
method breaks up a flat [List
](https://docs.raku.org/type/List.html) into a [List
](https://docs.raku.org/type/List.html) of [List
](https://docs.raku.org/type/List.html)s where each sublist has the number of elements you specify. You can get five sublists of length 2:
这儿有一些非常有用的东西。 .rotor
方法将平面[列表
](列表
(列表
(https://docs.raku.org/type/List.html),其中每个子列表都包含你指定的元素数。你可以获得 5 个长度为 2 的子列表:
my $list = 1 .. 10;
my $sublists = $list.rotor: 2;
say $sublists; # ((1 2) (3 4) (5 6) (7 8) (9 10))
This is especially nice to iterate over multiple items at the same time. It grabs the number of items that you specify and supplies them as a single [List
](https://docs.raku.org/type/List.html):
这对于同时迭代多个项尤其有用。它会抓取你指定的项数并将它们作为单个[列表
](https://docs.raku.org/type/List.html)提供:
my $list = 1 .. 10;
for $list.rotor: 3 {
.say
}
By default it only grabs exactly the number you specify. If there aren’t enough elements it doesn’t give you a partial [List
](https://docs.raku.org/type/List.html). This output is missing 10
:
默认情况下,它只捕获你指定的数字。如果没有足够的元素,它不会给你一个部分[列表
](https://docs.raku.org/type/List.html)。此输出没有 10
:
(1 2 3)
(4 5 6)
(7 8 9)
If you want a short sublist at the end, the :partial
adverb will do that:
如果你想在结尾处有一个简短的子列表,那么 :partial
副词将会这样做:
my $list = 1 .. 10;
for $list.rotor: 3, :partial {
.say
}
Now there’s a short list in the last iteration:
现在在最后一次迭代中有一个短的列表:
(1 2 3)
(4 5 6)
(7 8 9)
(10)
EXERCISE 6.11Use lines
and .rotor
to read chunks of three lines from input. Output the middle line in each chunk.
练习6.11 使用 lines
和 .rotor
从输入中读取三行的块。输出每个块的中间行。
3.6. Combining Lists
Making and manipulating [Positional
](https://docs.raku.org/type/Positional.html)s is only the first level of your programming skill. Raku has several facilities to manage, combine, and process multiple [Positional
](https://docs.raku.org/type/Positional.html) things together.
制作和操作 [Positional
](https://docs.raku.org/type/Positional.html) 只是编程技巧的第一级。 Raku 有几个工具来管理,组合和处理多个 [Positional
](https://docs.raku.org/type/Positional.html) 事物。
3.6.1. The Zip Operator, Z
The Z
operator takes elements from the same positions in the lists you provide and creates sublists from them:
Z
运算符从你提供的列表中的相同位置获取元素,并从中创建子列表:
say <1 2 3> Z <a b c>; # ((1 a) (2 b) (3 c))
When it reaches the end of the shortest list, it stops. It doesn’t matter which list is shorter:
当它到达最短列表的末尾时,它会停止。哪个列表更短并不重要:
say <1 2 3> Z <a b>; # ((1 a) (2 b))
say <1 2> Z <a b c>; # ((1 a) (2 b))
The zip
routine does the same thing:
zip
例程做同样的事情:
say zip( <1 2 3>, <a b> ); # ((1 a) (2 b))
This one gives the same output because $letters
doesn’t have enough elements to make more sublists:
这个给出相同的输出,因为 $letters
没有足够的元素来制作更多的子列表:
my $numbers = ( 1 .. 10 );
my $letters = ( 'a' .. 'c' );
say @$numbers Z @$letters; # ((1 a) (2 b) (3 c))
You can do it with more than two lists:
你可以使用两个以上的列表:
my $numbers = ( 1 .. 3 );
my $letters = ( 'a' .. 'c' );
my $animals = < 🐈 🐇 🐀 >; # cat rabbit rat
say @$numbers Z @$letters Z @$animals;
Each sublist has three elements:
每个子列表都有三个元素:
((1 a 🐈)(2 b 🐇)(3 c 🐀))
zip
does the same thing as Z
:
zip`和 `Z
做同样的事情:
say zip @$numbers, @$letters, @$animals;
You can use it with for
:
你可以将它和 for
一块使用:
for zip @$numbers, @$letters, @$animals {
.say;
}
(1 a 🐈)
(2 b 🐇)
(3 c 🐀)
EXERCISE 6.12Use the Z
operator to make an [Array
](https://docs.raku.org/type/Array.html) of [List
](https://docs.raku.org/type/List.html)s that pair each letter with its position in the alphabet.
练习6.12 使用 Z
运算符创建一个[列表
](数组
(https://docs.raku.org/type/Array.html),将每个字母与其在字母表中的位置配对。
3.6.2. The Cross Operator, X
The X
cross operator combines every element of one [Positional
](https://docs.raku.org/type/Positional.html) with every element of another:
X
交叉运算符将一个 [Positional
](https://docs.raku.org/type/Positional.html) 的每个元素与另一个 [Positional
](https://docs.raku.org/type/Positional.html) 的每个元素组合在一起:
my @letters = <A B C>;
my @digits = 1, 2, 3;
my @crossed = @letters X @digits;
say @crossed;
The output shows that every letter was paired with every number:
输出显示每个字母都与每个数字配对:
[(A 1) (A 2) (A 3) (B 1) (B 2) (B 3) (C 1) (C 2) (C 3)]
EXERCISE 6.13 A deck of 52 playing cards has four suits, ♣ ♡ ♠ ♢, each with 13 cards, 2 to 10, jack, queen, king, and ace. Use the cross operator to make a [List
](https://docs.raku.org/type/List.html) of [List
](https://docs.raku.org/type/List.html)s that represents each card. Output the list of cards so all the cards of one suit show up on the same line.
练习6.13 一副 52 张的扑克牌有四套花色,♣♡♠♢,每套有 13 张纸牌,2 到 10,J,Q,K,小王和大王。使用交叉运算符创建代表每张纸牌的[列表
](列表
(https://docs.raku.org/type/List.html)。输出纸牌的列表,以便一套花色的所有纸牌显示在同一条线上。
3.6.3. The Hyperoperators
Instead of combining [Positional
](https://docs.raku.org/type/Positional.html)s, you can operate on pairs of them to create a [List
](https://docs.raku.org/type/List.html) of the results. The hyperoperators can do that. Surround the +
operator with <<>>
. This numerically adds the first element of @right
to the first element of @left
. The result of that addition becomes the first element of the result. This happens for the second elements, then the third, and so on:
你可以对其中的一对进行操作,而不是组合 [Positional
](列表
(https://docs.raku.org/type/List.html)。 hyperoperators 可以做到这一点。使用 <<>>
包围 +
运算符。这在数字上将 @right
的第一个元素加到 @left
的第一个元素中。相加的结果成为结果的第一个元素。这发生在第二个元素上,然后是第三个元素上,依此类推:
my @right = 1, 2, 3;
my @left = 5, 9, 4;
say @left <<+>> @right; # [6 11 7]
Pick a different operator and follow the same process. The concatenation operator joins the [Str
](https://docs.raku.org/type/Str.html) versions of each element:
选择一个不同的运算符并按照相同的过程。连接运算符连接每个元素的[字符串
](https://docs.raku.org/type/Str.html)版本:
my @right = 1, 2, 3;
my @left = 5, 9, 4;
say @left <<~>> @right; # [51 92 43]
If one of the sides has fewer elements the <<>>
hyper recycles elements from the shorter one. It doesn’t matter which side the shorter list is on. Here, @left
has fewer elements. When it’s time to operate on the third elements the hyper starts at the beginning of @left
again to reuse 11
:
如果其中一边具有较少的元素,则 <<>>
hyper 会从较短的元素中循环该元素。短列表的哪一方无关紧要。在这里,@left
有更少的元素。当是时候对第三个元素进行操作时,hyper 会在 @left
的开头再次开始重用 11
:
my @right = 3, 5, 8;
my @left = 11, 13;
say @left <<+>> @right; # [14 18 19]
say @right <<+>> @left; # [14 18 19]
Point the angle brackets toward the inside to insist that both sides have the same number of elements. You’ll get an error when the sizes don’t match:
将尖括号指向内侧以确保两侧具有相同数量的元素。当大小不匹配时,你会收到错误:
say @left >>+<< @right; # Error!
Another option is to allow one side to be smaller than the other but to not recycle elements. If both sets of angle brackets point away from the shorter side then the hyper does not reuse elements from the shorter side:
另一种选择是允许一侧比另一侧小但不循环元素。如果两组尖括号都指向较短的一侧,那么 hyper 不会重用短边的元素:
my @long = 3, 5, 8;
my @short = 11, 13;
say @short >>+>> @long; # [14 18] no recycling
say @long >>+>> @short; # [14 18 19]
say @short <<+<< @long; # [14 18 19]
say @long <<+<< @short; # [14 18] no recycling
Instead of the double angle brackets you can use the fancier »«
versions:
你可以使用更漂亮的 »«
版本代替双角括号:
my @long = 3, 5, 8;
my @short = 11, 13;
say @short «+» @long; # [14 18 19]
say @short »+« @long; # Error
say @short »+» @long; # [14 18] no recycling
say @long »+» @short; # [14 18 19]
say @short «+« @long; # [14 18 19]
say @long «+« @short; # [14 18] no recycling
3.6.4. The Reduction Operator
The reduction operator is a bit different from Z
, X
, or the hyperoperators. It turns a [Positional
](https://docs.raku.org/type/Positional.html) into a single value by operating on two elements at a time to turn them into one element.
The prefix []
is the reduction operator. On the inside you put a binary operator. It applies that operator to the first two elements of its [Positional
](https://docs.raku.org/type/Positional.html) to get a single value. It replaces those two values with the result; this makes the input one element shorter. It keeps doing this until there’s one element left. That’s the final value.
Here’s a quick way to sum some numbers:
简化运算符与 Z
,X
或超运算符略有不同。它通过一次操作两个元素将 [Positional
](https://docs.raku.org/type/Positional.html) 转换为单个值,将它们转换为单个值。
前缀 []
是简化运算符。在里面你放了一个二元运算符。它将该运算符应用于其 [Positional
](https://docs.raku.org/type/Positional.html) 的前两个元素以获取单个值。它用结果替换这两个值;这使得输入一个元素更短。它一直这样做,直到剩下一个元素。这是最终值。
这是一个快速的方法来合计一些数字:
my $sum = [+] 1 .. 10; # 55
This is the same as this expression if you write out the steps:
如果你写出以下步骤,则与此表达式相同:
(((((((((1 + 2) + 3) + 4) + 5) + 6) + 7) + 8) + 9) + 10)
And to do a factorial:
计算阶乘:
my $factorial = [*] 1 .. 10; # 3628800
Are all the values True
? Apply the &&
to the first two elements and replace them with the result until there’s one element left. At the end use the ?
( or .so
) to coerce the result to a Boolean:
所有的值都是 True
吗?将 &&
应用于前两个元素并将其替换为结果,直到剩下一个元素。最后使用 ?
(或 .so
)将结果强制转换为布尔值:
my $condition = ?( [&&] 1 .. 10 ); # True
my $condition = ?( [&&] ^10 ); # False
There’s a binary max
operator too:
还有一个二元的 max
运算符:
my $max = 1 max 137; # 137;
You can put that inside the brackets. This makes one pass through the elements to discover the largest numeric value:
你可以把它放在方括号内。这使得一个遍历元素以发现最大的数值:
my $max = [max] @numbers
If you want to use your own subroutine, use an extra set of braces and the &
sigil to make it look like an operator:
如果你想使用自己的子程序,请使用额外的方括号和 &
sigil使其看起来像一个运算符:
sub longest {
$^a.chars > $^b.chars ?? $^a !! $^b;
}
my $longest =
[[&longest]] <Hamadryas Rhamma Asterocampa Tanaecia>;
put "Longest is $longest"; # Longest is Asterocampa
That trick works to convert a subroutine to a binary operator:
该技巧可以将子例程转换为二元运算符:
$first [&longest] $second
3.7. Filtering Lists
The .grep
method filters a [Positional
](https://docs.raku.org/type/Positional.html) to get the elements that satisfy your condition. Any element that satisfies the condition becomes part of the new [Seq
](https://docs.raku.org/type/Seq.html):
.grep
方法过滤 [Positional
](https://docs.raku.org/type/Positional.html) 以获取满足条件的元素。满足条件的任何元素都将成为新 [Seq
](https://docs.raku.org/type/Seq.html) 的一部分:
my $evens = (0..10).grep: * %% 2; # (0 2 4 6 8 10)
A [Block
](https://docs.raku.org/type/Block.html) works too. The current element shows up in $_
:
[Block
](https://docs.raku.org/type/Block.html) 也有效。当前元素出现在 `$_`中:
my $evens = (0..10).grep: { $_ %% 2 }; # (0 2 4 6 8 10)
If your condition is only a type .grep
smart matches the current element against that type:
如果你的条件只是一种类型 .grep
将当前元素与该类型智能匹配:
my $allomorphs = <137 2i 3/4 a b>;
my $int-strs = $allomorphs.grep: IntStr; # (137)
my $rat-strs = $allomorphs.grep: RatStr; # (3/4)
my $img-strs = $allomorphs.grep: ComplexStr; # (2i)
my $strs = $allomorphs.grep: Str; # (a b)
Remember that a smart match against a type includes matching anything that type is based on. Trying to get all the [Str
](https://docs.raku.org/type/Str.html)s finds everything since the <>
creates allomorphs and every element matches [Str
](https://docs.raku.org/type/Str.html):
请记住,针对类型的智能匹配包括匹配基于类型的任何内容。试图得到所有的[字符串
](https://docs.raku.org/type/Str.html)会找出所有的东西,因为 <>
创建了同质异形并且每个元素都匹配[字符串
](https://docs.raku.org/type/Str.html):
my $everything = $allomorphs.grep: Str; # (1 2i 3/4 a b)
The .does
method checks if the element has a role. Here, you want the elements that don’t do that role—if it can be a number, you don’t want it:
.does
方法检查元素是否具有角色。在这里,你需要该元素不执行该角色 - 如果它可以是数字,则你不需要它:
my $just-str = $allomorphs.grep: { ! .does(Numeric) }; # (a b)
You can specify some adverbs with .grep
. The :v
adverb (for “values”) gives the same list you get without it:
你可以使用 .grep
指定一些副词。 :v
副词(对于“值”)给出了没有它的相同列表:
my $int-strs = $allomorphs.grep: IntStr, :v; # same thing
The :k
adverb (for key) gives the positions of the matching elements. This returns 1
because that’s the index of the matching element:
:k
副词(用于键)给出匹配元素的位置。这返回 1
,因为这是匹配元素的索引:
my $int-strs = $allomorphs.grep: ComplexStr, :k; # (1)
You can get both the key and the value with :kv
. You get a flat [List
](https://docs.raku.org/type/List.html) in key-value order:
你可以使用 :kv
获取键和值。你可以按键值顺序获得一个平面[列表
](https://docs.raku.org/type/List.html):
my $int-strs = $allomorphs.grep: RatStr, :kv; # (2 3/4)
If multiple elements match you get a longer [Seq
](https://docs.raku.org/type/Seq.html). The even positions are still keys:
如果多个元素匹配,则获得更长的 [Seq
](https://docs.raku.org/type/Seq.html)。偶数位置仍是键:
$allomorphs.grep: { ! .does(Numeric) }, :kv; # (3 a 4 b)
There’s also a routine form of grep
. The [Positional
](https://docs.raku.org/type/Positional.html) comes after the matcher:
还有一种例程形式的 grep
。[Positional
](https://docs.raku.org/type/Positional.html) 在匹配器之后:
my $matched = grep IntStr, @$allomorphs;
3.8. Transforming a List
.map
creates a new [Seq
](https://docs.raku.org/type/Seq.html) based on an existing one by creating zero or more elements from each input element. Here’s an example that returns a [Seq
](https://docs.raku.org/type/Seq.html) of squares. .map
can take a [Block
](https://docs.raku.org/type/Block.html) or [WhateverCode
](https://docs.raku.org/type/WhateverCode.html) (although that’s a lot of `*`s):
.map`通过从每个输入元素创建零个或多个元素,基于现有的 [`Seq
](https://docs.raku.org/type/Seq.html) 创建新的 [Seq
](https://docs.raku.org/type/Seq.html)。这是一个返回平方的 [Seq
](https://docs.raku.org/type/Seq.html) 的示例。 .map
可以接收一个 [Block
](https://docs.raku.org/type/Block.html) 或 [WhateverCode
](https://docs.raku.org/type/WhateverCode.html)(虽然那有很多 *
号 ):
my $squares = (1..5).map: { $_ ** 2 }; # (0 1 4 9 16 25)
my $squares = (1..5).map: * ** 2;
There’s a routine version of map
that does the same :
有一个例程版本的 map
做同样的事情:
my $even-squares = map { $_ ** 2 }, @(1..5);
Perhaps you want to lowercase everything:
也许你想要将字符串都变成小写:
my $lowered = $words.map: *.lc;
You might return no output elements, but you can’t merely return the empty [List
](https://docs.raku.org/type/List.html) because it will show up as an element in the new [Seq
](https://docs.raku.org/type/Seq.html). In this example the |()
indicates an empty [List
](https://docs.raku.org/type/List.html) slipped into the bigger [List
](https://docs.raku.org/type/List.html):
你可能不返回任何输出元素,但你不能仅返回空[列表
](列表
(https://docs.raku.org/type/List.html)中的元素。在这个例子中,|()
表示一个空[列表
](列表(https://docs.raku.org/type/List.html):
my $even-squares = (0..9).map: { $_ %% 2 ?? $_**2 !! |() }; # (0 4 16 36 64)
You can use these methods together. This selects the even numbers then squares them:
你可以一起使用这些方法。这将选择偶数然后将它们平方:
my $squares = $allomorphs
.grep( { ! .does(Numeric) } )
.map( { $_ %% 2 ?? $_**2 !! |() } );
3.9. Sorting Lists
Often you want a list in some order. Perhaps that’s increasing numerical or alphabetic order, by the length of the [Str
](https://docs.raku.org/type/Str.html)s, or anything else that makes sense for you. You can do this with .sort
:
通常你想要一个按顺序排列的列表。也许以数字递增或字母顺序,按[字符串
](https://docs.raku.org/type/Str.html)的长度,或任何其他对你有意义的顺序。你可以使用 .sort
执行此操作:
my $sorted = ( 7, 5, 9, 3, 2 ).sort; # (2 3 5 7 9)
my $sorted = <p e r l 6>.sort; # (6 e l p r)
By default, .sort
compares each pair of elements with cmp
. If the two elements are numbers, it compares them as numbers. If it thinks they are [Str
](https://docs.raku.org/type/Str.html)s, it compares them as such. Here’s a [Str
](https://docs.raku.org/type/Str.html) comparison that may surprise you the first time you see it (and annoy you hereafter):
默认情况下,.sort
用 cmp
比较每对元素。如果这两个元素是数字,则将它们作为数字进行比较。如果它认为它们是[字符串
](字符串
(https://docs.raku.org/type/Str.html)比较,你可能会在第一次看到它时感到惊讶(并在此后惹恼你):
my $sorted = qw/1 11 10 101/.sort; # (1 10 101 11)
What happened? Since you constructed the list with qw
, you got a list of [Str
](https://docs.raku.org/type/Str.html) objects. These compare character by character, so the text 101
is “less than” the text 11
. This isn’t dictionary sorting, though. Try it with upper- and lowercase letters:
发生了什么?由于你使用 qw
构建了列表,因此你获得了[字符串
](https://docs.raku.org/type/Str.html)对象的列表。这些字符逐字符比较,因此文本`101` “小于”文本 11
。但这不是字典排序。尝试用大写和小写字母:
my $sorted = qw/a A b B c C/.sort;
Did you get what you expected? Some of you probably guessed incorrectly. The lowercase code points come after the uppercase ones, for they are greater:
你得到了你的期望吗?你们中的一些人可能猜错了。小写代码点位于大写代码之后,因为它们更大:
(A B C a b c)
cmp
sorts by the code number in the Universal Character Set (UCS). If you are used to ASCII, the code number is the same thing. Above ASCII, you may not get what you expect.
`cmp `按通用字符集(UCS)中的代码编号排序。如果你习惯使用 ASCII,则代码编号是相同的东西。在 ASCII 之上,你可能无法得到你期望的结果。
There is a .collate method that can handle Unicode collation for language-specific sorting, but it’s experimental.
|
You can tell .sort
how to compare the elements. For dictionary order (so case does not matter), you have to do a bit more work. The .sort
method can take a routine that decides how to sort. Start with the default `.sort`fully written out with its comparison:
有一种 .collate
方法可以处理特定于语言的排序的 Unicode 排序规则,但它是实验性的。
你可以告诉 .sort
如何比较元素。对于字典顺序(所以大小写无关紧要),你必须做更多的工作。 .sort
方法可以采用一个例程来决定如何排序。从默认的 .sort
完整写出来与其比较开始:
my $sorted = qw/a A b B c C/.sort: { $^a cmp $^b }
You can also write that with two [Whatever
](https://docs.raku.org/type/Whatever.html)s so you don’t have to type the braces. This is the same thing:
你也可以使用两个 [Whatever
](https://docs.raku.org/type/Whatever.html) 来编写它,这样你就不必键入花括号。这是一样的:
my $sorted = qw/a A b B c C/.sort: * cmp *
If you want to compare them case insensitively, you can call the .fc
method to do a proper case folding:
如果你想不区分大小写的比较它们,你可以调用 .fc
方法进行正确的大小写折叠:
my $sorted = qw/a A b B c C/.sort: *.fc cmp *.fc
Now you get the order that ignores case:
现在你得到忽略大小写的顺序:
(A a B b C c)
However, if you want to make the same transformation on both elements, you don’t need to write it twice; .sort
will figure it out. It saves the result and reuses it for all comparisons. This means that Raku has a builtinSchwartzian transform (a Perl 5 idiom for a cached-key sort)!
但是,如果要对两个元素进行相同的转换,则不需要将其写两次; .sort
会弄清楚的。它保存结果并重复使用它进行所有比较。这意味着 Raku 具有内置的 Schwartzian 变换(用于缓存按键排序的 Perl 5 惯用法)!
my $sorted = qw/a A b B c C/.sort: *.fc;
There’s a problem with cmp
, though. The order of elements you get depends on the type and order of elements in your input:
但是 cmp
存在问题。你获得的元素的顺序取决于输入中元素的类型和顺序:
for ^5 {
my @numbers = (1, 2, 11, '21', 111, 213, '7', 77).pick: *;
say @numbers.sort;
}
The .pick
method randomly chooses from the [List
](https://docs.raku.org/type/List.html) the number of elements you specify. The *
translates to the number of elements in the [List
](https://docs.raku.org/type/List.html). The effect is a shuffled [List
](https://docs.raku.org/type/List.html) of the same elements. Some of these are [Int
](https://docs.raku.org/type/Int.html)s and some are [Str
](https://docs.raku.org/type/Str.html)s. Depending on which element shows up where, they sort differently:
.pick
方法从[列表
](https://docs.raku.org/type/List.html)中随机选择你指定的元素数。 *
转换为[列表
](整数
(字符串
(https://docs.raku.org/type/Str.html)。根据哪个元素显示在哪里,它们的排序方式不同:
(1 2 11 111 21 77 213 7)
(1 2 11 111 21 213 7 77)
(1 2 11 21 77 111 213 7)
(1 2 11 111 21 7 77 213)
(1 2 11 21 77 111 213 7)
Use leg
(*l*ess-*e*qual-*g*reater) if you want to order these by their [Str
](https://docs.raku.org/type/Str.html) values every time:
如果你希望每次按[字符串
](https://docs.raku.org/type/Str.html)值排序,请使用 leg
(小于等于大于):
say @numbers.sort: * leg *;
If you want numbers, use <⇒
:
如果你想按数字值排序,请使用 <⇒
:
say @numbers.sort: * <=> *;
Alternatively, you can coerce the input to the type you want:
或者,你可以将输入强制转换为所需的类型:
say @numbers.sort: +*; # numbers
say @numbers.sort: ~*; # strings
Finally, there’s a routine version of .sort
. It has a single-argument form that takes a [List
](https://docs.raku.org/type/List.html) and a two-argument form that takes a sort routine and a [List
](https://docs.raku.org/type/List.html):
最后,还有 .sort
的例程版本。它有一个单参数形式,它接受一个[列表
](列表
(https://docs.raku.org/type/List.html)的双参数形式:
my $sorted = sort $list;
my $sorted = sort *.fc, $list;
EXERCISE 6.14Represent a deck of cards as a [List
](https://docs.raku.org/type/List.html) of [List
](https://docs.raku.org/type/List.html)s. Create five poker hands of five cards from that. Output the cards in ascending order of their ranks.
练习6.14将一副牌作为[列表
](列表
(https://docs.raku.org/type/List.html)。从中创建五手五张牌。按名次的升序输出卡片。
3.10. Sorting on Multiple Comparisons
You can use a [Block
](https://docs.raku.org/type/Block.html) to create more complicated comparisons, comparing two things that are the same in one regard in another way. When two people’s lasts names are the same, you can sort by the first name. If you sort these with the default .sort
you probably won’t get what you want:
你可以使用[Block
](https://docs.raku.org/type/Block.html)来创建更复杂的比较,以另一种方式比较两个在一个方面相同的事物。当两个人的姓名相同时,你可以按名字排序。如果你使用默认的 .sort
对它们进行排序,你可能无法获得所需内容:
my @butterflies = (
<John Smith>,
<Jane Smith>,
<John Doe>,
<Jon Smithers>,
<Jim Schmidt>,
);
my @sorted = @butterflies.sort;
put @sorted.join: "\n";
This comes out in alphabetical order, if you consider the [Str
](https://docs.raku.org/type/Str.html) to be the combination of the sublist elements as a single [Str
](https://docs.raku.org/type/Str.html):
Jane Smith
Jim Schmidt
John Doe
John Smith
Jon Smithers
Change the sort to work only on the second element of each sublist:
将排序更改为仅适用于每个子列表的第二个元素:
my @sorted = @butterflies.sort: *.[1];
put @sorted.join: "\n";
The last names sort in alphabetical order now, but the first names show up out of order (though that may depend on the ordering of your input):
姓氏现在按字母顺序排序,但名字显示不按顺序排列(尽管这可能取决于输入的顺序):
John Doe
Jim Schmidt
John Smith
Jane Smith
Jon Smithers
A more complex comparison can fix that. In each sublist, compare the last names to each other. If they are the same, add another comparison with the logical or
:
更复杂的比较可以解决这个问题。在每个子列表中,将姓氏相互比较。如果它们相同,则添加另一个与逻辑 or
的比较:
my @sorted = @butterflies.sort: {
$^a.[1] leg $^b.[1] # last name
or
$^a.[0] leg $^b.[0] # first name
};
When it compares the sublists for (John Smith)
and (Jane Smith)
it tries the last names and finds that they are the same. It then sorts on the first names and produces the result that you probably want:
当它比较(John Smith
)和(Jane Smith
)的子列表时,它会尝试姓氏并发现它们是相同的。然后它对名字进行排序并产生你可能想要的结果:
John Doe
Jim Schmidt
Jane Smith
John Smith
Jon Smithers
EXERCISE 6.15 Create a deck of cards and create five hands of five cards each. In each hand sort the cards by their rank. If the ranks are the same sort them by their suits.
练习6.15 创建一副牌并创建五手五张牌。每手按照等级对牌进行排序。如果排名与他们的花色相同。
3.11. Summary
The [List
](https://docs.raku.org/type/List.html), [Range
](https://docs.raku.org/type/Range.html), and [Array
](https://docs.raku.org/type/Array.html) types are [Positional
](https://docs.raku.org/type/Positional.html)s, and the [Seq
](https://docs.raku.org/type/Seq.html) type can fake it when it needs to. This allows some amazing lazy features where you don’t have to do anything until you actually need it. Not only that, but with a little practice you won’t even need to think about it.
Once you have your data structures, you have some powerful ways to combine them to make much more complex data structures. Some of these may be daunting at first. Don’t ignore them. You’ll find that your programming career will be easier with judiciously chosen structures that are easy to manipulate.
[List
](https://docs.raku.org/type/List.html), [Range
](https://docs.raku.org/type/Range.html), 和 [Array
](https://docs.raku.org/type/Array.html) 类型是 [Positional
](Seq
(https://docs.raku.org/type/Seq.html) 类型可以在需要时伪造它。这允许一些惊人的惰性功能,你不需要做任何事情,直到你真正需要它。不仅如此,通过一些练习,你甚至不需要考虑它。
拥有数据结构后,你可以通过一些强大的方法将它们组合在一起,从而构建更复杂的数据结构。其中一些可能一开始令人生畏。不要忽视它们。你会发现,通过易于操作的明智选择的结构,你的编程生涯将变得更加容易。
4. 当出错的时候
Raku doesn’t always immediately give up when something goes wrong. It can fail softly. If the result of that problem doesn’t affect anything else in the program there’s no need to complain about it. However, the moment it becomes a problem that passive failure demands your attention.
This chapter shows you the error mechanisms and how to deal with them. You’ll see how to handle the problems that your program notices on your behalf as well as detect and report problems on your own.
当出现问题时,Raku 并不总是立即放弃。它可能会轻轻失败。如果该问题的结果不影响程序中的任何其他内容,则无需抱怨它。然而,当它成为问题的那一刻被动失败就要引起你的注意了。
本章向你介绍错误机制以及如何处理它们。你将看到如何处理你的程序以你的名义注意到的问题,以及如何自行检测和报告问题。
4.1. 异常
Here’s a bit of code that tries to convert nonnumeric text into a number. Maybe something else didn’t put the right value in the variable:
这里有一些代码试图将非数字文本转换为数字。也许其他东西没有在变量中放入正确的值:
my $m = 'Hello';
my $value = +$m;
put 'Hello there!'; # no error, so, works?
Your program doesn’t complain because you don’t do anything with the problematic result. Change the program to output the result of what you think was a numeric conversion:
你的程序没有抱怨,因为你没有对有问题的结果做任何事情。更改程序以输出你认为是数字转换的结果:
my $m = 'Hello';
my $value = +$m;
put $value;
Now you get some error output instead:
现在你得到一些错误输出:
Cannot convert string to number: base-10 number must
begin with valid digits or '.' in '⏏Hello' (indicated by ⏏)
in block <unit> at ... line 2
Actually thrown at:
in block <unit> at ... line 3
Look at that error message. It reports two line numbers. The error occurred on line 2, but it wasn’t until line 3 (the one with put
) that it became a problem. That’s the soft failure. What’s actually in $result
? It’s a [Failure
](https://docs.raku.org/type/Failure.html) object:
看看那条错误信息。它报告两个行号。错误发生在第 2 行,但直到第 3 行(带有 put
的那行)才成为问题。那是软失败。 $result
中的实际内容是什么?这是一个 [Failure
](https://docs.raku.org/type/Failure.html) 对象:
my $m = 'Hello';
my $value = +$m;
put "type is {$value.^name}"; # type is Failure
These soft failures can be quite handy in cases where you don’t care if something didn’t work. If you can’t log a message because the logger is broken what are you going to do about it? Log the failure? Similarly, sometimes you don’t care if something fails because that might be a common case. It’s up to you to make those decisions, though.
The [Failure
](https://docs.raku.org/type/Failure.html) is really a wrapper around an [Exception
](https://docs.raku.org/type/Exception.html), so you need to know about exceptions first.
如果你不关心某些事情是否有效,这些软故障可能非常方便。如果由于记录器坏了而无法记录消息,你打算怎么做呢?记录失败?同样,有时你不关心某些事情是否失败,因为这可能是一种常见的情况。不过,由你决定做出决定。
[Failure
](https://docs.raku.org/type/Failure.html) 实际上是[Exception
](https://docs.raku.org/type/Exception.html)的包装,因此你首先需要了解异常。
4.1.1. 捕获异常
Something that wants to report an exception throws it. You’d say “the subroutine threw an exception.” Some people might say it “raised an exception.” It’s the same thing. If you don’t handle an exception it stops your program.
A try
wraps some code and can catch an [Exception
](https://docs.raku.org/type/Exception.html). If it catches an [Exception
](https://docs.raku.org/type/Exception.html) it puts it into the $!
special variable:
想要报告异常的东西会抛出它。你会说“子程序抛出了异常。” 有些人可能会说它“引发了异常。”这是同样的事情。如果你不处理异常,它将停止你的程序。
try
包装一些代码并可以捕获[异常
](https://docs.raku.org/type/Exception.html)。如果它捕获一个 [异常
](https://docs.raku.org/type/Exception.html),它会将异常放入 $!
特殊变量:
try {
my $m = 'Hello';
my $value = +$m;
put "value is {$value.^name}";
}
put "ERROR: $!" if $!;
put 'Got to the end.';
You catch the [Exception
](https://docs.raku.org/type/Exception.html) and your program continues:
ERROR: Cannot convert string to number
Got to the end.
With a single line you don’t need the braces:
使用单行你不需要大括号:
my $m = 'Hello';
try my $value = +$m;
If the code succeeds try
gives back the value. You can move the try
to the other side of the assignment:
如果代码成功,则 try
返回值。你可以将 try
移动到赋值的另一侧:
my $m = 'Hello';
my $value = try +$m;
Most [Exception
](https://docs.raku.org/type/Exception.html) types are under X
and inherit from [Exception
](https://docs.raku.org/type/Exception.html)—in this example it’s`X::Str::Numeric`:
大多数 [Exception
](https://docs.raku.org/type/Exception.html) 类型都在 X
下,并且从 [Exception
](https://docs.raku.org/type/Exception.html) 继承 - 在这个例子中,它是 X::Str::Numeric
:
put "Exception type is {$!.^name}";
If there was no [Exception
](https://docs.raku.org/type/Exception.html), the thingy in $!
is [Any
](https://docs.raku.org/type/Any.html). This is a bit annoying because [Exception
](https://docs.raku.org/type/Exception.html) inherits from [Any
](https://docs.raku.org/type/Any.html) too. The $!
in a condition is defined if there was a problem. Use it with given
and smart match against the types you want to handle. A default
[Block
](https://docs.raku.org/type/Block.html) handles anything you don’t:
如果没有 [Exception
](https://docs.raku.org/type/Exception.html),$!
里面的东西就是 [Any
](https://docs.raku.org/type/Any.html)。这有点烦人,因为 [Exception
](https://docs.raku.org/type/Exception.html) 也继承了 [Any
](https://docs.raku.org/type/Any.html)。 如果出现问题,$!
就处于定义状态。使用 given
和智能匹配你要处理的类型。default
[Block
](https://docs.raku.org/type/Block.html) 处理你不需要的任何内容:
put 'Problem was ', do given $! {
when X::Str::Numeric { ... }
default { ... }
};
[Exception
](https://docs.raku.org/type/Exception.html) types use different methods to give you more information. Each type defines a method that makes sense for its error. Look at each type to see which information it captures for you. The [X::Str::Numeric
](Exception
(https://docs.raku.org/type/Exception.html) type knows at which position in the [Str
](https://docs.raku.org/type/Str.html) it discovered the problem:
[Exception
](https://docs.raku.org/type/Exception.html)类型使用不同的方法为你提供更多信息。每种类型都定义了一种对其错误有意义的方法。查看每种类型以查看它为你捕获的信息。 [X::Str::Numeric
](https://docs.raku.org/type/X::Str::Numeric%20) [Exception
](字符串
(https://docs.raku.org/type/Str.html)中发现问题的位置:
put 'Problem was ', do given $! {
when X::Str::Numeric { "Char at {.pos} is not numeric" }
when X::Numeric::Real { "Trying to convert to {.target}" }
default { ... }
};
The $!
and given
happen outside of the try
. A CATCH
[Block
](https://docs.raku.org/type/Block.html) inside the try
can do the same thing. The [X::Str::Numeric
](https://docs.raku.org/type/X::Str::Numeric%20) [Exception
](https://docs.raku.org/type/Exception.html) shows up in $_
:
$!
和 given
发生在 try
之外。 try
中的 CATCH
[块
](https://docs.raku.org/type/Block.html)可以做同样的事情。 [X::Str::Numeric
](https://docs.raku.org/type/X::Str::Numeric%20) [Exception
](https://docs.raku.org/type/Exception.html) 出现在 $_
中:
try {
CATCH {
when X::Str::Numeric { put "ERROR: {.reason}" }
default { put "Caught {.^name}" }
}
my $m = 'Hello';
my $value = +$m;
put "value is {$value.^name}";
}
put 'Got to the end.';
The X::Str::Numeric
[Exception
](https://docs.raku.org/type/Exception.html) is thrown at the line my $value = +$m;
, then it skips the rest of the [Block
](https://docs.raku.org/type/Block.html). Handle this by outputting an error and continuing with the program:
X::Str::Numeric
[Exception
](https://docs.raku.org/type/Exception.html) 在 my $value =+ $m;
这一行被抛出,然后跳过[块
](https://docs.raku.org/type/Block.html)的其余部分。通过输出错误并继续执行程序来处理:
ERROR: base-10 number must begin with valid digits or '.'
Got to the end.
Most of these objects inherit from [Exception
](https://docs.raku.org/type/Exception.html) and have a .message
method that provides more information. Catch those with default
where you can output the name of the type:
这些对象中的大多数都继承自[Exception
](https://docs.raku.org/type/Exception.html),并且具有提供更多信息的 .message
方法。使用 default
捕获那些异常,你可以输出类型的名称:
try {
CATCH {
default { put "Caught {.^name} with 「{.message}」" }
}
my $m = 'Hello';
my $value = +$m;
put "value is {$value.^name}";
}
put "Got to the end.";
Now the output has the same message that the unhandled error showed you:
现在输出与未处理的错误显示的消息相同:
Caught X::Str::Numeric with 「Cannot convert string to number:
base-10 number must begin with valid digits or '.' in '⏏Hello'
(indicated by ⏏)」
Got to the end.
EXERCISE 7.1Divide a number by zero. What [Exception
](https://docs.raku.org/type/Exception.html) type do you get?
练习7.1 将数字除以零。你得到什么 [Exception
](https://docs.raku.org/type/Exception.html) 类型?
4.1.2. Backtraces
The [Exception
](https://docs.raku.org/type/Exception.html) contains a [Backtrace
](https://docs.raku.org/type/Backtrace.html) object that documents the path of the error. This example has three levels of subroutine calls with some code at the end that throws an [Exception
](https://docs.raku.org/type/Exception.html):
[Exception
](https://docs.raku.org/type/Exception.html) 包含一个 [Backtrace
](https://docs.raku.org/type/Backtrace.html) 对象,用于记录错误的路径。此示例有三个级别的子例程调用,最后的一些代码抛出[异常
](https://docs.raku.org/type/Exception.html):
sub top { middle() }
sub middle { bottom() }
sub bottom { 5 + "Hello" }
top();
You don’t handle the [Exception
](https://docs.raku.org/type/Exception.html) in middle
, you don’t handle it in top
, and finally, you don’t handle it at the top level. The [Exception
](https://docs.raku.org/type/Exception.html) complains and shows you its path through the code:
你不在 middle
中处理[异常
](https://docs.raku.org/type/Exception.html),你不在顶部处理它,最后,你不在 top
中处理它。 [Exception
](https://docs.raku.org/type/Exception.html) 会抱怨并向你展示其代码的路径:
Cannot convert string to number: base-10 number must
begin with valid digits or '.' in '⏏Hello' (indicated by ⏏)
in sub bottom at backtrace.p6 line 3
in sub middle at backtrace.p6 line 2
in sub top at backtrace.p6 line 1
in block <unit> at backtrace.p6 line 5
Actually thrown at:
in sub bottom at backtrace.p6 line 3
in sub middle at backtrace.p6 line 2
in sub top at backtrace.p6 line 1
in block <unit> at backtrace.p6 line 5
Don’t fret when you see long messages like this. Find the first level of the error and think about that. Work your way through the chain one level at a time. Often a simple fix makes it all go away.
You can handle the [Exception
](https://docs.raku.org/type/Exception.html) anywhere in that chain. The simplest option might be to wrap the call to bottom
in a try
. With a single-line expression you can omit the [Block
](https://docs.raku.org/type/Block.html) around the code. In middle
, you don’t specify a CATCH
so there’s a default handler that discards the [Exception
](https://docs.raku.org/type/Exception.html):
当你看到像这样的长信息时,不要担心。找出错误的第一级并考虑一下。一次一个层次地通过链条。通常一个简单的修复使它全部消失。
你可以在该链中的任何位置处理[异常
](https://docs.raku.org/type/Exception.html)。最简单的选择可能是在 try
中将调用包装到 bottom
。使用单行表达式,你可以省略代码周围的[块
](https://docs.raku.org/type/Block.html)。在 middle
中,你没有指定 CATCH
,因此有一个默认处理程序可以丢弃[Exception
](https://docs.raku.org/type/Exception.html):
sub top { middle() }
sub middle { try bottom() }
sub bottom { 137 + 'Hello' }
put top();
That program doesn’t produce an error (or any output). That’s probably not what you want. The middle layer can handle the case where you can’t convert a [Str
](https://docs.raku.org/type/Str.html) to a number by returning the special number NaN
(for “not a number”):
该程序不会产生错误(或任何输出)。这可能不是你想要的。中间层可以通过返回特殊数字 NaN
(意思是“非数字”)来处理无法将[字符串
](https://docs.raku.org/type/Str.html)转换为数字的情况:
sub top { middle() }
sub middle {
try {
CATCH { when X::Str::Numeric { return NaN } }
bottom()
}
}
sub bottom { 137 + 'Hello' }
put top();
Change the code to make a different error that the CATCH
in middle
doesn’t handle. Try to divide by zero and convert the result to a [Str
](https://docs.raku.org/type/Str.html):
更改代码以产生 middle
中的 CATCH
无法处理的其他错误。尝试除以零并将结果转换为[字符串
](https://docs.raku.org/type/Str.html):
sub top { middle() }
sub middle {
try {
CATCH { when X::Str::Numeric { return NaN } }
bottom()
}
}
sub bottom { ( 137 / 0 ).Str }
put top();
The CATCH
in middle
doesn’t handle this new type of error, so the [Exception
](https://docs.raku.org/type/Exception.html) interrupts the program at the call to top
:
middle
中的 CATCH
不处理这种新类型的错误,因此[Exception
](https://docs.raku.org/type/Exception.html)在调用 top
时中断程序:
Attempt to divide 137 by zero using div
in sub bottom at nan.p6 line 12
in sub middle at nan.p6 line 8
in sub top at nan.p6 line 4
in block <unit> at nan.p6 line 14
Catch this one inside top
. The [Exception
](https://docs.raku.org/type/Exception.html) passes through middle
, which has nothing to handle it. Since nothing handles that error it continues up the chain and ends up in top
, which handles it by returning the special value Inf
(for infinity):
在 top
里面捕获这个异常。[Exception
](https://docs.raku.org/type/Exception.html)通过 middle
,没有任何处理它。由于没有处理该错误,它会继续向上链接并最终到达 top
,通过返回特殊值 Inf
(意思是无穷大)来处理它:
sub top {
try {
CATCH {
when X::Numeric::DivideByZero { return Inf }
}
middle()
}
}
sub middle {
try {
CATCH {
when X::Str::Numeric { return NaN }
}
bottom()
}
}
sub bottom { ( 137 / 0 ).Str }
put top();
Extend this process as far up the chain as you like. The next example changes the error by trying to call an undefined method on 137
:
根据你的喜好将这个过程延伸到链上。下一个示例通过尝试在 137
上调用未定义的方法来更改错误:
sub top {
try {
CATCH {
when X::Numeric::DivideByZero { return Inf }
}
middle()
}
}
sub middle {
try {
CATCH {
when X::Str::Numeric { return NaN }
}
bottom()
}
}
sub bottom { 137.unknown-method }
try {
CATCH {
default { put "Uncaught exception {.^name}" }
}
top();
}
That’s a new sort of error that you don’t handle so far:
到目前为止,这是一种新的错误:
Uncaught exception X::Method::NotFound
Sometimes you don’t care about unfound methods. If it’s there you call it and if not you want to ignore it. There’s special syntax for this. If you place a ?
after the method call dot, you don’t get an [Exception
](https://docs.raku.org/type/Exception.html) if the method is not found:
有时你不关心未找到的方法。如果它在那里你就调用它,如果不是你就忽略它。这有特殊的语法。如果在方法调用点之后你放一个 ?
,如果找不到该方法,则不会出现[异常
](https://docs.raku.org/type/Exception.html):
sub bottom { 137.?unknown-method }
4.1.3. Rethrowing Errors
It gets better. You can catch an exception but not handle it. Modify the CATCH
in middle
to intercept X::Method::NotFound
and output a message, then .rethrow
it:
它变得更好了。你可以捕获异常但不处理它。修改 middle
里面的 CATCH
以拦截 X::Method::NotFound
并输出一条消息,然后 .rethrow
它:
sub top {
try {
CATCH {
when X::Numeric::DivideByZero { return Inf }
}
middle()
}
}
sub middle {
try {
CATCH {
when X::Str::Numeric { return NaN }
when X::Method::NotFound {
put "What happened?";
.rethrow
}
}
bottom()
}
}
sub bottom { 137.unknown-method }
try {
CATCH {
default { put "Uncaught exception {.^name}" }
}
top();
}
You can see that middle
was able to do its work but the [Exception
](https://docs.raku.org/type/Exception.html) was ultimately handled by top
:
你可以看到 middle
能够完成它的工作,但是[Exception
](https://docs.raku.org/type/Exception.html)最终由 top
处理:
What happened?
Uncaught exception X::Method::NotFound
EXERCISE 7.2Implement a subroutine whose only code is …
. That denotes code that you intend to fill in later. Call that from another subroutine and catch the [Exception
](https://docs.raku.org/type/Exception.html). What’s the type you get? Can you output your own [Backtrace
](https://docs.raku.org/type/Backtrace.html)?
练习7.2 实现一个子程序,其唯一的代码是 …
。这表示你打算稍后填写的代码。从另一个子例程调用它并捕获[异常
](Backtrace
(https://docs.raku.org/type/Backtrace.html)吗?
4.1.4. Throwing Your Own Exceptions
Up to now you’ve seen exceptions that come from problems in the source code. Those are easy to see without complicating the examples. You can also throw your own [Exception
](https://docs.raku.org/type/Exception.html)s. The easiest way is to use die
with a [Str
](https://docs.raku.org/type/Str.html) argument:
到目前为止,你已经看到源代码中存在问题的异常。这些很容易看到,而不会使示例复杂化。你也可以抛出自己的[Exception
](字符串
(https://docs.raku.org/type/Str.html)参数的 die
:
die 'Something went wrong!';
The die
subroutine takes the [Str
](https://docs.raku.org/type/Str.html) as the message for an [Exception
](https://docs.raku.org/type/Exception.html) of type [X::AdHoc
](https://docs.raku.org/type/X::AdHoc.html). That’s a catch-all type for anything that doesn’t have a more appropriate type.
Dying with a [Str
](https://docs.raku.org/type/Str.html) is the same as constructing an [X::AdHoc
](https://docs.raku.org/type/X::AdHoc.html). You can die
with a particular [Exception
](https://docs.raku.org/type/Exception.html) type by constructing it yourself:
die
子例程将[字符串
](https://docs.raku.org/type/Str.html)作为 [X::AdHoc
](https://docs.raku.org/type/X::AdHoc.html) 类型的[异常
](https://docs.raku.org/type/Exception.html)的消息。对于没有更合适类型的任何东西来说,这是一种全能类型。
die X::AdHoc.new( payload => "Something went wrong!" );
You’re actually creating a [Pair ](https://docs.raku.org/type/Pair.html) here, but you won’t see the ⇒ until [Chapter 9](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch09.html#camelia-hashes) or named parameters until [Chapter 11](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch11.html#camelia-subroutines), so take this on faith.
|
The die
is important. Merely constructing the [Exception
](https://docs.raku.org/type/Exception.html) does not throw it:
你实际上是在这里创建一个[Pair
](https://docs.raku.org/type/Pair.html),但在第11章之前你不会看到 ⇒
或者直到第11章,所以请相信这一点。
die
很重要。仅仅构建[Exception
](https://docs.raku.org/type/Exception.html)并不会抛出它:
# nothing happens
X::AdHoc.new( payload => "Something went wrong!" );
You can .throw
it yourself if you like, though. This is the same as die
:
不过,如果你愿意,你可以自己 .throw
。这跟 die
是一样的:
X::AdHoc
.new( payload => "Something went wrong!" )
.throw;
You can also create [Exception
](https://docs.raku.org/type/Exception.html)s of other predefined types. The [X::NYI
](https://docs.raku.org/type/X::NYI.html) type is for features not yetimplemented:
你还可以创建其他预定义类型的 [Exception
](https://docs.raku.org/type/Exception.html)。 [X::NYI
](https://docs.raku.org/type/X::NYI.html) 类型用于未实现的功能:
X::NYI.new: features => 'Something I haven't done yet';
EXERCISE 7.3Modify the previous exercise to die
with a [Str
](https://docs.raku.org/type/Str.html) argument. What type do you catch? Further modify that to die
with an X::StubCode
object that you construct yourself.
练习7.3 使用[字符串
](https://docs.raku.org/type/Str.html)参数修改上一个练习。你捕获了什么类型?使用你自己构建的 X::StubCode
对象进一步修改它。
4.1.5. Defining Your Own Exception Types
It’s a bit early to create subclasses—you’ll see how to do that in [Chapter 12](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch12.html#camelia-classes)—but with a little faith you can do this right away. Base your class on [Exception
](https://docs.raku.org/type/Exception.html) without doing anything else:
创建子类有点早 - 你会在第12章看到如何做到这一点 - 但有一点信心你可以马上做到这一点。将你的类基于[Exception
](https://docs.raku.org/type/Exception.html)而不做任何其他事情:
class X::MyException is Exception {}
sub my-own-error {
die X::MyException.new: payload => 'I did this';
}
my-own-error();
When you run the my-own-error
subroutine it dies with the new error type that you’ve defined:
当你运行 my-own-error
子例程时,它会以你定义的新错误类型失败:
Died with X::MyException
Now that your new type exists you can use it in a CATCH
(or smart match). Even without any sort of customization its name is enough to tell you what happened:
既然你的新类型存在,你可以在 CATCH
(或智能匹配)中使用它。即使没有任何自定义,它的名字也足以告诉你发生了什么:
try {
CATCH {
when X::MyException { put 'Caught a custom error' }
}
my-own-error();
}
[Chapter 12](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch12.html#camelia-classes) will cover class creation and show you more about what you can do inside a class and how to reuse existing classes.
第12章将涵盖类的创建,并向你展示更多关于你可以在类中做什么以及如何重用现有类。
4.2. Failures
[Failure
](https://docs.raku.org/type/Failure.html)s are wrappers around unthrown [Exception
](https://docs.raku.org/type/Exception.html)s. They’re passive until you try to use them later—hence “soft” exceptions. They don’t interrupt your program until something tries to use them as a normal value. It’s then that they throw their [Exception
](https://docs.raku.org/type/Exception.html)s.
As a Boolean a [Failure
](https://docs.raku.org/type/Failure.html) is always False
. You can “disarm” the [Failure
](https://docs.raku.org/type/Failure.html) by checking it. That might be with an if
, with a logical test, or by Booleanizing it with .so
or ?
. All of those mark the [Failure
](https://docs.raku.org/type/Failure.html) as handled and prevent it from implicitly throwing its [Exception
](https://docs.raku.org/type/Exception.html):
[Failure
](https://docs.raku.org/type/Failure.html) 是围绕unthrown [Exception
](https://docs.raku.org/type/Exception.html) 的包装器。它们是被动的,直到你以后尝试使用它们 - 因此是“软”异常。在尝试将它们用作正常值之前,它们不会中断你的程序。然后,他们抛出了他们的异常。
作为布尔值,[Failure
](https://docs.raku.org/type/Failure.html) 始终为 False
。你可以通过检查来“解除”[Failure
](https://docs.raku.org/type/Failure.html)。这可能是使用 if
,进行逻辑测试,或者使用 .so
或 ?
进行布尔化。所有这些都将[Failure
](https://docs.raku.org/type/Failure.html)标记为已处理并阻止它隐式抛出其异常:
my $result = do-something();
if $result { ... }
my $did-it-work = ?$results;
A [Failure
](https://docs.raku.org/type/Failure.html) is always undefined; maybe you want to set a default value if you encounter one:
[Failure
](https://docs.raku.org/type/Failure.html) 总是未定义的;也许你想要设置一个默认值,如果你遇到一个:
my $other-result = $result // 0;
You can handle a [Failure
](https://docs.raku.org/type/Failure.html) yourself without the try
. The .exception
method extracts that object so you can inspect it:
没有 try
你也可以自己处理[Failure
](https://docs.raku.org/type/Failure.html)。 .exception
方法提取该对象,以便你可以检查它:
unless $result {
given $result.exception {
when X::AdHoc { ... }
default { ... }
}
}
Create your own [Failure
](https://docs.raku.org/type/Failure.html)s by substituting die
with fail
:
通过将 die
替换为 fail
来创建自己的[Failure
](https://docs.raku.org/type/Failure.html):
fail "This ends up as an X::AdHoc";
fail My::X::SomeException.new(
:payload( 'Something wonderful' ) );
When you use fail
in a subroutine the [Failure
](https://docs.raku.org/type/Failure.html) object becomes the return value. Instead of die
-ing you should probably use fail
so that the programmers who use your code can decide for themselves how to handle the problem.
EXERCISE 7.4Create a subroutine that takes two arguments and returns their sum. If either argument is not a number return a [Failure
](https://docs.raku.org/type/Failure.html) object saying so. How would you handle the [Failure
](https://docs.raku.org/type/Failure.html)?
在子例程中使用 fail
时,[Failure
](https://docs.raku.org/type/Failure.html) 对象将成为返回值。你应该使用 fail
而不是 die
,以便使用你的代码的程序员可以自己决定如何处理问题。
练习7.4 创建一个带有两个参数并返回其总和的子程序。如果任一参数不是数字,则返回[Failure
](Failure
(https://docs.raku.org/type/Failure.html)?
4.3. Warnings
Instead of die
-ing you can use warn
to give the same message without stopping the program or forcing the program to catch the [Exception
](https://docs.raku.org/type/Exception.html):
你可以使用 warn
而不是 die
来提供相同的消息,而无需停止程序或强制程序捕获异常:
warn 'Something funny is going on!';
Warnings are a type of [Exception
](https://docs.raku.org/type/Exception.html) and you can catch them. They aren’t the same type of exception so they don’t show up in a CATCH
[Block
](https://docs.raku.org/type/Block.html). They are control exceptions that you catch in a CONTROL
[Block
](https://docs.raku.org/type/Block.html):
警告是一种[异常
](https://docs.raku.org/type/Exception.html),你可以捕获它们。它们不是同一类型的异常,因此它们不会出现在 CATCH
[块
](https://docs.raku.org/type/Block.html)中。它们是你在CONTROL [块
](https://docs.raku.org/type/Block.html)中捕获的控制异常:
try {
CONTROL {
put "Caught an exception, in the try";
put .^name;
}
do-that-thing-you-do();
}
sub do-that-thing-you-do {
CONTROL {
put "Caught an exception, in the sub";
put .^name;
}
warn "This is a warning";
}
If you don’t care about the warnings (they are annoying after all) you can wrap the annoying code in a quietly
[Block
](https://docs.raku.org/type/Block.html):
如果你不关心警告(毕竟它们很烦人),你可以将烦人的代码包装在一个 quietly
[块
](https://docs.raku.org/type/Block.html)中:
quietly {
do-that-thing-you-do();
}
EXERCISE 7.5Modify the previous exercise to warn
for each argument that cannot be converted to a number. Once you’ve seen those warnings, further modify the program to ignore the warnings.
练习7.5 修改上一个练习,以警告(warn
)每个无法转换为数字的参数。一旦看到这些警告,进一步修改程序以忽略警告。
4.4. The Wisdom of Exceptions
[Exception
](https://docs.raku.org/type/Exception.html)s can be a contentious subject. Some people love them and some hate them. Since they are a feature, you need to know about them. I want to leave you with some words of caution before you get yourself in too much trouble. Your love/hate relationship with [Exception
](https://docs.raku.org/type/Exception.html)s will most likely fluctuate over your career.
[Exception
](https://docs.raku.org/type/Exception.html)s are a way of communicating information. By design this feature expects you to recognize the type of error and handle it appropriately. This implies that you can actually handle the error. If you encounter a situation that your program cannot correct, an [Exception
](https://docs.raku.org/type/Exception.html) might not be the appropriate feature to use.
Even if your program could correct the error, many people don’t expect most programmers to handle errors. Your [Exception
](https://docs.raku.org/type/Exception.html) may be a nuisance that they catch and ignore. Think about that before you spend too much time crafting fine-grained [Exception
](https://docs.raku.org/type/Exception.html) types that cover all situations.
As part of program flow [Exception
](https://docs.raku.org/type/Exception.html)s are really a fancy break mechanism. You’re in one bit of code, then suddenly in another. Those cases should truly be exceptional and rare. Anything else that you expect to happen you should handle with normal program flow.
That’s all I’ll say about that. Perhaps you have a different opinion. That’s fine. Read more about this on your own and judge your particular situation.
[Exception
](https://docs.raku.org/type/Exception.html) 可能是一个有争议的主题。有些人喜欢他们,有些人讨厌他们。由于它们是一个功能,你需要了解它们。在你遇到太多麻烦之前,我想给你一些谨慎的话。你与[异常
](https://docs.raku.org/type/Exception.html)的爱/恨关系很可能会在你的职业生涯中波动。
即使你的程序可以纠正错误,许多人也不希望大多数程序员处理错误。你的[异常
](https://docs.raku.org/type/Exception.html)可能是他们捕获和忽视的麻烦。在花费太多时间制作涵盖所有情况的细粒度异常类型之前,请考虑一下。
作为程序流程的一部分,[异常
](https://docs.raku.org/type/Exception.html)实际上是一种奇特的破坏机制。你在一点代码中,然后突然在另一个代码中。这些情况应该是特殊和罕见的。你期望发生的任何其他事情都应该处理正常的程序流程。
这就是我要说的全部内容。也许你有不同的意见。没关系。自己阅读更多相关信息并判断你的具体情况。
4.5. Summary
[Exception
](https://docs.raku.org/type/Exception.html)s are a feature of Raku, but it doesn’t hit you over the head with them. They can be soft failures until they would actually cause a problem.
[Exception
](https://docs.raku.org/type/Exception.html)是 Raku 的一个功能,但它并没有让你头脑发热。它们可能是软故障,直到它们实际上会导致问题。
Don’t become overwhelmed by the different types of [Exception
](https://docs.raku.org/type/Exception.html)s your program may report. You’ll continue to see these throughout the rest of the book. Use them appropriately (whatever definition you choose) and effectively to note problems in your program. Try to detect those as early as you can.
不要被你的程序可能报告的不同类型的[异常
](https://docs.raku.org/type/Exception.html)所淹没。在本书的其余部分中,你将继续看到这些内容。适当地使用它们(无论你选择何种定义)并有效地记录程序中的问题。尽可能早地检测这些。
== 文件和目录,输入和输出
读写文本是许多你想要编写的程序的基础。你将数据存储在文件中并稍后检索该数据。本章是关于你需要执行此操作的所有功能。在此过程中,你将看到如何处理文件路径,移动文件以及使用目录。大多数情况都是使用你已经看过的相同语法完成的,但现在使用不同对象的类型。
本章中的许多任务可能由于程序之外的原因而失败。如果你希望在不同的目录中工作或希望某个文件存在,那么如果这些条件不为真,你可能不希望继续。这只是一个处理外部资源的程序的事实。
4.6. 文件路径
[IO::Path
](https://docs.raku.org/type/IO::Path.html) 对象表示文件路径。它知道如何根据文件系统的规则组合和拆分路径。只要路径的形式符合这些规则,该路径与实际存在的文件无关。你马上会看到如何处理丢失的文件。现在,在任何[字符串
](https://docs.raku.org/type/Str.html)上调用 .IO
将其转换为 [IO::Path
](https://docs.raku.org/type/IO::Path.html) 对象:
my $unix-path = '/home'.IO;
my $windows-path = 'C:/Users'.IO;
要构建更深的路径,请使用 .add
。你一次可以有多个层级。 .add
不会改变原始对象;它为你提供了一个新对象:
my $home-directory = $unix-path.add: 'hamadryas';
my $file = $unix-path.add: 'hamadryas/file.txt';
如果要在那里构建路径,请赋值回原始对象:
$unix-path = $unix-path.add: 'hamadryas/file.txt';
二元赋值形式可能更有用:
$unix-path .= add: 'hamadryas/file.txt';
.basename
和 .parent
方法拆分路径:
my $home = '/home'.IO;
my $user = 'hamadryas'; # Str or IO::File will work
my $file = 'file.txt'.IO;
my $path = $home.add( $user ).add( $file );
put 'Basename: ', $path.basename; # Basename: file.txt
put 'Dirname: ', $path.parent; # Dirname: /home/hamadryas
.basename`返回一个[`字符串 ](https://docs.raku.org/type/Str.html),而不是另一个 [IO::Path ](https://docs.raku.org/type/IO::Path.html)。如果需要,可以再次使用 .IO 。
|
使用 .parent
,你可以决定向上升多少个层级:
my $home = '/home'.IO;
my $user = 'hamadryas';
my $file = 'file.txt'.IO;
my $path = $home.add( $user ).add( $file );
put $path; # /home/hamadryas/file.txt
put 'One up:', $path.parent; # One up: /home/hamadryas
put 'Two up: ', $path.parent(2); # Two up: /home
你可以提出问题,以确定你是否有绝对路径或相对路径:
my $home = '/home'.IO;
my $user = 'hamadryas';
my $file = 'file.txt'.IO;
for $home, $file {
put "$_ is ", .is-absolute ?? 'absolute' !! 'relative';
# put "$_ is ", .is-relative ?? 'relative' !! 'absolute';
}
使相对路径成为绝对路径。没有参数 .absolute
在你创建 [IO::Path
](https://docs.raku.org/type/IO::Path.html) 对象时使用当前工作目录。如果你想要一些其他的基目录,给它一个参数。无论哪种方式,你得到一个[字符串
](https://docs.raku.org/type/Str.html)而不是另一个 [IO::Path
](https://docs.raku.org/type/IO::Path.html) 对象。 .absolute
方法不关心该路径是否实际存在:
my $file = 'file.txt'.IO;
put $file.absolute; # /home/hamadryas/file.txt
put $file.absolute( '/etc' ); # /etc/file.txt
put $file.absolute( '/etc/../etc' ); # /etc/../etc/file.txt
调用 .resolve
会检查文件系统。它弄清楚了 .
和 ..
并将符号链接转换为目标。请注意 /etc/..
被替换为 /private
,因为 /etc
是 macOS 上 /private/etc
的符号链接:
my $file = 'file.txt'.IO;
put $file.absolute( '/etc/..' ); # /etc/../file.txt
put $file.absolute( '/etc/..' ).IO.resolve; # /private/file.txt
你可以使用 :completely
强调该文件存在。如果路径的任何部分(除了最后的部分)不存在或无法解析,则会收到错误:
my $file = 'file.txt'.IO;
{
CATCH {
default { put "Caught {.^name}" } # Caught X::IO::Resolve
}
put $file.absolute( '/homer/..' ).IO.resolve: :completely; # fails
}
4.6.1. 文件测试操作符
文件测试操作符回答有关文件路径的问题。他们中的大多数都返回 True
或 False
。从[字符串
](https://docs.raku.org/type/Str.html)开始调用 .IO
方法来创建 [IO::Path
](https://docs.raku.org/type/IO::Path.html) 对象。使用 .e
检查文件是否存在(表8-1显示了其他文件测试):
my $file = '/some/path';
unless $file.IO.e {
put "The file <$file> does not exist!";
}
为什么是 .e
?它来自 Unix 测试程序,它使用命令行开关(例如 -e
)来回答有关路径的问题。那些相同的字母成为方法的名称。表8-1显示了文件测试。其中大多数与类似语言中的相同,尽管一些多字母将多个测试组合成一个。
Method |
The question it answers |
|
存在 |
|
是一个目录 |
|
是一个普通文件 |
|
文件大小(字节) |
|
是一个符号连接 |
|
对当前用户是可读的 |
|
对当前用户是可写的 |
|
对当前用户是可读和可写的 |
|
对当前用户是可执行的 |
|
对当前用户是可读,可写,可执行的 |
|
文件存在,零字节 |
Almost all of the file tests return a [Boolean](https://docs.raku.org/type/Bool) value. The one odd test is .s
, which asks for the file size in bytes. That’s not a [Boolean](https://docs.raku.org/type/Bool), so how would it note a problem such as a missing file? It might return 0
in that case, because a file can have nothing in it (hence the .z
method to ask if it exists with zero size). .s
returns a [Failure
](https://docs.raku.org/type/Failure.html)instead of False
if there’s a problem:
几乎所有文件测试都返回一个[布尔值](https://docs.raku.org/type/Bool)。一个奇怪的测试是 .s
,它以字节为单位询问文件大小。这不是[布尔值](https://docs.raku.org/type/Bool),那么它会如何记录丢失文件等问题?在这种情况下,它可能返回 0
,因为文件中可能没有任何内容(因此 .z
方法询问它是否存在,大小为零)。如果出现问题,.s
会返回 [Failure
](https://docs.raku.org/type/Failure.html) 而不是 False
:
my $file = 'not-there';
given $file.IO {
CATCH {
# $_ in here is the exception
when X::IO::NotAFile
{ put "$file is not a plain file" }
when X::IO::DoesNotExist
{ put "$file does not exist" }
}
put "Size is { .s }";
}
在尝试获取其大小之前,你可能会检查文件是否存在并且是纯文件(尽管 .f
表示 .e
),但这种方式可能不太安全,因为文件可能会在你进入[块
](https://docs.raku.org/type/Block.html)和当你尝试获取文件大小之间消失:
my $file = 'not-there';
given $file.IO {
when .e && .f { put "Size is { .s }" }
when .e { put "Not a plain file" }
default { put "Does not exist" }
}
但这不是文件测试的唯一语法。还有副词版本。你可以智能地匹配你想要的测试。此示例使用 [Junction
](https://docs.raku.org/type/Junction.html) 来组合测试,即使你在第14章之前不会看到这些测试:
if $file.IO ~~ :e & :f { # Junction!
put "Size is { .s }"
}
练习8.1 创建一个程序,该程序从命令行参数中获取文件列表,并报告当前用户是否可读,可写或可执行。如果文件不存在,你会怎么做?
4.6.2. 文件元数据
文件记录的不仅仅是其内容。他们保留有关自己的额外信息;这就是元数据。 .mode
方法返回文件的 POSIX 权限(如果你的文件系统支持这样的事情)。这是一个整数,表示用户,组和其他所有人的设置:
my $file = '/etc/hosts';
my $mode = $file.IO.mode;
put $mode.fmt: '%04o'; # 0644
某些 POSIX 或 Unix 特定的想法不适用于 Windows。在我写的时候,没有特定于 Windows 的模块来填补这些空白。 |
每组权限需要三位:一个用于读,一个用于写和一个用于执行。你使用位运算符(你还没有看到它们)从单个数字中提取单独的权限。
按位 AND 运算符 +&
,使用位掩码(例如以下示例中的 0o700
)隔离集合。按位右移运算符 +>
,提取正确的数字:
my $file = '/etc/hosts';
my $mode = $file.IO.mode;
put $mode.fmt: '%04o'; # 0644
my $user = ( $mode +& 0o700 ) +> 6;
my $group = ( $mode +& 0o070 ) +> 3;
my $all = ( $mode +& 0o007 );
在每个权限集内,你可以使用另一个掩码来隔离你想要的位。在这部分你将最终得到 True
或 False
:
put qq:to/END/;
mode: { $mode.fmt: '%04o' }
user: $user
read: { ($user +& 0b100).so }
write: { ($user +& 0b010).so }
execute: { ($user +& 0b001).so }
group: { $group }
all: { $all }
END
你可以使用 chmod
子例程更改这些权限。给它相同的数字。将它表示为十进制数字可能最简单:
chmod $file.IO.chmod: 0o755;
FILE TIMES
.modified
,.accessed
和 .changed
方法返回表示文件的修改,访问和 inode 更改时间的 [Instant
](https://docs.raku.org/type/Instant.html) 对象(如果你的系统支持这些)。你可以使用 .DateTime
方法将 [Instant
](https://docs.raku.org/type/Instant.html) 转换为人类可读的日期:
my $file = '/home/hamadryas/.bash_profile';
given $file.IO {
if .e {
put qq:to/HERE/
Name: $_
Modified: { .modified.DateTime }
Accessed: { .accessed.DateTime }
Changed: { .changed.DateTime }
Mode: { .mode }
HERE
}
}
这给出了这样的东西:
Name: /home/hamadryas/.bash_profile
Modified: 2018-08-15T01:19:09Z
Accessed: 2018-08-16T10:07:00Z
Changed: 2018-08-15T01:19:09Z
Mode: 0664
4.6.3. Linking and Unlinking Files
文件名是你存储在某处的某些数据的标签。重要的是要记住名称不是数据。同样,目录或文件夹的隐喻就是这样。它并不真正“包含”文件。它知道它应该记住的文件名列表。牢记这一点应该使下一部分更容易掌握。
名称是数据的链接,相同的数据可以有多个链接。只要有链接,你就可以获得该数据。这并不意味着数据在没有链接时会消失。存储的那些部分仅用于其他部分。这就是你有时可以恢复数据的原因。你的特定文件系统可能会以不同的方式执行操作,但这是基本的想法。
通常,你删除链接的能力取决于目录权限,而不是文件权限。你实际上是从目录所包含的文件列表中删除该文件。 |
要删除文件,请使用 .unlink
删除指向它的链接。你没有删除数据;这就是为什么它不被称为 .delete
或类似的东西的原因。其他指向相同数据的链接可能仍然存在。如果 .unlink
可以删除该文件,则返回 True
。如果失败则返回 X::IO::Unlink
:
my $file = '/etc/hosts'.IO;
try {
CATCH {
when X::IO::Unlink { put .message }
}
$file.unlink;
}
你可以使用子例程形式同时删除多个文件。它返回你必须从备份还原的文件的名称(也称为成功取消链接的文件):
my @unlinked-files = unlink @files;
[集合
](https://docs.raku.org/type/Set.html) 的差集在这里很有用,虽然在第 14 章之前你不会看到 [Set
](https://docs.raku.org/type/Set.html)。请注意,你可以取消链接不存在的文件,它们不会出现在 @error-files
中:
my @error-files = @files.Set (-) @unlinked-files.Set;
你可以删除原始文件名,但数据仍然存在。文件背后的数据一直存在,直到所有链接消失。这些都不能删除目录,但你马上会看到如何做到这一点。
使用 .link
为某些数据创建新标签。该路径必须与数据位于同一磁盘或分区上。如果这不起作用,则失败并抛出 X::IO::Link
异常:
my $file = '/Users/hamadryas/test.txt'.IO;
{
CATCH {
when X::IO::Unlink { ... }
when X::IO::Link { ... }
}
$file.link: '/Users/hamadryas/test2.txt';
$file.unlink;
}
还有另一种链接,称为符号链接(简称“符号链接”)。这不是一个实际的链接;它是指向另一个文件名(“目标”)的文件。当文件系统遇到符号链接时,它会使用目标路径。
目标是最终文件名。你创建的符号链接指向该文件名。在目标上调用 .symlink
来创建指向它的文件:
{
CATCH {
when X::IO::Symlink { ... }
}
$target.symlink: '/opt/different/disk/test.txt';
}
4.6.4. 重命名和复制文件
要更改文件名,请使用 .rename
。与 .link
一样,这仅适用于同一磁盘或分区。它会在不移动数据的情况下更改标签。如果它无法做到这一点,则会失败并抛出 X::IO::Rename
异常:
my $file = '/Users/hamadryas/test.txt'.IO;
{
CATCH {
when X::IO::Rename { put .message }
}
$file.rename: '/home/hamadryas/other-dir/new-name.txt';
}
你可以将数据复制(.copy
)到其他设备或分区。这会将数据物理地放在磁盘上的新位置上。原始数据及其链接保留在原地,复制的数据有自己的链接。之后,两者没有连接,你有两个单独的数据副本。如果它不起作用,则会失败并抛出 X::IO::Copy
异常:
my $file = '/Users/hamadryas/test.txt'.IO;
{
CATCH {
when X::IO::Copy { put .message }
}
$file.copy: '/opt/new-name.txt';
}
使用 .move
首先复制数据然后删除原始数据。如果文件已经存在, .copy
将替换新文件(并且它具有正确的权限)):
my $file = '/Users/hamadryas/test.txt'.IO;
{
CATCH {
when X::IO::Move { put .message }
}
$file.copy: '/opt/new-name.txt';
}
使用 :create-only
副词来阻止替换:
$file.copy: '/opt/new-name.txt', :create-only;
.move
方法结合了 .copy
和 .unlink
:
$file.move: '/opt/new-name.txt';
复制文件后 .move
可能无法删除原始文件。你可能希望在开始之前检查该权限,但无法保证权限不会更改。
4.7. 操作目录
程序启动时,会知道它的当前工作目录。当前工作目录存储在特殊变量 $*CWD
中。处理相对文件路径时,程序会在当前目录中查找:
put "Current working directory is $*CWD";
要更改该目录,请使用 chdir
。给它一个绝对路径来改变到那个目录:
chdir( '/some/other/path' );
给它一个相对路径来改变到当前工作目录的子目录:
chdir( 'a/relative/path' );
如果失败,则返回带有`X::IO::Chdir` [异常
](https://docs.raku.org/type/Exception.html)的 [Failure
](https://docs.raku.org/type/Failure.html):
unless my $dir = chdir $subdir {
... # handle the error
}
不带参数的`chdir`会给你一个错误。你可能希望转到你的家目录。如果需要,请使用 $*HOME
作为参数。这是存储家目录的特殊变量:
chdir( $*HOME );
如何设置 $*HOME
取决于你的特定系统。在类 Unix 系统上,这可能是 HOME
环境变量。在 Windows 上,它可能是 HOMEPATH
。
练习8.2 输出你的家目录路径。创建一个现有子目录的新路径并切换到该目录。输出当前工作目录的值。如果子目录不存在会发生什么?
有时你只需要为程序的一小部分更改目录,之后你就想回到你开始的地方。 indir
子例程接收目录和代码块并运行该代码,就好像它是当前的工作目录一样。它实际上并没有弄乱 $*CWD
:
my $result = indir $dir, { ... };
unless $result {
... # handle the error
}
如果一切正常,则 indir
返回块的结果,尽管可能是 False
值甚至是 [Failure
](https://docs.raku.org/type/Failure.html)。如果 indir
无法更改到目录,则返回 [Failure
](https://docs.raku.org/type/Failure.html)。小心你正在处理的情况!
4.7.1. 目录清单
dir`获取目录中的文件[`序列
](https://docs.raku.org/type/Seq.html)作为 [IO::Path
](https://docs.raku.org/type/IO::Path.html) 对象。它包含隐藏文件(但不包括 .
和 ..
虚拟文件)。如果不带参数则它使用当前目录:
my @files = dir();
my $files = dir();
With an argument it gets a [Seq
](https://docs.raku.org/type/Seq.html) of the files in the specified directory:
如果 dir
带参数,则它获取指定目录中的文件[序列
](https://docs.raku.org/type/Seq.html):
my @files = dir( '/etc' );
for dir( '/etc' ) -> $file {
put $file;
}
say dir( '/etc' ); # ("/etc/emond.d".IO ...)
say dir( 'lib' ); # ("lib/raku".IO ...)
如果遇到问题,dir `会返回 [`Failure
](https://docs.raku.org/type/Failure.html),例如不存在的目录。
dir
的另一个不错的功能是:它知道要跳过哪些条目。有一个可选的第二个参数,可以测试条目以决定它们是否应该成为结果的一部分。默认情况下,测试是一个 [Junction
](https://docs.raku.org/type/Junction.html)(第14章),它排除了 .
和 ..
虚拟目录:
say dir( 'lib', test => none( <. ..> ) );
练习8.3 输出另一个目录中所有文件的列表。每行显示一个并给每行编号。你能对文件列表进行排序吗?如果你没有想要浏览的目录,请在类 Unix 系统上尝试 /etc
,或在 Windows 上尝试 C:\rakudo
。
练习8.4 创建一个接收目录名并列出其中所有文件的程序。下降到子目录并列出他们的文件。稍后你将在第19章中使用该程序。
4.7.2. 创建目录
你可以使用 mkdir
创建自己的目录。如果这是你要求的,它可以立即为你创建多层级的子目录。如果 mkdir
无法创建目录,则会抛出 X::IO::Mkdir
[异常
](https://docs.raku.org/type/Exception.html):
try {
CATCH {
when X::IO::Mkdir { put "Exception is {.message}" }
}
my $subdir = 'Butterflies'.IO.add: 'Hamadryas';
mkdir $subdir;
}
可选的第二个参数是 Unix 风格的八进制模式(Windows 忽略此参数)。 Unix 权限最容易读作八进制数:
mkdir $subdir, 0o755;
你也可以从[字符串
](https://docs.raku.org/type/Str.html)开始,然后使用 .IO
将其转换为 [IO::Path
](https://docs.raku.org/type/IO::Path.html) 对象,然后在所有这些对象上调用 .mkdir
。你省略或不省略模式都可以:
$subdir.IO.mkdir;
$subdir.IO.mkdir: 0o755;
练习8.5 编写一个程序来创建一个在命令行中指定的子目录。将完整路径指定为参数时会发生什么?如果目录已经存在怎么办?
4.7.3. 移除目录
有两种方法可以删除目录,但你可能只想使用其中一种。在开始使用这些之前,你可能会考虑使用虚拟机的快照或在无法删除任何重要内容的特殊帐户中工作。小心!
第一个是 rmdir
,只要目录是空的(没有文件或子目录)就删除一个或多个目录:
my @directories-removed = rmdir @dirs;
使用方法形式,你可以一次删除一个。如果失败则抛出 X::IO::Rmdir
[异常
](https://docs.raku.org/type/Exception.html):
try {
CATCH {
when X::IO::Rmdir { ... }
}
$directory.IO.rmdir;
}
这有点不方便。通常,你希望删除目录及其包含的所有内容。 File::Directory::Tree
中的 rmtree
子例程对此非常有用:
use File::Directory::Tree;
my $result = try rmtree $directory;
4.8. 格式化输出
你可以在输出值之前格式化值,也可以将值插入到[字符串
](https://docs.raku.org/type/Str.html)中。选项遵循你在其他语言中已经看到的内容,因此你只能在这里体验它们。
将模板[字符串
](https://docs.raku.org/type/Str.html)赋予 .fmt
以描述值的显示方式。模板使用指令; 这些指令以 %
开头,并用字符来描述格式。下面这些是以十六进制(%x
),八进制(%o
)和二进制(%b
)格式化的同一个数字:
$_ = 108;
put .fmt: '%x'; # 6c
put .fmt: '%X'; # 6C (uppercase!)
put .fmt: '%o'; # 154
put .fmt: '%b'; # 1101100
某些指令有额外的选项,这些选项出现在 %
和字母之间。数字指定列的最小宽度(尽管可能会溢出)。前导零使用零填充未使用的列。插值[字符串
](https://docs.raku.org/type/Str.html)时可以看到这种情况;格式化输出周围的字符清楚地表明 .fmt
创建了什么:
put "$_ is ={.fmt: '%b'}="; # 108 is =1101100=
put "$_ is ={.fmt: '%8b'}="; # 108 is = 1101100=
put "$_ is ={.fmt: '%08b'}="; # 108 is =01101100=
模板文本可以包含其他字符。如果这些不是指令的一部分,则它们是字面字符。这会将前面的示例翻出来,因此所有字符都在模板中:
put .fmt: "$_ is =%08b="; # 108 is =01101100=
如果你想要一个字面的 %
符号则用另一个 %
转义它。 %f
指令格式化一个浮点数,这对百分比很方便。你可以指定总宽度(包括小数点)和小数位数:
my $n = 1;
my $d = 7;
put (100*$n/$d).fmt: "$n/$d is %5.2f%%"; # 1/7 is 14.29%
省略总宽度仍然有效,并允许你仅指定小数位数。这会将最终显示的十进制数字舍入:
put (100*$n/$d).fmt: "$n/$d is %.2f%%"; # 1/7 is 14.29%
Calling .fmt
on a [Positional
](https://docs.raku.org/type/Positional.html) formats each element according to the template, joins them with a space, and gives you a single [Str
](https://docs.raku.org/type/Str.html):
在 [Positional
](https://docs.raku.org/type/Positional.html) 上调用 .fmt
会根据模板格式化每个元素,将它们与空格连接,并为你提供单个[字符串
](https://docs.raku.org/type/Str.html):
put ( 222, 173, 190, 239 ).fmt: '%02x'; # de ad be ef
.fmt
的第二个参数更改分隔符:
put ( 222, 173, 190, 239 ).fmt: '%02x', ''; # deadbeef
`sprintf`可以通过更多的控制来完成同样的工作。这是一个例程,它接收同样的模板作为它的第一个参数,然后是一个值列表。每个值按顺序填充一个指令。你不必输出结果:
my $string = sprintf( '%2d %s', $line-number, $line );
`printf`执行相同的操作并直接将结果输出到标准输出(不添加换行符):
printf '%2d %s', $line-number, $line;
[表8-2](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch08.html#camelia-files-TABLE-directives)列出了一些可用的 sprintf
指令。
Directive |
Description |
|
十进制有符号整数 |
|
十进制无符号整数 |
|
八进制无符号整数 |
|
十六进制无符号整数 (小写) |
|
十六进制无符号整数 (大写) |
|
二进制无符号整数 |
|
浮点数 |
|
文本值 |
练习8.6 创建一个使用 printf
的程序,并将右对齐文本输出到你指定的列数。输出标尺线可能会对你有所帮助。
4.8.1. 常见的格式化任务
使用 %f
舍入数字。指定整个模板的宽度和小数位数。小数点和后续数字计为宽度的一部分:
put (2/3).fmt: '%4.2f'; # 0.67;
但是,总宽度不限制列。它至少是那个列数,但可能更多:
put (2/3).fmt: '%4.5f'; # 0.66667;
如果你不关心宽度,可以将其省略。这只是将值舍入为你指定的小数位数:
put (2/3).fmt: '%.3f'; # 0.667;
%
之后的 #
添加了数字系统前缀,但不是 Raku 使用的前缀。它是宇宙其余部分使用的前缀; 八进制数得到前导零:
put 108.fmt: '%#x'; # 0x6c
put 108.fmt: '%#o'; # 0154
%s
格式化文本值。使用宽度它将值向右推,并在必要时用空格填充它。 宽度前面的 -
将文本推向左侧:
put 'Hamadryas'.fmt: '|%s|'; # |Hamadryas|
put 'Hamadryas'.fmt: '|%15s|'; # | Hamadryas|
put 'Hamadryas'.fmt: '|%-15s|'; # |Hamadryas |
使用 sprintf
创建柱状输出。宽度使一切排成一行:
my $line = sprintf '%02d %-20s %5d %5d %5d', @values;
练习8.7 输出你在命令行中指定的两个数字的百分比。将输出限制为三位小数。
练习8.8 输出一个12乘12的乘法表。
4.9. 标准文件句柄
文件句柄是程序和文件之间的连接。你可以免费获得其中的三个。两个用于输出,一个用于输入。标准输出是你从本书开始以来一直使用的输出。它是输出的默认文件句柄。你还使用了标准错误,因为这是用于警告和错误的文件句柄。标准输入将你的程序连接到某人试图提供的数据上。
在继续阅读和编写任意文件的一般过程之前,你可能会发现查看基本文件句柄很有用。如果你已经知道这些事情,那么跳过本节并无不妥。
4.9.1. 标准输出
标准输出是大多数输出方法的默认文件句柄。当你在其例程形式中使用其中任何一个时,你就正在使用标准输出:
put $up-the-dishes;
say $some-stuff;
print $some-stuff;
printf $template, $thing1, $thing2;
在 $*OUT
上调用方法会使其显式化。这是保存默认文件句柄的特殊变量:
$*OUT.put: $up-the-dishes;
$*OUT.say: $some-stuff;
$*OUT.print: $some-stuff;
$*OUT.printf: $template, $thing1, $thing2;
你可能在某些时候在命令行上使用了重定向。 >
将程序的标准输出发送到文件(或其他地方):
% raku program.p6 > output.txt
如果要运行程序但不关心输出,可以将其发送到空设备。输出无处可去,然后消失了。这在 Unix 系统和 Windows 中略有不同:
% raku program.p6 > /dev/null
C:\ raku program.p6 > NUL
练习8.9 创建一个将内容写入标准输出的程序。运行该程序并将输出重定向到文件。再次运行它并将输出重定向到空设备。
4.9.2. 标准错误
标准错误是输出的另一种途径。当程序不希望影响正常输出时,程序通常会对警告和其他消息使用标准错误。你可以在不搞乱格式化输出的情况下获得警告。
warn
将其消息输出到标准错误,程序继续。顾名思义,当你遇到一个你可以预料到并且你认为有人应该知道的情况时,它就是为警告而设计的:
warn 'You need to use a number between 0 and 255';
fail
和 die
是相似的。他们将消息发送到标准错误,但他们也可以停止你的程序,除非你捕获或处理它们。
note
就像 say
;它在其参数上调用 .gist
并将结果输出到标准错误。这对调试输出很有用:
note $some-object;
通常这种输出是通过某些命令行开关或其他设置启用的:
note $some-object if $debugging > 0;
输出方法适用于 $*ERR
,它保存默认错误文件句柄:
$*ERR.put: 'This is a warning message';
当你在终端中工作时,通常会同时看到标准输出和标准错误(或“合并”)。用 2>
重定向错误输出;获取文件描述符编号 2(标准错误)并将其发送到不是终端的某个地方。如果你不理解任何一个,只需按照例子:
% raku program.p6 2> error_output.txt
C:\ raku program.p6 2> error_output.txt
% raku program.p6 2> /dev/null
C:\ raku program.p6 2> NUL
将文件描述符 2 重定向到文件描述符 1 以合并标准输出和错误。同样,你可以按照示例进行操作,而无需追根究底:
% raku program.p6 2>&1 /dev/null
练习8.10 创建一个程序,输出标准输出和标准错误。运行它并将标准输出重定向到文件。再次运行它,但将标准错误重定向到空设备。
4.9.3. 标准输入
当你使用没有命令行参数的 lines()
时,它会从标准输入读取。数据流入你的程序:
for lines() {
put ++$, ': ', $_;
}
你的程序会等待你输入内容并将其输出给你:
% raku no-args.p6
Hello Raku
0: Hello Raku
this is the second line
1: this is the second line
If you only want standard input you can explicitly use $*IN
. Call .lines
as follows:
如果你只想要标准输入,则可以显式地使用 $*IN
。像下面这样调用 .lines
:
for $*IN.lines() {
put ++$, ': ', $_;
}
标准输入也可以来自另一个程序。你可以将一个程序的输出传递给另一个程序的输入:
% raku out-err.p6 | raku no-args.p6
练习8.11 创建两个程序。第一个应输出包含第一个参数的命令行文件中的所有行。将其输出通过管道输出到第二个程序,读取其输入并以全部大写形式输出。将第一个程序的输出通过管道输入到第二个程序。
4.10. 读取输入
你已经看到了将数据导入程序的几种方法。prompt
例程输出一条消息并等待一行输入:
my $answer = prompt( 'Enter some stuff> ' );
用 slurp
一次性读取整个文件。slup
作为方法或例程:
my $entire-file = $filename.IO.slurp;
my $entire-file = slurp $filename;
如果你无法阅读该文件,你将收到 [Failure
](https://docs.raku.org/type/Failure.html)。始终检查你是否能够做你想做的事情:
unless my $entire-file = slurp $filename.IO.slurp {
... # handle error
}
4.10.1. 读取行
在第6章中,你了解了如何使用 lines()
从你在命令行中指定的文件名中读取行。通过 @*ARGS
并在单个文件上调用 lines
来自己完成此操作。你可以过滤掉不存在或存在其他问题的文件(lines()
不执行的操作):
for @*ARGS {
put '=' x 20, ' ', $_;
# maybe more error checking here
unless .IO.e { put 'Does not exist'; next }
for .IO.lines() {
put "$_:", ++$, ' ', $_;
}
}
这代码有点太多了。 lines()
从 $*ARGFILES
文件句柄读取。这与显式使用它是一样的:
for $*ARGFILES.lines() {
put ++$, ': ', $_;
}
使用 $*ARGFILES.path
提取当前文件名:
for $*ARGFILES.lines() {
put "{$*ARGFILES.path}:", ++$, ' ', $_;
}
这不会处理为每个文件开始编号为新鲜的行,但是有一个技巧:$*ARGFILES
知道在切换文件时让你在发生这种情况时运行一些代码。给 .on-switch
一个代码块,以便在文件更改时运行。用它来重置持久计数器:
for lines() {
state $lines = 1;
FIRST { $*ARGFILES.on-switch = { $lines = 1 } }
put "{$*ARGFILES.path}:{$lines++} $_";
}
当我写这篇文章时,如果 lines 遇到一个无法读取的文件,则抛出一个你无法恢复的[异常 ](https://docs.raku.org/type/Exception.html)。我会忽略这一点,因为我希望情况很快就会改变。
|
练习8.12 创建一个程序,输出你在命令行中指定的所有文件的行。在输出每个文件的行之前输出显示其名称的文件横幅。完成上一个文件后会发生什么?
4.10.2. 读取文件
Both slurp
and lines
handle the details implicitly. open
lets you do it in whatever manner you like. It returns a filehandle that you use to get the data from the file. If there’s a problem open
returns a [Failure
](https://docs.raku.org/type/Failure.html):
slurp
和 lines
都隐式地处理细节。 open
允许你以任何你喜欢的方式来做。它返回一个文件句柄,用于从文件中获取数据。如果出现问题,则 open
返回[Failure
](https://docs.raku.org/type/Failure.html):
my $fh = open 'not-there';
unless $fh {
put "Error: { $fh.exception }";
exit;
}
for $fh.lines() { .put }
你可能更喜欢方法形式:
my $fh = $filename.IO.open;
你可以更改编码,行结束处理和特定行结束。 :enc
副词设置输入编码:
my $fh = open 'not-there', :enc('latin1');
To keep the line endings instead of autochomping them, use :chomp
:
要保留行结尾而不是自动切除,请使用 :chomp
:
my $fh = open 'not-there', :chomp(False);
行结尾设置为 :nl-in
并且可以是多个[字符串
](https://docs.raku.org/type/Str.html),其中任何一个都可以工作:
my $fh = open 'not-there', :nl-in( "\f" );
my $fh = open 'not-there', :nl-in( [ "\f", "\v" ] );
如果你不想结束行(比如 slurp
),空的[字符串
](https://docs.raku.org/type/Str.html)或 False
会起作用:
my $fh = open 'not-there', :nl-in( '' );
my $fh = open 'not-there', :nl-in( False );
你可以读取单行。告诉 .lines
你想要多少行:
my $next-line = $fh.lines: 1;
.lines
是惰性的。那实际上没有读取一行。直到你尝试使用 $next-line
时它才会这样做。如果你想让它立即发生,你可以让它急切:
my $next-line = $fh.lines(1).eager;
如果你想要所有的行你仍然可以从文件句柄 .slurp
:
my $rest-of-data = $fh.slurp;
完成后关闭文件句柄。程序将在某些时候自动为你执行此操作,但你不希望这些事情可能会在程序结束时出现:
$fh.close;
练习8.13 打开在命令行中指定的每个文件。输出第一行和最后一行。在这两者之间报告你遗漏的行数。
4.11. 写出
写文件的最简单方法是使用 spurt
。给它一个文件名和一些数据,它为你完成剩下的工作:
spurt $path, $data;
如果文件已存在,则会覆盖已存在的任何内容。要添加已经存在的内容,请使用 :append
副词:
spurt $path, $data, :append;
仅当文件尚不存在时,你才可以通过指定 :exclusive
来输出数据。如果文件已经存在,则会失败:
spurt $path, $data, :exclusive;
当 spurt
工作时,它返回 True
。如果出现问题则返回 [Failure
](https://docs.raku.org/type/Failure.html):
unless spurt $path, $data {
... # handle error
}
4.11.1. 打开文件以写入
使用 spurt
可能很方便,但每次使用它时,你真正打开了一个文件,写入文件并关闭它。如果你想继续添加到文件中,你可以自己打开文件并保持打开状态,直到完成为止:
unless my $fh = open $path, :w {
...;
}
$fh.print: $data;
$fh.print: $more-data;
任何输出方法都适用于文件句柄:
$fh.put: $data;
$fh.say: $data;
完成文件后调用 .close
。这可确保较低级别可能已缓冲的任何数据都会进入文件:
$fh.close;
如果你不喜欢默认行分隔符,则可以指定自己的行分割符。当你有多行的项要包含在一起作为单个记录时,换页符`\f`,作为“行”分隔符很方便:
unless my $fh = open $path, :w, :nl-out("\f") {
...; # handle the error
}
$fh.print: ...;
使用 try
可能更干净:
my $fh = try open $path, :w, :exclusive, :enc('latin1'), :nl-out("\f");
if $! {
... # handle the error
}
练习8.14 创建一个程序,该程序将你在命令行中指定的两个数字之间的所有素数写入文件。如果文件已存在,你应该怎么做?
4.12. 二进制文件
二进制文件不是基于字符的。图像,电影等都是例子。你不希望文件阅读器将这些解码为 Perl 的内部字符格式;你想要原始数据。使用带有 :bin
副词的 slurp
来读取。它返回一 [Buf
](https://docs.raku.org/type/Buf.html) 而不是返回一个[字符串
](https://docs.raku.org/type/Str.html)。你可以像任何其他 [Positional
](https://docs.raku.org/type/Positional.html) 一样处理 [Buf
](https://docs.raku.org/type/Buf.html):
my $buffer = slurp $filename, :bin; # Buf object
for @$buffer { ... }
使用相同的 :bin
副词打开文件以获取其原始内容:
unless my $fh = open $path, :bin {
... # handle the error
}
4.12.1. 移动
告诉 .read
要读取多少个八位字节,它返回一个 [Buf
](https://docs.raku.org/type/Buf.html),其中每个元素是 0 到 255 之间的整数(无符号8位范围):
my Buf $buffer = $fh->read( $count );
[Buf
](https://docs.raku.org/type/Buf.html) 是一种 [Positional
](https://docs.raku.org/type/Positional.html)。每个八位字节都是缓冲区的一个元素,你可以通过它的位置获得一个八位字节:
my $third_byte = $buffer[2];
下次调用 .read
时,你将从文件中你离开的位置开始获取八位字节。使用 .seek
移动到其他位置。指定`SeekFromCurrent` 从你离开的位置移动:
my $relative_position = 137;
$fh.seek( $relative_position, SeekFromCurrent );
使用负值向后移动:
my $negative_position = -137;
$fh.seek( $negative_position, SeekFromCurrent );
如果指定 SeekFromBeginning
,它将从文件的开头开始计数并移动到你指定的绝对位置:
my $absolute_position = 1370;
$fh.seek( $absolute_position, SeekFromBeginning );
EXERCISE 8.15 写一个小的十六进制转储程序。一次读取16个八位字节的原始文件。打印每个八位字节的十六进制值,它们之间有空格,末尾有换行符。每行应该有这样的形式:20 50 65 72 6c 20 36 2c 20 4d 6f 61 72 56 4d 20
4.12.2. 写二进制文件
另一方面,你可以将八位字节写入文件。使用相同的 :bin
副词打开文件进行写入:
unless my $fh = open $path, :w, :bin {
...;
}
使用 .write
并给它一个 [Buf
](https://docs.raku.org/type/Buf.html) 对象。每个元素必须是 0 到 255 之间的整数:
my $buf = Buf.new: 82, 97, 107, 117, 100, 111, 10;
$fh.write: $buf;
用十六进制表示它们可能更容易:
my $buf = Buf.new: <52 61 6b 75 64 6f 0a>.map: *.parse-base: 16;
练习8.16 实现程序将 [Buf
](https://docs.raku.org/type/Buf.html) 写入文件。最终文件中的内容是什么?
4.13. 总结
你在本章中看到的功能可能是你编写的许多有用程序的核心。你可以将数据放入文件中,以后再检索该数据。你可以创建目录,将文件移动到这些目录中,或者将它们全部删除。大多数操作简单明了;一旦你知道了正确的对象,你就能轻松找到所需的方法。然而,这些东西中的大多数都与外部世界相互作用,并在事情无法解决时抱怨。不要忽视那些抱怨! == Associative
[Associative
](https://docs.raku.org/type/Associative.html) 使用被称为键的任何名字索引到值。[Associative
](https://docs.raku.org/type/Associative.html)是无序的,因为键没有相对顺序。其他语言具有类似的数据类型,它们称为关联数组,字典,散列,映射或类似的东西。有几种类型的专用关联数据结构,你已经使用了其中的一些。
4.14. Pairs
A [Pair
](https://docs.raku.org/type/Pair.html) has a single key and a value. You’ve already used these in their adverbial form, although you didn’t know they were [Pair
](https://docs.raku.org/type/Pair.html)s. Create a [Pair
](https://docs.raku.org/type/Pair.html) through general object construction with the name and value as arguments:
[Pair
](https://docs.raku.org/type/Pair.html) 具有单个键和值。你已经以他们的副词形式使用了这些,虽然你不知道他们是 [Pair
](https://docs.raku.org/type/Pair.html)。通过一般对象构造创建一个 [Pair
](https://docs.raku.org/type/Pair.html),名称和值作为参数:
my $pair = Pair.new: 'Genus', 'Hamadryas';
The ⇒
is the [Pair
](https://docs.raku.org/type/Pair.html) constructor. You don’t have to quote the lefthand side because the ⇒
does that for you as long as it looks like a term:
⇒
是 [Pair
](https://docs.raku.org/type/Pair.html) 构造函数。你不必将左侧用引号引起来,因为 ⇒
为你做了,只要它看起来像一个项:
my $pair = Genus => 'Hamadryas'; # this works
my $nope = ⛇ => 'Hamadryas'; # this doesn't
Any value can be a [Pair
](https://docs.raku.org/type/Pair.html) value. Here’s a value that’s a [List
](https://docs.raku.org/type/List.html):
[Pair
](https://docs.raku.org/type/Pair.html) 的值可以是任意值。下面这个 [Pair
](https://docs.raku.org/type/Pair.html) 的值是一个 [List
](https://docs.raku.org/type/List.html):
my $pair = Pair.new: 'Colors', <blue black grey>;
Combining .new
and ⇒
probably doesn’t do what you want. Passing it a single [Pair
](https://docs.raku.org/type/Pair.html) means that you are missing its value. The .new
method thinks that the [Pair
](https://docs.raku.org/type/Pair.html) is the key and you forgot the value:
结合 .new
和 ⇒
可能不会做你想要的。给 .new
传递单个 [Pair
](https://docs.raku.org/type/Pair.html) 就意味着你丢掉了值。 .new
方法认为 [Pair
](https://docs.raku.org/type/Pair.html) 是键,而你忘记了值:
my $pair = Pair.new: 'Genus' => 'Hamadryas'; # WRONG!
4.14.1. 副词
A more common syntax is the adverbial form that you have already seen with Q
quoting. Start with a colon, add the unquoted name, and specify the value inside <>
for allomorphic quoting or inside ()
where you quote the value yourself:
更常见的语法是你在 Q
引用中已经看到的副词形式。从冒号开始,添加不带引号的名字,并在 <>
内指定用于异形引号的值,或在 ()
中指定你自己用引号引起的值:
my $pair = :Genus<Hamadryas>;
my $pair = :Genus('Hamadryas');
my $genus = 'Hamadryas';
my $pair = :Genus($genus);
Without an explicit value an adverb has the value True
:
如果没有显式的值,那么副词的值为 True
:
my $pair = :name; # name => True
Using an adverb with Q
with no value means you turn on that feature (everything else is False
by default):
使用不带值的 Q
的副词意味着你开启该功能(默认情况下其他所有内容都为 False
):
Q :double /The name is $butterfly/;
If you’d like it to be False
instead, put a !
in front of the key name:
如果你想要它是假的,那就放一个 !
在键名前面:
my $pair = :!name; # name => False
You can create a [Pair
](https://docs.raku.org/type/Pair.html) from a scalar variable. The identifier becomes the key and the value is the variable’s value. You’ll see more of this coming up:
你可以从标量变量创建一个 [Pair
](https://docs.raku.org/type/Pair.html)。标识符成为键,值是变量的值。你会看到更多这样的事情:
my $Genus = 'Hamadryas';
my $pair = :$Genus; # same as 'Genus' => 'Hamadryas';
There’s also a tricky syntax that reverses a numeric value and alpha-headed text key. This is a bit prettier for adverbs representing positions, such as 1st
, 2nd
, 3rd
, and so on:
还有一种棘手的语法可以反转数值和带字母的文本键。这对于代表位置的副词来说有点漂亮,比如 1st
, 2nd
, `3rd`等等:
my $pair = :2nd; # same as nd => 2
The .key
and .value
methods extract those parts of the [Pair
](https://docs.raku.org/type/Pair.html):
.key
和 .value
方法提取 [Pair
](https://docs.raku.org/type/Pair.html) 中的那些部分:
put "{$p.key} => {$pair.value}\n";
The .kv
method returns both as a [Seq
](https://docs.raku.org/type/Seq.html):
.kv
方法作为 [Seq
](https://docs.raku.org/type/Seq.html) 返回:
put join ' => ', $pair.kv;
EXERCISE 9.1Develop a subroutine that creates [Pair
](https://docs.raku.org/type/Pair.html)s for the numbers from 0 to 10. Given the argument 1
, it returns the [Pair
](https://docs.raku.org/type/Pair.html) :1st
. Given 2
, it returns :2nd
. Given 3
, it returns :3rd
. For all other numbers, it uses th
as the suffix.
练习9.1 开发一个子例程,为 0 到 10 之间的数字创建 [Pair
](https://docs.raku.org/type/Pair.html)。给定参数 1
,它返回 [Pair
](https://docs.raku.org/type/Pair.html) :1st
。给定 2
,它返回::2nd
。给定 3
,它返回::3rd
。对于所有其他数字,它使用 th
作为后缀。
4.14.2. 修改 Pair
You can’t change the key of a [Pair
](https://docs.raku.org/type/Pair.html). You’d have to make a new [Pair
](https://docs.raku.org/type/Pair.html) to get a different key:
你无法更改 [Pair
](https://docs.raku.org/type/Pair.html) 的键。你必须创建一个新的 [Pair
](https://docs.raku.org/type/Pair.html) 才能获得不同的键:
my $pair = 'Genus' => 'Hamadryas';
$pair.key = 'Species'; # Nope!
If the [Pair
](https://docs.raku.org/type/Pair.html) value is a container you can change the value inside the container, but if you constructed it with a literal [Str
](https://docs.raku.org/type/Str.html) there’s no container and you can’t change the value:
如果 [Pair
](https://docs.raku.org/type/Pair.html) 的值是容器,则可以更改容器内的值,但如果使用文字[字符串
](https://docs.raku.org/type/Str.html)构造它,则没有容器,并且你无法更改值:
my $pair = 'Genus' => 'Hamadryas';
$pair.value = 'Papillo'; # Nope!
You can assign to it if that value came from a variable storing a container:
如果该值来自存储容器的变量,则可以为其分配:
my $name = 'Hamadryas';
my $pair = 'Genus' => $name;
$pair.value = 'Papillo';
Remember that not all variables are mutable. You may have bound to a value:
请记住,并非所有变量都是可变的。你可能已经绑定了一个值:
my $name := 'Hamadryas'; # bound directly to value, no container
my $pair = 'Genus' => $name;
$pair.value = 'Papillo'; # Nope! Still a fixed value
To ensure that you get a container, assign the value to an anonymous scalar. You don’t create a new named variable and you end up with a container:
要确保获得容器,请将值指定给匿名标量。你不创建新的命名变量,最终得到一个容器:
my $pair = 'Genus' => $ = $name;
$pair.value = 'Papillo'; # Works!
.freeze
the [Pair
](https://docs.raku.org/type/Pair.html) to make the value immutable no matter how it came to you:
冻结(.freeze
) [Pair
](https://docs.raku.org/type/Pair.html) 以使值不可变,无论它以怎样呈现给你:
my $name = 'Hamadryas';
my $pair = 'Genus' => $name;
$pair.freeze;
$pair.value = 'Papillo'; # Nope!
There’s one last thing about [Pair
](https://docs.raku.org/type/Pair.html)s. You can line up the colon forms head-to-foot and it will create a list even though you don’t use commas:
[Pair
](https://docs.raku.org/type/Pair.html) 的最后一件事。你可以将冒号形式从头到脚排列,即使你不使用逗号,它也会创建一个列表:
my $pairs = ( :1st:2nd:3rd:4th );
That’s the same as the form with commas:
这与逗号形式相同:
my $pairs = ( :1st, :2nd, :3rd, :4th );
You’ve already seen this with Q
: you can turn on several features by lining up adverbs. The :q:a:c
here arethree separate adverbs:
你已经在 Q
中看过这个:你可以通过排列副词来开启几个功能。 :q:a:c
这里有三个单独的副词:
Q :q:a:c /Single quoting @array[] interpolation {$name}/;
4.15. Maps
A [Map
](https://docs.raku.org/type/Map.html) is an immutable mapping of zero or more keys to values. You can look up the value if you know the key. Here’s a translation of color names to their RGB values. The .new
method takes a list of [Str
](https://docs.raku.org/type/Str.html)s and their values:
[Map
](https://docs.raku.org/type/Map.html) 是零或多个键与值的不可变映射。如果你知道键,则可以查找该值。这是颜色名到 RGB 值的转换。 .new
方法接收[字符串
](https://docs.raku.org/type/Str.html)及其值的列表:
my $color-name-to-rgb = Map.new:
'red', 'FF0000',
'green', '00FF00',
'blue', '0000FF',
;
You can use the fat arrow to make a list of [Pair
](https://docs.raku.org/type/Pair.html)s:
你可以使用胖箭头来创建 [Pair
](https://docs.raku.org/type/Pair.html) 列表:
my $color-name-to-rgb = Map.new:
'red' => 'FF0000',
'green' => '00FF00',
'blue' => '0000FF',
;
Using the fat arrow notation with autoquoting won’t work; the method thinks these are named arguments instead of [Pair
](https://docs.raku.org/type/Pair.html)s for the [Map
](https://docs.raku.org/type/Map.html) and they are treated as options to the method instead of keys and values. This gives you a [Map
](https://docs.raku.org/type/Map.html) with no keys or values:
使用带有自动引用功能的胖箭头符号将不起作用;该方法认为这些是命名参数而不是映射的[Pair
](Map
(https://docs.raku.org/type/Map.html):
# don't do this!
my $color-name-to-rgb = Map.new:
red => 'FF0000',
green => '00FF00',
blue => '0000FF',
;
A [Map
](https://docs.raku.org/type/Map.html) is fixed; you can’t change it once you’ve created it. This may be exactly what you want since it can keep something else from accidentally modifying it:
[Map
](https://docs.raku.org/type/Map.html) 是固定的;一旦你创建它就不能改变它。这可能正是你想要的,因为它可以防止其他东西意外修改它:
$color-name-to-rgb<green> = '22DD22'; # Error!
To look up one of the color codes you subscript the object. This is similar to [Positional
](https://docs.raku.org/type/Positional.html)s but uses different postcircumfix characters. Use the autoquoting <>
or quote it yourself with {}
:
要查找其中一个颜色代码,你可以下标该对象。这与 [Positional
](https://docs.raku.org/type/Positional.html) 类似,但使用不同的 postcircumfix 字符。使用autoquoting <>
或使用 {}
自行引用:
put $color-name-to-rgb<green>; # quoted key with allomorph
put $color-name-to-rgb{'green'}; # quoted key
put $color-name-to-rgb{$color}; # quoted key with interpolation
If you want to look up more than one key at a time, you can use a slice to get a [List
](https://docs.raku.org/type/List.html) of values:
如果要一次查找多个键,可以使用切片获取值[列表
](https://docs.raku.org/type/List.html):
my @rgb = $color-name-to-rgb<red green>
4.15.1. Checking Keys
To check that a key exists before you try to use it, add the :exists
adverb after the single-element access. This won’t create the key. You’ll get True
if the key is in the [Map
](https://docs.raku.org/type/Map.html) and False
otherwise:
要在尝试使用键之前检查键是否存在,请在单元素访问后添加 :exists
副词。这不会创建键。如果键在 [Map
](https://docs.raku.org/type/Map.html) 中,则为 True
,否则为 False
:
if $color-name-to-rgb{$color}:exists {
$color-name-to-rgb{$color} = '22DD22';
}
The .keys
method returns a [Seq
](https://docs.raku.org/type/Seq.html) of keys:
.keys
方法返回一个键的[序列
](https://docs.raku.org/type/Seq.html):
for $color-name-to-rgb.keys {
put "$^key => {$color-name-to-rgb{$^key}}";
}
You do something similar to get only the values:
你做类似的事情只得到值:
my @rgb-values = $color-name-to-rgb.values;
The .kv
method returns a key and its value at the same time. This saves you some complexity inside the [Block
](https://docs.raku.org/type/Block.html):
.kv
方法同时返回键及其值。这可以节省[Block
](https://docs.raku.org/type/Block.html)内部的一些复杂性:
for $color-name-to-rgb.kv -> $name, $rgb {
put "$name => $rgb";
}
Placeholder values inside the [Block
](https://docs.raku.org/type/Block.html) (but not a pointy [Block
](https://docs.raku.org/type/Block.html)) can do most of the work for you:
[Block
](https://docs.raku.org/type/Block.html) 中的占位符值(但不是尖头[块
](https://docs.raku.org/type/Block.html))可以为你完成大部分工作:
for $color-name-to-rgb.kv {
put "$^k => $^v";
}
4.15.2. Creating from a Positional
You can create a [Map
](https://docs.raku.org/type/Map.html) from a [Positional
](https://docs.raku.org/type/Positional.html) using .map
. That returns a [Seq
](https://docs.raku.org/type/Seq.html) that you can use as arguments to .new
. These create new values based on the original ones:
你可以使用 .map
从 [Positional
](https://docs.raku.org/type/Positional.html) 创建 [Map
](https://docs.raku.org/type/Map.html)。返回一个可以用作 .new
参数的 [Seq
](https://docs.raku.org/type/Seq.html)。这些基于原始值创建新值:
my $plus-one-seq = (1..3).map: * + 1;
my $double = (^3).map: { $^a + $^a }
Although the [Map ](https://docs.raku.org/type/Map.html) type and the .map method have the same name and do a similar job, one is an immutable object that provides the translation whereas the other is a method that transforms a [Positional ](https://docs.raku.org/type/Positional.html) into a [Seq ](https://docs.raku.org/type/Seq.html).
|
虽然 [Map
](https://docs.raku.org/type/Map.html) 类型和 .map
方法具有相同的名称并执行类似的工作,但是一个是提供转换的不可变对象,而另一个是将 [Positional
](https://docs.raku.org/type/Positional.html) 转换为 [Seq
](https://docs.raku.org/type/Seq.html) 的方法。
Your [Block
](https://docs.raku.org/type/Block.html) or thunk can take more than one parameter. Use two parameters to construct [Pair
](https://docs.raku.org/type/Pair.html)s:
你的[Block
](Pair
(https://docs.raku.org/type/Pair.html):
my $pairs = (^3).map: { $^a => 1 }; # (0 => 1 1 => 1 2 => 1)
my $pairs = (^3).map: * => 1; # same thing
There’s also a routine form of map
where the code comes first and the values come after it. There’s a required comma between them in either form:
还有一种例程形式的 map
,其中代码先出现,值出现在代码之后。两种形式之间都有一个必需的逗号:
my $pairs = map { $^a => 1 }, ^3;
my $pairs = map * => 1, ^3;
The result of the .map
can go right into the arguments for .new
:
.map
的结果可以直接进入 .new
的参数:
my $map-thingy = Map.new: (^3).map: { $^a => 1 }
These examples work because you want to produce [Pair
](https://docs.raku.org/type/Pair.html)s. If you want to simply create multiple items for a larger list you need to create a [Slip
](https://docs.raku.org/type/Slip.html) so you don’t end up with a [List
](https://docs.raku.org/type/List.html) of [List
](https://docs.raku.org/type/List.html)s:
这些示例有效,因为你想要生成 [Pair
](https://docs.raku.org/type/Pair.html)。如果你只想为更大的列表创建多个项目,则需要创建一个 [Slip
](列表
(列表
(https://docs.raku.org/type/List.html):
my $list = map { $^a, $^a * 2 }, 1..3; # ((1 2) (2 4) (3 6))
put $list.elems; # 3
You can fix that with slip
. This creates a [Slip
](https://docs.raku.org/type/Slip.html) object that automatically flattens into the structure that contains it:
你可以用 slip
修复它。这将创建一个[Slip
](https://docs.raku.org/type/Slip.html)对象,该对象会自动展平到包含它的结构中:
my $list = map { slip $^a, $^a * 2 }, 1..3; # (1 2 2 4 3 6)
put $list.elems; # 6
EXERCISE 9.2Rewrite the subroutine from the previous section using a [Map
](https://docs.raku.org/type/Map.html) to decide which [Pair
](https://docs.raku.org/type/Pair.html) you should return. If a number is not in the [Map
](https://docs.raku.org/type/Map.html), use th
. Add a new rule that numbers ending in 5
(but not 15
) should get the suffix ty
(like, 5ty
).
练习9.2使用 [Map
](https://docs.raku.org/type/Map.html) 重写上一节中的子程序,以决定应该返回哪一个 [Pair
](https://docs.raku.org/type/Pair.html)。如果数字不在 [Map
](https://docs.raku.org/type/Map.html) 中,请使用 th
。添加一个新规则,以 5
(但不是 15
)结尾的数字应该得到后缀 ty
(如,5ty
)。
4.15.3. 检查允许的值
A common use for a [Map
](https://docs.raku.org/type/Map.html) is to look up permissible values. Perhaps you only allow certain inputs in your subroutines. You can make those the keys of a [Map
](https://docs.raku.org/type/Map.html). If they are in the [Map
](https://docs.raku.org/type/Map.html) they are valid. If they aren’t, well, they aren’t.
[Map
](https://docs.raku.org/type/Map.html) 的常见用途是查找允许的值。也许你只允许子程序中的某些输入。你可以把它们作为[Map
](Map
(https://docs.raku.org/type/Map.html) 中他们是有效的。如果他们不是,那么,他们不是。
Go through the list of colors and return the color names, which you’ll use as the keys, and some value (1
is serviceable). You really want just the keys, so you can look them up later:
浏览颜色列表并返回颜色名称(你将用作键)和一些值(1可用)。你真的只想要键,所以你可以稍后查看它们:
my @permissable_colors = <red green blue>;
my $permissable_colors =
Map.new: @permissable_colors.map: * => 1;
loop {
my $color = prompt 'Enter a color: ';
last unless $color;
if $permissable_colors{$color}:exists {
put "$color is a valid color";
}
else {
put "$color is an invalid color";
}
}
This sort of data structure makes the lookup time the same no matter how many keys you have. Consider what you’d have to do with just the [List
](https://docs.raku.org/type/List.html). This scan-array
subroutine checks each element of the array until it finds a match:
无论你拥有多少个键,这种数据结构都会使查找时间保持不变。考虑一下你只需要列表。此扫描数组子例程检查数组的每个元素,直到找到匹配项:
sub scan-array ( $list, $item ) {
for @$list {
return True if $^element eq $item;
}
return False;
}
You might shorten your search by using .first
to stop when it finds an appropriate element. At worst this checks every element every time:
你可以使用 .first
缩短搜索范围,以便在找到合适的元素时停止搜索。在最坏的情况下,每次检查每个元素:
sub first-array ( Array $array, $item ) {
$array.first( * eq $item ).Bool;
}
EXERCISE 9.3Use the .map
technique to construct a [Map
](https://docs.raku.org/type/Map.html) from numbers between 1 and 10 (inclusively) to their squares. Create a loop to prompt for a number. If the number is in the [Map
](https://docs.raku.org/type/Map.html), print its square.
练习9.3使用 .map
技术从1到10之间的数字(包括)构造一个[Map
](Map
(https://docs.raku.org/type/Map.html)中,则打印其正方形。
4.16. Hashes
The [Hash
](https://docs.raku.org/type/Hash.html) is like a [Map
](https://docs.raku.org/type/Map.html) but mutable. You can add or delete keys and update values. This is the [Associative
](https://docs.raku.org/type/Associative.html)type you’ll probably use the most. Create a [Hash
](https://docs.raku.org/type/Hash.html) through its object constructor:
my $color-name-to-rgb = Hash.new:
'red', 'FF0000',
'green', '00FF00',
'blue', '0000FF',
;
That’s a bit tedious. You can enclose the key-value list in %()
instead:
这有点乏味。你可以将键值列表括在 %()
中:
my $color-name-to-rgb = %( # Still makes a Hash
'red', 'FF0000',
'green', '00FF00',
'blue', '0000FF',
);
Curly braces also work, but this is a discouraged form. With the fat arrow there are [Pair
](https://docs.raku.org/type/Pair.html)s inside the braces so the parser thinks this is a [Hash
](https://docs.raku.org/type/Hash.html):
花括号也有效,但这是一种沮丧的形式。使用胖箭头,在大括号内有[Pair
](Hash
(https://docs.raku.org/type/Hash.html):
my $color-name-to-rgb = { # Still makes a Hash
'red' => 'FF0000',
'green' => '00FF00',
'blue' => '0000FF',
};
If the parser doesn’t get enough hints about the contents of the braces, you might end up with a [Block
](https://docs.raku.org/type/Block.html) instead of a [Hash
](https://docs.raku.org/type/Hash.html):
如果解析器没有获得关于花括号内容的足够提示,那么最终可能会使用[Block
](Hash
(https://docs.raku.org/type/Hash.html):
my $color-name-to-rgb = { # This is a Block!
'red', 'FF0000',
'green', '00FF00',
'blue', '0000FF',
};
There’s a special sigil for [Associative
](https://docs.raku.org/type/Associative.html). If you use the %
sigil you can assign a [List
](https://docs.raku.org/type/List.html) to create your [Hash
](https://docs.raku.org/type/Hash.html):
[Associative
](https://docs.raku.org/type/Associative.html) 有一个特殊的印记。如果你使用 %
sigil,你可以指定一个[列表
](哈希
(https://docs.raku.org/type/Hash.html):
my %color-name-to-rgb =
'red', 'FF0000',
'green', '00FF00',
'blue', '0000FF'
;
Perhaps you don’t like your definition of blue. You can assign a new value to it. Notice that the sigil does not change for the single-element access:
也许你不喜欢你对蓝色的定义。你可以为其重新赋值。请注意,单元素访问的sigil不会更改:
%color<blue> = '0000AA'; # a bit darker
You can remove a key with the :delete
adverb. It returns the value of the just-deleted key:
你可以使用 :delete
副词删除键。它返回刚刚删除的键的值:
my $rgb = %color<blue>:delete
You can add new colors by assigning to the key that you want:
你可以通过分配所需的键来添加新颜色:
%color<mauve> = 'E0B0FF';
EXERCISE 9.4Update your ordinal suffix program to use a [Hash
](https://docs.raku.org/type/Hash.html). That’s the easy part. Once you’ve got that working, use your [Hash
](https://docs.raku.org/type/Hash.html) to cache values so you don’t have to compute their result again.
练习9.4 更新你的序数后缀程序以使用[Hash
](Hash
(https://docs.raku.org/type/Hash.html)缓存值,这样你就不必再次计算他们的结果。
4.16.1. Accumulating with a Hash
Counting is another common use for a [Hash
](https://docs.raku.org/type/Hash.html). The key is the thing you want to count and its value is the number of times you’ve encountered it. First, you need something to count. Here’s a program that simulates rolling some dice:
计数是[Hash
](https://docs.raku.org/type/Hash.html)的另一种常见用法。键是你想要计算的东西,它的值是你遇到它的次数。首先,你需要一些东西来计算。这是一个模拟滚动骰子的程序:
sub MAIN ( $die-count = 2, $sides = 6, $rolls = 137 ) {
my $die_sides = 6;
for ^$rolls {
my $roll = (1..$sides).roll($die-count).List;
my $sum = [+] $roll;
put "($roll) is $sum";
}
}
The .roll
method picks an element from your [List
](https://docs.raku.org/type/List.html) the number of times you specify. Each time it picks an element is independent of other times, so it might repeat some values. This produces output that shows the individual die values and the sum of the values:
.roll
方法从[List
](https://docs.raku.org/type/List.html)中选择一个元素指定的次数。每次拾取元素都与其他时间无关,因此它可能会重复某些值。这会生成输出,显示各个芯片值和值的总和:
(3 4) is 7
(4 1) is 5
(6 4) is 10
(2 6) is 8
(6 6) is 12
(1 4) is 5
(5 6) is 11
Now you have several things to count. Start by counting the sums. Inside that for
, use the sum as the [Hash
](https://docs.raku.org/type/Hash.html) key and the number of times you encounter it as the value:
现在你需要考虑几件事。首先计算总和。在 for
里面,使用 sum 作为 [Hash
](https://docs.raku.org/type/Hash.html) 键以及你遇到它的次数作为值:
sub MAIN ( $die-count = 2, $sides = 6, $rolls = 137 ) {
my $die_sides = 6;
my %sums;
for ^$rolls {
my $roll = (1..$sides).roll($die-count).List;
my $sum = [+] $roll;
%sums{$sum}++;
}
# sort the hash by its value
my $seq = %sums.keys.sort( { %sums{$^a} } ).reverse;
for @$seq {
put "$^a: %sums{$^a}"
}
}
Now you get a dice sum frequency report:
现在你得到一个骰子和的频率报告:
7: 27
8: 25
5: 19
4: 13
9: 12
6: 11
11: 9
10: 8
3: 7
2: 3
12: 3
If you are motivated enough, you can compare those values to the probabilities for perfect dice. But there’s another interesting thing you can count—the rolls themselves. If you use $roll
as the key it stringifies it. You can then count the unique stringifications. Sort the results so equivalent rolls such as (1 6)
and (6 1)
become the same key:
如果你有足够的动力,你可以将这些值与完美骰子的概率进行比较。但是你可以计算另一个有趣的事情 - rolls 本身。如果你使用 $roll
作为键,则将其字符串化。然后,你可以计算唯一的字符串。对结果进行排序,使等效的卷(如`(1 6)` 和 (6 1)
)成为相同的键:
sub MAIN ( $die-count = 2, $sides = 6, $rolls = 137 ) {
my $die_sides = 6;
my %sums;
for ^$rolls {
my $roll = (1..$sides).roll($die-count).sort.List;
%sums{$roll}++;
}
my $seq = %sums.keys.sort( { %sums{$^a} } ).reverse;
for @$seq {
put "$^a: %sums{$^a}"
}
}
Now you get a sorted list of your dice rolls:
现在你得到一个你的骰子卷的排序列表:
3 4: 15
1 4: 11
1 2: 10
2 5: 9
3 5: 9
3 6: 9
2 3: 8
EXERCISE 9.5Create a program to count the occurrences of words in a file and output the words sorted by their count. Store each lowercased word as the key in a hash and increment its value every time you see it. Don’t worry about punctuation or other characters; you’ll learn how to deal with those later. What happens if two words have the same count?
练习9.5 创建一个程序来计算文件中单词的出现次数,并输出按其计数排序的单词。将每个小写单词作为键存储在哈希中,并在每次看到它时增加其值。不要担心标点符号或其他字符;你将学习如何在以后处理这些问题。如果两个单词的计数相同,会发生什么?
4.17. Multilevel Hashes
Hash values can be almost anything, including another [Hash
](https://docs.raku.org/type/Hash.html) or [Array
](https://docs.raku.org/type/Array.html). Here’s an example with a couple of [Hash
](https://docs.raku.org/type/Hash.html)es that count the number of butterflies in the Hamadryas and Danaus genera:
散列值几乎可以是任何值,包括另一个散列或数组。这是一个有几个哈希数的例子,它们计算了哈马德里亚斯和丹那属的蝴蝶数量:
my %Hamadryas = map { slip $_, 0 }, <
februa
honorina
velutina
>;
my %Danaus = map { slip $_, 0 }, <
gilippus
melanippus
>;
But you want to contain all of that in one big [Hash
](https://docs.raku.org/type/Hash.html), so you construct that. The [Hash
](https://docs.raku.org/type/Hash.html) value is another [Hash
](https://docs.raku.org/type/Hash.html):
my %butterflies = (
'Hamadryas' => %Hamadryas,
'Danaus' => %Danaus,
);
say %butterflies;
The %butterflies
data structure looks like this (using the discouraged braces form):
%butterflies
数据结构看起来像这样(使用不鼓励的花括号形式):
{Danaus => {gilippus => 0, melanippus => 0},
Hamadryas => {februa => 0, honorina => 0, velutina => 0}}
Suppose you want to see the count for Danaus melanippus. You have to look in the top-level [Hash
](https://docs.raku.org/type/Hash.html) to get the value for Danaus
, then take that value and look at its melanippus
keys:
假设你想看看Danaus melanippus的数量。你必须查看顶级Hash以获取Danaus的值,然后获取该值并查看其melanippus键:
my $genus = %butterflies<Danaus>;
my $count = $genus<melanippus>;
That’s too much work. Line up the subscripts in one expression:
那工作太多了。在一个表达式中排列下标:
put "Count is %butterflies<Danaus><melanippus>";
When you want to count a particular butterfly, you can do that:
当你想要计算一只特定的蝴蝶时,你可以这样做:
%butterflies<Danaus><melanippus>++;
EXERCISE 9.6Read the lines from the butterfly census file (from [https://www.learningraku.com/downloads/](https://www.learningraku.com/downloads/)) and break each line into a genus and species. Count each combination of genus and species. Report your results.
练习9.6 读取蝴蝶人口普查文件中的行(来自https://www.learningraku.com/downloads/),并将每行划分为属和种。计算属和物种的每个组合。报告你的结果。
EXERCISE 9.7Modify the previous exercise to write the genus and species counts to a file. Each line of the file should have the genus, species, and count separated by a tab. You’ll need this file for an exercise in [Chapter 15](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch15.html#camelia-regex1).
练习9.7 修改上一个练习,将属和种类计数写入文件。文件的每一行都应具有由制表符分隔的属,种和计数。你将需要此文件进行第15章的练习。
4.18. 总结
[Associative
](https://docs.raku.org/type/Associative.html)s let you quickly get from a [Str
](https://docs.raku.org/type/Str.html) to another value. There are several types that facilitate this. At the lowest level is the [Pair
](https://docs.raku.org/type/Pair.html) of one key and one value. A [Map
](https://docs.raku.org/type/Map.html) fixes those once created (much like a [List
](https://docs.raku.org/type/List.html)), while a [Hash
](https://docs.raku.org/type/Hash.html) is more flexible (much like an [Array
](https://docs.raku.org/type/Array.html)). These will probably be some of the most useful and hard-working data structures that you’ll encounter.
[Associative
](字符串
(https://docs.raku.org/type/Str.html)到另一个值。有几种类型可以促进这一点。最低级别是一对一键和一个值。 [Map
](https://docs.raku.org/type/Map.html) 修复了曾经创建的(很像[列表
](Hash
(https://docs.raku.org/type/Hash.html)更灵活(很像数组)。这些可能是你将遇到的一些最有用和最勤奋的数据结构。
== 使用模块
Modules allow you to compartmentalize, distribute, and reuse code. Someone creates a general solution to something, then packages it so you can reuse it in your programs. Sometimes people make these modules available to everyone. You can find some Raku modules at https://modules.raku.org
You don’t have to understand the code inside a module to benefit from its features. You can usually follow the examples in its documentation even if it uses syntax that you haven’t already seen.
模块允许你划分,分发和重用代码。有人创建了一个通用的解决方案,然后将其打包,以便你可以在程序中重用它。有时人们会向所有人提供这些模块。你可以在 https://modules.raku.org 找到一些 Raku 模块。
你无需了解模块内部的代码即可从其功能中受益。你通常可以按照其文档中的示例进行操作,即使它使用你尚未看到的语法。
4.19. Installing Modules
zef is one of the Raku module managers. It can install, update, and uninstall modules. It comes with Rakudo Star but you can install it yourself:
zef 是 Raku 的模块管理器之一。它可以安装,更新和卸载模块。它附带在 Rakudo Star 中,但你可以自行安装:
% git clone https://github.com/ugexe/zef.git
% cd zef
% raku -Ilib bin/zef install .
Once you have zef you can install modules. The Task::Popular
module installs those most used by other modules:
一旦你有了 zef,你就可以安装模块了。 Task::Popular
模块安装其他模块最常用的模块:
% zef install Task::Popular
You can install a module by name if the author has registered it in the module ecosystem:
如果作者已在模块生态系统中注册了模块,你可以按名称安装模块:
% zef install HTTP::Tiny
You can also tell it to install the code directly from a Git repository:
你也可以告诉它直接从 Git 仓库安装代码:
% zef install https://github.com/sergot/http-useragent.git
% zef install git://github.com/sergot/http-useragent.git
Ensure that you are using the clone URL and not the project page URL.
You can install from a local directory if the module infrastructure is there and there’s a META6.json file. You have to make the argument to zef not look like a module name. This one looks for a module directory in the current directory:
确保你使用的是克隆 URL,而不是项目页面 URL。
如果存在模块基础结构且存在 META6.json 文件,则可以从本地目录进行安装。你必须使 zef 的参数看起来不像模块名。这个在当前目录中查找模块目录:
% zef install ./json-tiny
You can install the modules from the current directory by using the .
as the current working directory:
你可以使用 .
作为当前工作目录从当前目录安装模块:
% zef install .
You may find references to panda, an early module installation tool. It’s the old, unsupported tool. *zef*is the new hotness. Double-check the [documentation](https://docs.raku.org/language/modules), though, since the favored tool may change by the time you read this book. |
EXERCISE 10.1Install the Inline::Perl5
module by its name. You’ll use that module later in this chapter. Install the Grammar::Debugger
module by its repository URL. You’ll use that module in [Chapter 17](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch17.html#camelia-grammars). Find and clone the Grammar::Tracer
module repository, change into its local directory, and install it from that directory.
你可以找到对 panda 的参考,这是一个早期的模块安装工具。这是旧的,不受支持的工具。 zef 是新秀。但请仔细检查[文档](https://docs.raku.org/language/modules),因为在你阅读本书时,受欢迎的工具可能会发生变化。
练习10.1 按名称安装 Inline::Perl5
模块。你将在本章后面使用该模块。通过其仓库 URL 安装 Grammar::Debugger
模块。你将在第 17 章中使用该模块。查找并克隆 Grammar::Tracer
模块仓库,切换到其本地目录,然后从该目录安装它。
4.20. 加载模块
You load a module into your program with need
. This searches through the module repository looking for a match. If you’ve installed modules with zef they should be in the repository (in the next section I show you how to tell your program to look in other places too):
你使用 need
将模块加载到程序中。这将搜索模块存储库以查找匹配项。如果你已经使用 zef 安装了模块,那么它们应该在存储库中(在下一节中,我将向你展示如何告诉你的程序在其他地方查找):
need Number::Bytes::Human;
my $human = Number::Bytes::Human.new;
put $human.format(123435653); # '118M'
put $human.parse('3GB'); # 3221225472
Loading a module with use
does the same thing but also automatically imports anything the module has set to export. This allows modules to define thingys in your current scope as if you’d defined the code there yourself:
使用 use
加载模块会做同样的事情,但也会自动导入模块设置为导出的任何内容。这允许模块在当前作用域内定义东西,就像你自己定义了代码一样:
use Number::Bytes::Human;
This is the same as doing a need
and then an import
:
这与执行 need
然后 import
相同:
need Number::Bytes::Human;
import Number::Bytes::Human;
Some modules import things automatically and others wait until you ask for them. You can specify a list after the module name that asks for specific imports. The Number::Bytes::Human
module uses an adverb for that:
有些模块自动导入东西,有些模块会等到你要求它们。你可以在要求特定导入的模块名称后指定一个列表。 Number::Bytes::Human
模块为此使用一个副词:
use Number::Bytes::Human :functions;
put format-bytes(123435653); # '118M'
put parse-bytes('3GB'); # 3221225472
No matter which way you load it, follow the examples in the documentation (or maybe look in the module tests).
无论你以哪种方式加载,请按照文档中的示例(或者查看模块测试)。
4.20.1. 查找模块
When you install a module with zef the module’s filename becomes a digest of that file and is saved in one of the module repositories—this allows several versions and sources of the file to be simultaneously installed. You can see this path with the command zef locate
:
当你使用 zef 安装模块时,模块的文件名将成为该文件的摘要并保存在其中一个模块存储库中 - 这允许同时安装该文件的多个版本和源。你可以使用命令 zef locate
查看此路径:
% zef locate Number::Bytes::Human
===> From Distribution: Number::Bytes::Human:ver<0.0.3>:auth<>:api<>
Number::Bytes::Human => /opt/raku/site/sources/A5EA...
As you can see in the output, the module shows up in a cryptically named file. Raku uses several methods to store and retrieve compunits in repositories. This is very flexible but also much more than I have space to explain here. For the most part you don’t need to worry about that.
正如你在输出中看到的那样,该模块显示在一个文件名加密过的文件中。 Raku 使用几种方法来存储和检索存储库中的 compunits。这非常灵活,但也比我在这里解释的要多。在大多数情况下,你无需担心这一点。
The repository system is complicated because it can manage the same module name with different versions or authors. This means it’s possible to store or load old and new module versions simultaneously. |
存储库系统很复杂,因为它可以使用不同的版本或作者管理相同的模块名称。这意味着可以同时存储或加载新旧模块版本。
LIB 指令
No matter where the module is, you need to tell your program where to find it. zef uses the repositories that raku*has configured by default. The lib
pragma can add a directory as a repository. You can store plain files in there (that is, unmanaged by Raku). The module name is translated to a path by replacing ::
with a /
and adding a *.pm or .pm6 extension:
无论模块在哪里,你都需要告诉你的程序在哪里找到它。 zef 使用 raku 默认配置的存储库。 lib
指令可以将目录添加为存储库。你可以在其中存储普通文件(即,不受 Raku 管理)。通过将 ::
替换为 /
并添加 .pm
或 .pm6
扩展名将模块名称转换为路径:
use lib </path/to/module/directory>;
use Number::Bytes::Human
This looks for Number/Bytes/Human.pm or Number/Bytes/Human.pm6 in /path/to/module/directory.
You can specify multiple directories:
这将在 /path/to/module/directory 中查找 Number/Bytes/Human.pm 或 Number/Bytes/Human.pm6。
你可以指定多个目录:
use lib </path/to/module/directory /other/path>;
Or specify lib
multiple times:
或者多次指定 lib
:
use lib '/path/to/module/directory';
use lib '/other/path';
Relative paths resolve themselves according to the current working directory:
相对路径根据当前工作目录自行解析:
use lib <module/directory>; # looks for module/ in current dir
The .
works as the current working directory. People tend to do this if the module file and the program are in the same directory:
这个 .
作为当前工作目录。如果模块文件和程序在同一目录中,人们倾向于这样做:
use lib <.>:
You should carefully consider using the current working directory in your library search path. Since it’s a relative location you’re never quite sure where it’s looking. Running your program from a different directory (with a command like the following) means your program looks in a different directory and probably won’t find the module:
你应该仔细考虑在库搜索路径中使用当前工作目录。由于它是一个相对位置,你永远不能确定它在哪里。从不同的目录运行程序(使用如下命令)意味着你的程序在不同的目录中查找,可能找不到该模块:
% raku bin/my-program
It takes a bit more work to figure out the relative directory. Your program’s path is in the special variable $PROGRAM
. You can turn that into an [IO::Path
](https://docs.raku.org/type/IO::Path.html) object with .IO
and use .parent
to get its directory. You can use that to add a *lib directory at the same level as your program:
找出相对目录需要更多的工作。你的程序路径位于特殊变量 $PROGRAM
中。你可以使用 .IO
将其转换为 [IO::Path
](https://docs.raku.org/type/IO::Path.html) 对象,并使用 .parent
来获取其目录。你可以使用它来添加与程序相同级别的 *lib 目录:
# random-between.p6
use lib $*PROGRAM.IO.parent;
use lib $*PROGRAM.IO.parent.add: 'lib';
There’s also the $?FILE
compile-time variable:
还有 $?FILE
编译时变量:
use lib $?FILE.IO.parent;
use lib $?FILE.IO.parent.add: 'lib';
For this to work you must add the paths to search before you try to load the library. It does no good to tell it where to look after it has already looked!
为此,你必须在尝试加载库之前添加要搜索的路径。告诉它已经看过它在哪里照顾它没有用!
环境
The PERL6LIB
environment variable applies to every program you run in the current session. Separate the directories with commas (no matter which system you are using). Here it is in bash syntax:
PERL6LIB
环境变量适用于你在当前会话中运行的每个程序。用逗号分隔目录(无论你使用哪个系统)。这是 bash 语法:
% export PERL6LIB=/path/to/module/directory,/other/path
And in Windows syntax:
在 Windows 语法中:
C:\ set PERL6LIB=C:/module/directory,C:/other/path
-I 开关
The -I
switch to raku works for a single run of a program. This is handy inside a project repository (a different sort of repository!) that you haven’t installed. You can use the development version of the module from the project repository instead of a previous one you might have installed:
-I
切换到 raku 适用于单个程序运行。这在你尚未安装的项目存储库(不同类型的存储库!)中很方便。你可以使用项目存储库中的模块开发版本,而不是之前安装的模块:
% raku -Ilib bin/my_program.p6
Specify more than one extra directory with multiple -I
switches or by separating them with commas:
使用多个 -I
开关指定多个额外目录,或者用逗号分隔它们:
% raku -Ilib -I../lib bin/my_program.p6
% raku -Ilib,../lib bin/my_program.p6
You can see the -I
at work if you want to use prove to run Raku module tests. The argument to -e
is the interpreter to use (with Perl 5 being the default). You want a raku that looks for the development modules in the current repository:
如果要使用 prove 来运行 Raku 模块测试,可以看到 -I
在工作。 -e
参数是要使用的解释器(Perl 5 是默认值)。你需要一个在当前存储库中查找开发模块的 raku:
% prove -e "raku -Ilib"
The $*REPO
variable can tell you where Raku will look for modules. These aren’t just directories. The repositories could be almost anything—including other code:
$*REPO
变量可以告诉你 Raku 在哪里寻找模块。这些不仅仅是目录。存储库几乎可以是任何东西 - 包括其他代码:
for $*REPO.repo-chain -> $item {
say $item;
}
EXERCISE 10.2Create a program to show the repository chain. Run it in several situations using PERL6LIB
, -I
, and use lib
.
EXERCISE 10.2 创建一个程序来显示存储库链。使用 PERL6LIB
,-I
和 use lib
在几种情况下运行它。
4.20.2. Lexical Effect
Loading a module only affects the current scope. If you load a module in a [Block
](https://docs.raku.org/type/Block) it’s only available in that[Block
](https://docs.raku.org/type/Block), and anything it imports is only available in that [Block
](https://docs.raku.org/type/Block). Outside of the [Block
](https://docs.raku.org/type/Block) the program doesn’t know about the module:
加载模块仅影响当前作用域。如果在[块
](https://docs.raku.org/type/Block) 中加载模块,它只在该[块
](块
(https://docs.raku.org/type/Block)中可用。在 [块
](https://docs.raku.org/type/Block) 之外,程序不知道该模块:
{
use Number::Bytes::Human;
my $human = Number::Bytes::Human.new; # works in here
put $human.format(123435653); # '118M'
}
my $human = Number::Bytes::Human.new; # ERROR: not defined here
You’ll get an odd error where your program is looking for a subroutine that has a name that’s the same as the last part of the module name:
你的程序正在寻找一个名称与模块名称的最后部分相同的子程序,你会得到一个奇怪的错误:
Could not find symbol '&Human'
You can limit module imports to just where you need them. If you only need it in a subroutine you can load it there:
你可以将模块导入限制在你需要的位置。如果你只需要在子程序中,你可以在那里加载它:
sub translate-it ( Int $bytes ) {
use Number::Bytes::Human;
state $human = Number::Bytes::Human.new;
$human.format( $bytes );
}
This means that you could load different versions of the same module for different parts of your program. The lib
declaration is lexically scoped as well:
这意味着你可以为程序的不同部分加载同一模块的不同版本。 lib
声明也是词法作用域的:
sub stable-version {
use Number::Bytes::Human;
...
}
sub experimental-version {
use lib </home/hamadryas/dev-module/lib>;
use Number::Bytes::Human;
...
}
This sort of thing is often handy to convert data formats when they change between module versions:
当模块版本之间的数据格式发生变化时,转换数据格式通常很方便:
sub translate-to-new-format ( Str $file ) {
my $data = do {
use lib </path/to/legacy/lib>;
use Module::Format;
Module::Format.new.load: $file;
};
use Module::Format; # new version
Module::Format.new.save: $data, $file;
}
4.20.3. 在运行时加载模块
need
and use
load a module as the program compiles. Sometimes you don’t know which module you want until you want to use it, or you know that you might use one of several modules but only want to load the one you’ll actually use. You can wait until runtime to load it with require
. If the module isn’t there the `require`throws an exception:
need
和 use
在程序编译时加载模块。有时你不知道你想要使用哪个模块直到你要使用它,或者你知道你可能使用几个模块中的一个但只想加载你实际使用的模块。你可以等到运行时使用 require
加载它。如果模块不在那里,则 require
抛出异常:
try require Data::Dump::Tree;
if $! { die "Could not load module!" }
Even if the module fails to load, the require still creates the type. You can’t rely on the type being defined as a signal of successful loading.
|
Perhaps you want to check that a module is installed before you try to load it. The $*REPO
object has a`.resolve` method that can find a module from its dependency specification:
即使模块无法加载,require
仍会创建类型。你不能依赖被定义的类型作为成功加载的信号。
也许你想在尝试加载模块之前检查模块是否已安装。 $*REPO
对象有一个 .resolve
方法,可以从其依赖项规范中找到一个模块:
my $dependency-spec =
CompUnit::DependencySpecification.new: :short-name($module);
if $*REPO.resolve: $dependency-spec {
put "Found $module";
}
EXERCISE 10.3Write a program that reports whether a module is installed. Try this with Number::Bytes::Human
(assuming you installed it so it is present) and Does::Not::Exist
(or any other name that isn’t a module).
练习10.3 编写一个报告模块是否已安装的程序。尝试使用 Number::Bytes::Human
(假设你安装它以便它存在)和 Does::Not::Exist
(或任何其他不是模块的名称)。
插值模块名
You can interpolate a [Str
](https://docs.raku.org/type/Str.html) where you’d normally have a literal class name by putting the [Str
](https://docs.raku.org/type/Str.html) inside ::()
:
你可以通过将[字符串
](https://docs.raku.org/type/Str.html)放入 ::()
来插入一个通常具有字面类名称的[字符串
](https://docs.raku.org/type/Str.html):
require ::($module);
Anywhere you’d use the literal class name you can use that ::($module)
. When you want to create an object but you don’t know the literal module name, you interpolate it just as you did in the require
:
在你使用字面类名称的任何地方,你都可以使用 ::($module)
。如果要创建对象但不知道字面模块名称,则可以像在 require
中一样进行插值:
my $new-object = ::($module).new;
Not only that, but you can use a method name from a [Str
](https://docs.raku.org/type/Str.html) by putting it in double quotes. You must use the parentheses for the argument list when you do this:
不仅如此,你还可以通过将其放在双引号中来使用[字符串
](https://docs.raku.org/type/Str.html)中的方法名称。执行此操作时,必须使用圆括号作为参数列表:
$new-object."$method-name"( @args );
You can use the return value of require
. If it was able to load the module that value is the type:
你可以使用 require
的返回值。如果它能够加载模块的值是类型:
my $class-i-loaded = (require ::($module));
my $object = $class-i-loaded.new;
This might work better with a literal name that you don’t want to repeatedly type:
对于你不想重复输入的字面名称,这可能会更好:
my $class-i-loaded = (require Digest::MD5);
my $object = $class-i-loaded.new;
Checking this is a bit tricky. You can’t simply check for the type because that will be defined no matter which way it goes. Check that it’s not a [Failure
](https://docs.raku.org/type/Failure.html):
检查这个有点棘手。你无法简单地检查类型,因为无论它采用何种方式,都将对其进行定义。检查它不是 [Failure
](https://docs.raku.org/type/Failure.html):
my $module = 'Hamadryas';
try require ::($module);
put ::($module).^name; # Failure
say ::($module).^mro; # ((Failure) Nil (Cool) (Any) (Mu))
if ::($module) ~~ Failure {
put "Couldn't load $module!"; # Couldn't load Hamadryas!
}
These aren’t tricks to use frequently, but they are there as a last resort should you need them. Here’s a program that lets you choose which dumper class to use. It uses a [Hash
](https://docs.raku.org/type/Hash.html) to translate the class name to the method name it uses. At the end it merely dumps the only [Hash
](https://docs.raku.org/type/Hash.html) defined in the program:
这些不是经常使用的技巧,但如果你需要它们,它们是最后的手段。这是一个程序,可以让你选择要使用的转储程序类。它使用 [Hash
](https://docs.raku.org/type/Hash.html) 将类名转换为它使用的方法名。最后它只转储程序中定义的唯一 [Hash
](https://docs.raku.org/type/Hash.html) :
sub MAIN ( Str $class = 'PrettyDump' ) {
my %dumper-adapters = %(
'Data::Dump::Tree' => 'ddt',
'PrettyDump' => 'dump',
'Pretty::Printer' => 'pp',
);
CATCH {
when X::CompUnit::UnsatisfiedDependency {
note "Could not find $class";
exit 1;
}
default {
note "Some other problem with $class: {.message}";
exit 2;
}
}
require ::($class);
my $method = %dumper-adapters{$class};
unless $method {
note "Do not know how to dump with $class";
exit 2;
}
put ::($class).new."$method"( %dumper-adapters );
}
EXERCISE 10.4Modify the dumping program. Create another subroutine that takes a list of modules and returns the ones that are installed. Use that subroutine to provide the default for MAIN
.
练习10.4 修改转储程序。创建另一个子例程,该子例程获取模块列表并返回已安装的模块。使用该子例程为 MAIN
提供默认值。
4.21. 从 Web 抓取数据
HTTP::UserAgent
is a handy module to fetch data from the web. Install it with zef and follow the example:
HTTP::UserAgent
是一个方便的模块,用于从 Web 抓取数据。用 zef 安装它并按照示例:
use HTTP::UserAgent;
my $ua = HTTP::UserAgent.new;
$ua.timeout = 10;
my $url = ...;
my $response = $ua.get( $url );
my $data = do with $response {
.is-success ?? .content !! die .status-line
}
Once you have the data you can do whatever you like, including reading some lines from it:
获得数据后,你可以随意执行任何操作,包括从中读取一些行:
for $data.lines(5) -> $line {
put ++$, ': ', $line;
}
EXERCISE 10.5Write a program that fetches the URL you provide on the command line, then outputs its contents to standard output.
练习10.5 编写一个程序,用于抓取你在命令行上提供的 URL,然后将其内容输出到标准输出。
4.22. 在 Raku 中运行 Perl 5
One of Raku’s original goals was Perl 5 interoperability. Larry Wall said that if the new Raku could run “with 95-percent accuracy 95 percent of the [Perl 5] scripts, and 100-percent accuracy 80 percent of the [Perl 5] scripts, then that’s getting into the ballpark.” This meant, as a goal, that a lot of the current Perl 5 contents of the Comprehensive Perl Archive Network (CPAN) would be available in Raku.
The Inline::Perl5
module allows you to load Perl 5 modules or evaluate Perl 5 snippets from a Raku program. Add the source :from<Perl5>
after the module you want to load, then translate the syntax to Raku (so, .
for a method call and so on). You don’t have to load Inline::Perl5
explicitly in this case:
Raku 最初的目标之一是 Perl 5 的互操作性。 Larry Wall 表示,如果新的 Raku 能够“以 95% 的[Perl 5]脚本 95% 的准确率运行,并且 80% 的[Perl 5]脚本能够 100% 准确地运行,那么它就会进入可变通范围。”这意味着,作为一个目标,Raku 中将提供 Comprehensive Perl Archive Network(CPAN)的许多当前 Perl 5 内容。
Inline::Perl5
模块允许你加载 Perl 5 模块或计算 Raku 程序中的 Perl 5 片段。在要加载的模块之后添加源::from<Perl5>
,然后将语法转换为 Raku(因此,.
用于方法调用等等。)。在这种情况下,你不必显式加载 Inline::Perl5
:
use Business::ISBN:from<Perl5>;
my $isbn = Business::ISBN.new( '9781491977682' );
say $isbn.as_isbn10.as_string;
You can have Perl 5 code in your program and call it when you need it, dropping in and out of it as you like. Create an object that will handle the Perl 5 code for you:
你可以在程序中使用 Perl 5 代码,并在需要时调用它,根据需要加入和退出。创建一个将为你处理 Perl 5 代码的对象:
use Inline::Perl5;
my $p5 = Inline::Perl5.new;
$p5.run: q:to/END/;
sub p5_test { return 'Hello from Perl 5!' }
END
put 'Hello from Raku!';
$p5.run: 'print p5_test()';
EXERCISE 10.6Compare the results of the Perl 5 and 6 versions of Digest::MD5
by loading them into the same program. Get the digest of the program itself. You can use slurp
to read the entire contents of a file.
练习10.6将 Digest::MD5
的 Perl 5 和 Raku 版本的结果加载到同一程序中。获取程序本身的摘要。你可以使用 slurp
来读取文件的全部内容。
4.23. 总结
You’ve learned how to find and install modules with zef. You can often simply follow the example in the module’s documentation to get what you want. Before you set out to program something, see if someone else has already done it.
You’re not limited to modules from Raku either. The Inline
modules allow you to use code from other languages. If you have a favorite module you might not have to give it up.
你已经学会了如何使用 zef 查找和安装模块。你通常可以按照模块文档中的示例来获得所需内容。在你开始编程之前,看看其他人是否已经做过了。
你不仅限于 Raku 的模块。Inline
模块允许你使用其他语言的代码。如果你有一个喜欢的模块,你可能不必放弃它。
== 子例程
Now it’s time for more sophisticated subroutines. You were introduced to them in [Chapter 5](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch05.html#camelia-blocks) but you only saw enough to support the upcoming chapters. Now that you’ve seen [Array
](https://docs.raku.org/type/Array.html)s and [Hash
](https://docs.raku.org/type/Hash.html)es, there’s much more you can do with subroutine signatures.
现在是更复杂的子例程的时候了。你在第五章见过它们了,但你只看到足以支持即将到来的章节。现在你已经看过[数组
](https://docs.raku.org/type/Array.html) 和 [哈希
](https://docs.raku.org/type/Hash.html),你可以用子例程签名做更多的事情。
4.24. A Basic Subroutine
When you run a subroutine you get some sort of result: the last evaluated expression. That’s the return value. This sets basic routines apart from the simpler [Block
](https://docs.raku.org/type/Block.html)s you saw in [Chapter 5](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch05.html#camelia-blocks). A [Routine
](https://docs.raku.org/type/Routine.html) knows how to send a value back to the code that called it. This subroutine returns a different [Str
](https://docs.raku.org/type/Str.html) if the argument is odd or even:
当你运行子例程时,你会得到某种结果:最后被计算的表达式。这是返回值。这将基础子例程和你在第五章中看到的更简单的 [Block
](https://docs.raku.org/type/Block.html) 区分开。[例程
](字符串
(https://docs.raku.org/type/Str.html):
sub odd-or-even {
if ( @_[0] %% 2 ) { 'Even' }
else { 'Odd' }
}
odd-or-even( 2 ); # Even
odd-or-even( 137 ); # Odd
Without a signature the arguments show up in @_. Each subroutine has its own version of that variable so it doesn’t conflict with any other subroutine’s @_. This code calls one subroutine that calls another. top-call`shows its `@_
before and after show-args
:
如果没有签名,则参数会出现在 @_ 中。每个子例程都有自己的变量版本,因此它不会与任何其它子例程的 @_ 冲突。此代码调用一个调用另一个子例程的子例程。 top-callshows
显示 show-args
之前和之后的 @_
:
top-call( <Hamadryas perlicus> );
sub show-args { say @_ }
sub top-call {
put "Top: @_[]";
show-args( <a b c> );
put "Top: @_[]";
}
Even though both use @
they are separate. The @
in top-call
isn’t disturbed by show-args
:
尽管两者都使用 @
,但它们是分开的。 top-call
中的 @
不受 show-args
的干扰:
Top: Hamadryas perlicus
[a b c]
Top: Hamadryas perlicus
The subroutine definition is lexically scoped. If you need it for only part of the code you can hide it in a [Block
](https://docs.raku.org/type/Block.html). Outside the [Block
](https://docs.raku.org/type/Block.html) that subroutine is not visible:
子例程定义是词法范围的。如果只需要部分代码,则可以将其隐藏在 Block 中。在 Block 之外,子例程不可见:
{
put odd-or-even( 137 );
sub odd-or-even { ... } # only defined in this block
}
put odd-or-even( 37 ); # undeclared routine!
4.24.1. 额外的参数
What does odd-or-even
accept, though? The parameter is an [Array
](https://docs.raku.org/type/Array.html) but you only use the first element. These calls still work without warnings or errors:
但是 odd-or-even
接受了什么?参数是一个数组,但你只使用第一个元素。这些调用仍然有效,没有警告或错误:
put odd-or-even( 2, 47 ); # Even
put odd-or-even( 137, 'Hello' ); # Odd
This isn’t necessarily wrong. It depends on what you are trying to do. Maybe you specifically want as many arguments as the caller decides to send:
这不一定是错的。这取决于你想要做什么。也许你特别想要调用者决定发送尽可能多的参数:
sub plus-minus {
[-]
@_
.rotor(2, :partial)
.map: { $^a[0] + ($^a[1] // 0) }
}
put plus-minus( 9,1,2,3 );
With the signatures you’ll see later in the chapter you’ll be able to control this to get the situation that you want.
使用你将在本章后面看到的签名,你将能够控制它以获得你想要的情况。
4.24.2. 显式返回
You can explicitly return from anywhere in a subroutine with return
. This distinguishes a subroutine from the [Block
](https://docs.raku.org/type/Block.html)s you used in [Chapter 5](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch05.html#camelia-blocks). This version is the same thing but with an explicit return
:
你可以使用 return
从子例程中的任何位置显式返回。这将子例程与你在第五章中使用的 Block 区分开来。这个版本是相同的,但具有明确的返回:
sub odd-or-even ( $n ) {
if ( $n %% 2 ) { return 'Even' }
else { return 'Odd' }
}
Call this with extra arguments and you get an error:
使用额外的参数调用此方法会出现错误:
put odd-or-even( 2, 47 ); # Error
The message tells you the argument list does not match the signature:
该消息告诉你参数列表与签名不匹配:
Calling odd-or-even(Int, Int) will never work with declared signature ($n)
You could write this differently. do
converts the entire if
structure into something that evaluates to its last evaluated expression. Return the value of the do
instead of repeating the return
:
你可以用不同的方式写出来。do
将整个 if
结构转换为计算其最后计算的表达式的内容。返回 do
的值而不是重复 return
:
sub odd-or-even ( $n ) {
return do {
if ( $n %% 2 ) { 'Even' }
else { 'Odd' }
}
}
The conditional operator is the same thing expressed differently:
条件运算符是同样的东西,它以不同的方式表达:
sub odd-or-even ( $n ) {
return $n %% 2 ?? 'Even' !! 'Odd'
}
Another way to do the same thing is to have a default return value but return sooner for other situations:
另一种做同样事情的方法是使用默认返回值,但在其他情况下更快返回:
sub odd-or-even ( $n ) {
return 'Even' if $n %% 2;
'Odd';
}
Or back to where you started with an implicit return:
或者回到你开始的隐式返回:
sub odd-or-even ( $n ) { $n %% 2 ?? 'Even' !! 'Odd' }
These techniques are more appealing in more complex situations that I’m not going to show here. No matter which of these serves your situation, they all do the same thing: they send a value back to the code that called it.
这些技术在更复杂的情况下更具吸引力,我不会在这里展示。无论哪种情况适合你的情况,它们都会做同样的事情:它们将值发送回调用它的代码。
EXERCISE 11.1Create a subroutine that returns the least common multiple of two integers. Use that in a program that takes two integers from the command line. The particulars of this exercise are very simple but it’s the structure of the subroutine definitions that matter.
练习11.1创建一个返回两个整数的最小公倍数的子程序。在从命令行获取两个整数的程序中使用它。这个练习的细节非常简单,但重要的是子程序定义的结构。
4.25. 递归
Subroutines can call themselves; this is called recursion. The classic example is Fibonacci numbers, where the next number in the series is the sum of the preceding two given that the first two numbers are 0 and 1:
子程序可以调用自己;这称为递归。典型的例子是 Fibonacci 数,其中系列中的下一个数字是前两个的总和,前两个数字是 0 和 1:
sub fibonacci ( $n ) {
return 0 if $n == 0; # special case of n = 0
return 1 if $n == 1;
return fibonacci( $n - 1 ) + fibonacci( $n - 2 );
}
say fibonacci( 10 ); # 55
When you call this subroutine with the value of 10
it calls itself twice to get the values for 9
and 8
. When it calls itself for 9
, it creates two more calls for 8
and 7
. It keeps creating more and more calls to itself until the arguments are 0
or 1
. It can then return a value one level up, working its way back to where it started.
当你使用值 10
调用此子例程时,它会调用自身两次以获取 9
和 8
的值。当它自己调用 9
时,它会为 8
和 7
创建两个以上的调用。它会不断创建对自身的调用,直到参数为 0
或 1
.然后它可以返回一个级别的值,然后返回到它开始的位置。
A Raku subroutine knows what it is inside its own [Block
](https://docs.raku.org/type/Block.html). The variable &?ROUTINE
is the same subroutine object. You don’t have to know the current subroutine’s name. This is the same thing:
Raku 子程序知道它自己的 Block 内部是什么。变量`&?ROUTINE`是相同的子程序对象。你不必知道当前子例程的名称。这是同样的东西:
sub fibonacci ( $n ) {
return 0 if $n == 0;
return 1 if $n == 1;
return &?ROUTINE( $n - 1 ) + &?ROUTINE( $n - 2 );
}
This is only slightly better. You’ll read more about this later when you encounter multi
subroutines.
这稍微好一点。当你遇到 multi
子程序时,你将在以后阅读更多相关信息。
EXERCISE 11.2Another favorite example of recursion is the factorial function. Start with a positive whole number and multiply it by all the strictly positive numbers that come before it. The factorial of 6 would be 6*5*4*3*2*1. Implement this as a recursive function. Once you’ve done that, implement it in the amazingly simple Raku fashion. How big a number can you get your program to produce?
练习11.2递归的另一个例子是阶乘函数。从正整数开始,然后乘以前面的所有严格正数。阶乘为6将为65432 * 1。将其实现为递归函数。完成后,以非常简单的Raku方式实现它。你的程序可以产生多大的数字?
4.25.1. 迭代而不是递归
You can turn many recursive solutions into iterative ones. Instead of repeatedly calling subroutines with all the overhead they entail (each call sets up a new scope, defines new variables, and so on), rearrange things so you don’t need a subroutine.
你可以将许多递归解决方案转换为迭代解决方案。而不是重复调用子程序,它们需要所有开销(每个调用设置一个新的作用域,定义新的变量,等等),重新排列事物,这样你就不需要子程序了。
The factorial case is easy. The reduction operator does that for you:
阶乘这个情况很容易。规约运算符为你执行此操作:
my $factorial = [*] 1 .. $n;
The operators are actually methods, so you don’t actually avoid calling something. |
运算符实际上是方法,所以你实际上避免不了调用某些东西。
The Fibonacci case is easy too when you use a [Seq
](https://docs.raku.org/type/Seq.html):
使用 Seq
时,Fibonacci 案例也很简单:
my $fibonacci := 1, 1, * + * ... *;
put "Fib(5) is ", $fibonacci[5];
You can make a queue of things to work on. With a queue you can add items anywhere you like. Instead of processing the next thingy immediately you can put it at the end of the queue. When it’s time to process the next thingy you can take it from the beginning, end, or middle. You can add as many elements as you want:
你可以创建一个可以处理的事物队列。使用队列,你可以在任何地方添加项目。不要立即处理下一个东西,而是将它放在队列的末尾。当处理下一件事时,你可以从开始,结束或中间开始。你可以根据需要添加任意数量的元素:
my @queue = ( ... );
while @queue {
my $thingy = @queue.shift; # or .pop
... # generate more items to process
@queue.append: @additional-items; # or .push or .unshift
}
4.26. 在库中存储子程序
Start with a simple subroutine to choose a random integer between two integers (including the endpoints). Use .rand
and coerce the result with .Int
, then shift the result into the right range:
从一个简单的子程序开始,选择两个整数(包括端点)之间的随机整数。使用 .rand
并使用 .Int
强制结果,然后将结果移动到正确的范围:
sub random-between ( $i, $j ) {
( $j - $i ).rand.Int + $i;
}
say random-between( -10, -3 );
You might need to convince yourself that works. Your program gets its job done, so you don’t think about it again. Then you write a different program doing something similar and you want to use that subroutine again. You do what many people don’t want to admit to: you cut and paste the subroutine into a different program. Again, it works. Or does it?
你可能需要说服自己有所作为。你的程序完成了它的工作,所以你不要再考虑它了。然后你编写一个不同的程序做类似的事情,你想再次使用该子程序。你做了许多人不愿意承认的事情:你将子程序剪切并粘贴到另一个程序中。再次,它的工作原理。或者是吗?
Did you really get a number between $i
and $j
inclusively?
你真的得到了$ i和$ j之间的数字吗?
EXERCISE 11.3What’s the maximum number that random-between
produces for any $i
and $j
? Write a program that figures it out by running random-between
repeatedly to see the actual range of results.
练习11.3任意$ i和$ j之间随机产生的最大数量是多少?编写一个程序,通过反复运行随机查看结果的实际范围来计算出来。
Once you’ve done that exercise you know that random-between
didn’t ever select the second endpoint as one of the random values. If you had copied it into other programs it would have been wrong in several places. There’s a way to fix that.
一旦你完成了这个练习,你就知道随机中断之间并没有选择第二个端点作为随机值之一。如果你把它复制到其他程序中,它会在几个地方出错。有办法解决这个问题。
To use the same subroutine in several programs you can define it once in a library. That’s a separate file that you can import into your program.
要在多个程序中使用相同的子程序,可以在库中定义一次。这是一个单独的文件,你可以导入到你的程序中。
Move random-between
to a new file that has the .pm or .pm6 extension:
随机移动到具有.pm或.pm6扩展名的新文件:
# MyRandLibrary.pm6
sub random-between ( $i, $j ) {
( $j - $i ).rand.Int + $i;
}
In your original program import your library with use
. Set lib
as you saw in [Chapter 10](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch10.html#camelia-using_modules):
在你的原始程序中导入你的库使用。按照第10章中的说明设置lib:
# random-between.p6
use lib (1)
use MyRandLibrary;
say random-between( -10, -3 );
Your program finds your library but now you get a different error:
你的程序找到了你的库,但现在你收到了另一个错误:
% raku random-between.p6
===SORRY!=== Error while compiling ...
Undeclared routine:
random-between used at line ...
4.26.1. 导出子程序
Subroutines are lexically scoped by default, so they can’t be seen outside their files. If you want another file to use them you need to export those subroutines. The is export
trait does that and comes right after the signature:
默认情况下,子例程在词法上是作用域的,因此不能在文件外看到它们。如果你想要使用其他文件,则需要导出这些子例程。出口特征是这样做的,并在签名后立即出现:
# MyRandLibrary.pm6
sub random-between ( $i, $j ) is export {
( $j - $i ).rand.Int + $i;
}
Your program now finds the library, imports the subroutine, and produces a number between your endpoints:
你的程序现在找到库,导入子例程,并在端点之间生成一个数字:
% raku random-between.p6
11
EXERCISE 11.4Create the library that exports the random-between
subroutine and use it in a program to get a random number between the two command-line arguments. What happens when the first argument is greater than the second? What happens if one of the arguments is not a number?
练习11.4创建导出random-between子例程的库,并在程序中使用它来获取两个命令行参数之间的随机数。当第一个参数大于第二个参数时会发生什么?如果其中一个参数不是数字,会发生什么?
4.27. 位置参数
There are two types of parameters. The first are the positional parameters that you’ve seen already in [Chapter 5](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch05.html#camelia-blocks). These parameters handle the arguments by their order in the argument list. We’ll look at them in a bit more detail here. You’ll see the other sort, named parameters, later in this chapter.
有两种类型的参数。第一个是你在第5章中已经看到的位置参数。这些参数按参数列表中的顺序处理参数。我们将在这里详细介绍它们。你将在本章后面看到另一种命名参数。
With no explicit signature the arguments show up in the array @
. Each subroutine gets its own @
so it doesn’t conflict with that for any other subroutine. So, if you write this:
没有明确的签名,参数显示在数组@中。每个子程序都有自己的@,因此它不会与任何其他子程序冲突。所以,如果你这样写:
sub show-the-arguments {
put "The arguments are: ", @_.gist;
}
show-the-arguments( 1, 3, 7 );
You get:
你得到:
The arguments are: [1 3 7]
Using @
inside the subroutine automatically adds the implicit signature. But it’s not as simple as an explicit @
`parameter by itself. This signature expects a single [`Positional
](https://docs.raku.org/type/Positional.html) argument:
在子例程中使用@_会自动添加隐式签名。但它本身并不像显式的@_parameter那么简单。此签名需要一个Positional参数:
sub show-the-arguments ( @_ ) { # Single Positional argument
put "The arguments are: ", @_.gist;
}
Calling it with multiple arguments is a compile-time error:
使用多个参数调用它是编译时错误:
show-the-arguments( 1, 3, 7 ); # Won't compile
The ( @_ )
signature wants a single argument that’s some sort of [Positional
](https://docs.raku.org/type/Positional.html) (not necessarily an [Array
](https://docs.raku.org/type/Array.html)):
(@_)签名需要一个参数,它是某种Positional(不一定是数组):
show-the-arguments( [ 1, 3, 7 ] ); # Single argument
4.27.1. Slurpy Parameters 吞噬参数
A slurpy parameter gets all of the remaining arguments into a single [Array
](https://docs.raku.org/type/Array.html). Prefix the array parameter with a *
. This is the same as the version with no explicit signature:
一个slurpy参数将所有剩余的参数都放入一个Array中。使用*前缀数组参数。这与没有显式签名的版本相同:
sub show-the-arguments ( *@_ ) { # slurpy
put "The arguments are: ", @_.gist;
}
show-the-arguments( 1, 3, 7 );
The output shows the three numbers:
输出显示三个数字:
The arguments are: [1 3 7]
There’s not much special about @_
. You can use your own variable name instead:
@_没什么特别之处。你可以使用自己的变量名称:
sub show-the-arguments ( *@args ) { # slurpy
put "The arguments are: ", @args.gist;
}
Try it with something slightly different now. Include a [List
](https://docs.raku.org/type/List.html) as one of the arguments:
现在尝试使用略有不同的东西。包含List作为参数之一:
sub show-the-arguments ( *@args ) { # slurpy
put "The arguments are: ", @args.gist;
}
show-the-arguments( 1, 3, ( 7, 6, 5 ) );
Did you expect this output? It’s a flat [List
](https://docs.raku.org/type/List.html) with no structure:
你有没有期待这个输出?这是一个没有结构的平面列表:
The arguments are: [1 3 7 6 5]
This isn’t a problem with formatting the data; the slurpy parameter flattens the data. Try it again with another level:
这不是格式化数据的问题; slurpy参数使数据变平。再试一次另一个级别:
show-the-arguments( 1, 3, ( 7, (6, 5) ) );
You get the same output with no structure:
你得到相同的输出,没有结构:
The arguments are: [1 3 7 6 5]
The slurpy parameter only flattens objects that you can iterate. If you itemize one of the [List
](https://docs.raku.org/type/List.html)s that item is no longer iterable. Items resist flattening:
slurpy参数仅展平你可以迭代的对象。如果你列出其中一个列表,则该项目不再可迭代。物品抗压扁:
show-the-arguments( 1, 3, ( 7, $(6, 5) ) );
The output is a bit different:
输出有点不同:
The arguments are: [1, 3, 7, (6, 5)]
How about this one?
这个怎么样?
show-the-arguments( [ 1, 3, ( 7, $(6, 5) ) ] );
Instead of a [List
](https://docs.raku.org/type/List.html) you have an [Array
](https://docs.raku.org/type/Array.html). Remember that an [Array
](https://docs.raku.org/type/Array.html) already itemizes each of its elements. The ( 7, $(6, 5) )
is itemized because it’s an element of an [Array
](https://docs.raku.org/type/Array.html):
而不是List你有一个数组。请记住,数组已经逐项列出了每个元素。 (7,$(6,5))是逐项列出的,因为它是数组的一个元素:
The arguments are: [1, 3, (7, $(6, 5))]
Use a **
in front of the parameter if you don’t want this automatic flattening:
如果你不希望这种自动展平,请在参数前面使用 **
:
sub show-nonflat-arguments ( **@args ) { # nonflattening slurpy
put "The nonflat arguments are: ", @args.gist;
}
show-nonflat-arguments( [ 1, 3, ( 7, $(6, 5) ) ] );
This output has a double set of square brackets around the data. The single argument is the inner [Array
](https://docs.raku.org/type/Array.html) and the entire argument list is the outer one:
此输出在数据周围有一组双方括号。单个参数是内部数组,整个参数列表是外部数据:
The nonflat arguments are: [[1 3 (7 (6 5))]]
EXERCISE 11.5Create a subroutine that outputs its argument count and shows each argument on a separate line. Try it with these argument lists:`1, 3, 7 1, 3, ( 7, 6, 5 ) 1, 3, ( 7, $(6, 5) ) [ 1, 3, ( 7, $(6, 5) ) ]`
练习11.5创建一个子程序,输出其参数计数并在一个单独的行上显示每个参数。尝试使用这些参数列表:1,3,7 1,3,3(7,6,5)1,3,(7,$(6,5))[1,3,(7,$(6, 5))]
4.27.2. Have It Both Ways
What if you want both flattening and nonflattening at the same time? If there’s one argument, you want to flatten that. If there’s more than one argument you want to leave that [List
](https://docs.raku.org/type/List.html) alone. Use a +
in front of a parameter to use the single argument rule:
如果你想要同时展平和不展平怎么办?如果有一个论点,你想要弄平。如果有多个参数,你希望单独保留该列表。在参数前面使用+来使用单个参数规则:
sub show-plus-arguments ( +@args ) { # single argument rule
put "There are {@args.elems} arguments";
put "The nonflat arguments are: ", @args.gist;
}
If you pass one argument that argument is flattened into @args
. With more than one argument you don’t get flattening:
如果你传递一个参数,将参数展平为@args。如果有多个参数,你就不会变平:
my @a = (1,3,7);
show-plus-arguments( @a ); # flattened
show-plus-arguments( @a, 5 ); # not flattened
The output shows the difference. In your first call to show-plus-arguments
it looks like you have single [Array
](https://docs.raku.org/type/Array.html) argument. By the time it gets inside the subroutine that [Array
](https://docs.raku.org/type/Array.html) has been flattened into three `Int`arguments:
输出显示差异。在第一次调用show-plus-arguments时,看起来你有单个Array参数。当它进入子程序时,Array已被展平为三个Intarguments:
There are 3 arguments
The nonflat arguments are: [1 3 7]
There are 2 arguments
The nonflat arguments are: [[1 3 7] 5]
Your second call has the [Array
](https://docs.raku.org/type/Array.html) along with 5
. With more than one argument you don’t get flattening and the argument list has an [Array
](https://docs.raku.org/type/Array.html) argument and an [Int
](https://docs.raku.org/type/Int.html) argument.
你的第二个调用有数组和5.有多个参数你没有变平,参数列表有一个Array参数和一个Int参数。
4.27.3. Combining Slurpies
You can have only one slurpy [Array
](https://docs.raku.org/type/Array.html) parameter, since it will take up the rest of the positional arguments. However, you can have positional parameters before a slurpy parameter:
你可以只有一个slurpy Array参数,因为它将占用其余的位置参数。但是,你可以在slurpy参数之前获得位置参数:
sub show-the-arguments ( $i, $j, *@args ) { # slurpy
put "The arguments are i: $i j: $j and @args[]";
}
show-the-arguments( 1, 3, 7, 5 );
The first two arguments fill in $i
and $j
and anything left over goes into @args
:
前两个参数填写$ i和$ j,剩下的任何内容都会输入@args:
The arguments are i: 1 j: 3 and 7 5
What if you put all but one of the arguments into an [Array
](https://docs.raku.org/type/Array.html)?
如果将除一个参数之外的所有参数放入数组中该怎么办?
my @a = ( 3, 7, 5 );
show-the-arguments( 1, @a );
Now the output shows that $j
has an [Array
](https://docs.raku.org/type/Array.html) value and @args
has nothing:
现在输出显示$ j有一个Array值而@args什么都没有:
The arguments are i: 1 j: 3 7 5 and
EXERCISE 11.6Create a library that provides a head
and a tail
function that each take a [List
](https://docs.raku.org/type/List.html) parameter. Make your head`function return the first item in the [`List
](https://docs.raku.org/type/List.html) and your tail
function return everything else. Do not change the original [List
](https://docs.raku.org/type/List.html). If you’re used to Lisp you might call these car
and cdr
:`use lib <.>; use HeadsTails; my @a = <1 3 5 7 11 13>; say head( @a ); # 1 say tail( @a ); # [ 3 5 7 11 13 ]`
练习11.6创建一个提供head和tail函数的库,每个函数都有一个List参数。让你的headfunction返回List中的第一项,你的tail函数返回其他所有内容。请勿更改原始列表。如果你习惯使用Lisp,可以将这些汽车和cdr称为:使用lib <。>;使用HeadsTails;我的@a = <1 3 5 7 11 13>;说头(@a); #1说尾巴(@a); #[3 5 7 11 13]
4.27.4. Optional and Default Arguments
By default all positional parameters require arguments. A question mark, ?
, after a parameter marks it as optional so that you don’t need to supply an argument. This subroutine takes one or two arguments:
默认情况下,所有位置参数都需要参问号?后面的参数将其标记为可选,这样你就不需要提供参数。该子例程需要一个或两个参数:
sub one-or-two ( $a, $b? ) {
put $b.defined ?? "Got $a and $b" !! "Got $a";
}
one-or-two( 'Hamadryas' );
one-or-two( 'Hamadryas', 'perlicus' );
If you have an optional argument you probably want a default value. Assign to a parameter to give it a default value. That assignment occurs only when you don’t supply an argument:
如果你有可选参数,则可能需要默认值。分配给参数以为其提供默认值。只有在你不提供参数时才会发生该分配:
sub one-or-two ( $a, $b = 137 ) {
put $b.defined ?? "Got $a and $b" !! "Got $a";
}
one-or-two( 19 ); # one number
one-or-two( 'Hamadryas', 'perlicus' ); # two strings
one-or-two( <Hamadryas perlicus> ); # one array
one-or-two( |<Hamadryas perlicus> ); # flattened array
The output shows that the arguments fill in the parameters differently each time:
输出显示参数每次填充参数的方式不同:
Got 19 and 137
Got Hamadryas and perlicus
Got Hamadryas perlicus and 137
Got Hamadryas and perlicus
You can’t have required positional parameters after an optional one:
在可选项之后,你不能拥有所需的位置参数:
sub one-or-two ( $a?, $b ) {
put $b.defined ?? "Got $a and $b" !! "Got $a";
}
That’s a compile-time error:
这是一个编译时错误:
Error while compiling
Cannot put required parameter $b after optional parameters
4.27.5. Parameter Traits
The parameter variables are filled in with read-only aliases to the original data. You see the same values but you can’t change them. This subroutine tries to add one to its value:
参数变量用原始数据的只读别名填充。你看到相同的值但无法更改它们。此子例程尝试在其值中添加一个:
sub increment ( $a ) { $a++ }
my $a = 137;
put increment( $a );
This doesn’t work because you can’t change the parameter variable:
这不起作用,因为你无法更改参数变量:
Cannot resolve caller postfix:<++>(Int); the following candidates
match the type but require mutable arguments:
The read-only alias is the default. You can change that by applying traits to the parameters. Apply the `is copy`trait to get a mutable value that’s separate from the original argument. You can change it without changing the original value:
只读别名是默认值。你可以通过将特征应用于参数来更改它。应用is copytrait以获取与原始参数分开的可变值。你可以在不更改原始值的情况下进行更改:
sub mutable-copy ( $a is copy ) { $a++; put "Inside: $a" }
my $a = 137;
put "Before: $a";
mutable-copy( $a );
put "After: $a";
The output shows that the original variable’s value did not change:
输出显示原始变量的值未更改:
Before: 137
Inside: 138
After: 137
Use the is rw
trait to change the original value. If the argument is a writable container you can change the value. If the value is not some sort of container you’ll get an error:
使用is rw trait更改原始值。如果参数是可写容器,则可以更改该值。如果该值不是某种容器,则会出现错误:
sub read-write ( $a is rw ) { $a++ }
my $a = 137;
my $b := 37;
my \c = 7;
read-write( $a ); # writable so okay
read-write( $b ); # literal, not mutable - ERROR!
read-write( c ); # constant, not mutable - ERROR!
read-write( 5 ); # literal, not mutable - ERROR!
4.27.6. 参数约束
You can constrain a parameter to a particular type. You already saw some of this in [Chapter 5](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch05.html#camelia-blocks):
你可以将参数约束为特定类型。你已经在第5章中看到了一些内容:
sub do-something ( Int:D $n ) { ... }
The sigils impose their own constraints. An @
accepts something that is a [Positional
](https://docs.raku.org/type/Positional.html), the %
accepts something that does [Associative
](https://docs.raku.org/type/Associative.html), and the &
accepts something that does [Callable
](https://docs.raku.org/type/Callable.html):
这些印记强加了自己的约束。 @接受一个定位的东西,%接受一个做关联的东西,而接受一些做Callable的东西:
sub wants-pos ( @array ) { put "Got a positional: @array[]" }
sub wants-assoc ( %hash ) { put "Got an associative: {%hash.gist}" }
sub wants-code ( &code ) { put "Got code" }
wants-pos( <a b c> );
wants-assoc( Map.new: 'a' => 1 );
wants-code( { put "Mini code" } );
These won’t work because they don’t supply the right types of arguments:
这些不起作用,因为它们不提供正确类型的参数:
wants-pos( %hash );
wants-assoc( <x y z> );
wants-code( 1 );
Additionally, something that accepts a code block can specify its own signature that must match the argument’s signature. Put the desired signature after the parameter variable:
此外,接受代码块的东西可以指定自己的签名,该签名必须与参数的签名匹配。在参数变量后面放置所需的签名:
sub one-arg ( &code:( $a ), $A ) { &code.($A) }
sub two-args ( &code:( $a, $b ), $A, $B ) { &code.($A, $B) }
one-arg( { put "Got $^a" }, 'Hamadryas' );
two-args( { put "Got $^a and $^b" }, 'Hamadryas', 'perlicus' );
4.28. Same Name, Different Signature
You can define the same subroutine name twice by giving it different signatures. Each of these is a candidate. A dispatcher decides which candidate to call based on your arguments. There are several things the dispatcher considers, in this order:
你可以通过为其指定不同的签名来定义相同的子例程名称两次。这些都是候选人。调度员根据你的参数决定调用哪个候选者。调度员按以下顺序考虑以下几点:
-
Literal value
-
Number of arguments (arity)
-
Types of arguments
-
Other constraints
To define candidates, declare the subroutine with multi
. And since multi
works on a subroutine by default (you’ll see methods in [Chapter 12](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch12.html#camelia-classes)), you can leave off the sub
:
要定义候选项,请使用multi声明子例程。并且由于默认情况下多个工作在子程序上(你将在第12章中看到方法),你可以不使用子工具:
multi sub some-subroutine { ... }
multi some-subroutine { ... }
4.28.1. Literal Value Parameters
You can also make a signature that has a literal value. These `multi`s are selected when the argument value is the same as the literal parameter:
你还可以创建具有文字值的签名。当参数值与文字参数相同时,选择这些multis:
multi something ( 1 ) { put "Got a one" }
multi something ( 0 ) { put "Got a zero" }
multi something ( $a ) { put "Got something else" }
something( 1 );
something( 0 );
something( 137 );
The literal value parameters decide the appropriate subroutine for the first two cases:
文字值参数决定前两种情况的相应子例程:
Got a one
Got a zero
Got something else
What if you wanted a [Rat
](https://docs.raku.org/type/Rat.html) as one of the literal values? Put the value inside <>
so the compiler doesn’t think the /
is the start of a regex ([Chapter 15](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch15.html#camelia-regex1)):
如果你想将鼠作为字面值之一,该怎么办?将值放在<>中,这样编译器就不会认为/是正则表达式的开头(第15章):
multi something ( 1 ) { put "Got a one" }
multi something ( 0 ) { put "Got a zero" }
multi something ( <1/137> ) { put "Got something fine" }
multi something ( $b ) { put "Got something else" }
something( 1 );
something( 0 );
something( 1/137 );
something( 'Hello' );
Think about the previous Fibonacci example:
想想之前的Fibonacci示例:
sub fibonacci ( $n ) {
return 0 if $n == 0;
return 1 if $n == 1;
return &?ROUTINE( $n - 1 ) + &?ROUTINE( $n - 2 );
}
That implementation has two special cases for 0
and 1
. You have to provide special code to handle those. You can move those special cases away from the main idea by giving each case its own multi
:
该实现有两个特殊情况0和1.你必须提供特殊代码来处理这些。你可以通过为每个案例提供自己的多个来移动这些特殊情况远离主要想法:
multi fibonacci ( 0 ) { 0 }
multi fibonacci ( 1 ) { 1 }
multi fibonacci ( $n ) {
return fibonacci( $n - 1 ) + fibonacci( $n - 2 );
}
put fibonacci(0);
put fibonacci(1);
put fibonacci(5);
Notice that you can’t use &?ROUTINE
because $n-1
might not be handled by the same subroutine.
请注意,你不能使用&?ROUTINE,因为$ n-1可能无法由同一子例程处理。
4.28.2. Number of Arguments
Declare the sub
with multi
. One candidate takes a single positional argument and the other candidate takes two positional arguments:
用multi声明sub。一个候选者采用单个位置参数,另一个候选者采用两个位置参数:
multi subsomething ( $a ) { put "One argument"; }
multi subsomething ( $a, $b ) { put "Two arguments"; }
something( 1 );
something( 1, 3 );
# something();
The output shows that you called two different subroutines:
输出显示你调用了两个不同的子例程:
One argument
Two arguments
Uncomment the call with no arguments, and you’ll get a compile-time error. The compiler knows no signatures can match:
取消注释没有参数的调用,你将得到一个编译时错误。编译器知道没有签名可以匹配:
Calling something() will never work with any of these multi signatures:
($a)
($a, $b)
You can shorten the multi sub
to simply multi
since that implies sub
:
你可以将multi sub简化为multi,因为这意味着sub:
multi something ( $a ) { put "One argument"; }
multi something ( $a, $b ) { put "Two arguments"; }
This sort of dispatch depends on arity—the number of arguments that you supply. This means that the compiler also knows when you try to define subroutines with the same arity, like this:
这种调度取决于arity - 你提供的参数数量。这意味着编译器也知道你何时尝试使用相同的arity定义子例程,如下所示:
multi something ( $a ) { put "One argument"; }
multi something ( $b ) { put "Also one arguments"; } # Error
This is also a runtime error because the dispatcher can’t choose one candidate over the other (and it won’t run all of them):
这也是一个运行时错误,因为调度程序不能选择一个候选项而不是另一个候选项(它不会运行所有这些候选项):
Ambiguous call to 'something'; these signatures all match:
:($a)
:($b)
4.28.3. Parameter Types
You can also choose amongst `multi`s by parameter type. These each take the same number of arguments but distinguish them by type:
你还可以通过参数类型在multis中进行选择。这些参数都采用相同数量的参数,但按类型区分:
multi something ( Int:D $a ) { put "Int argument"; }
multi something ( Str:D $a ) { put "Str arguments"; }
something( 137 );
something( 'Hamadryas' );
These call different subroutines because the argument types are different:
这些调用不同的子例程,因为参数类型不同:
Int argument
Str arguments
You might have the different subroutines take the same type. In those cases you can select the right one by a custom constraint. The dispatcher chooses the most specific one:
你可能有不同的子例程采用相同的类型。在这些情况下,你可以通过自定义约束选择正确的约束。调度员选择最具体的一个:
multi something ( Int:D $a ) { put "Odd arguments"; }
multi something ( Int:D $a where * %% 2 ) { put "Even argument" }
something( 137 );
something( 538 );
Notice that this works regardless of the order in which you define the subroutines:
请注意,无论你定义子例程的顺序如何,这都有效:
Odd arguments
Even arguments
In the next example the first subroutine constrains its parameter to numbers that are odd. The second subroutine constrains its parameter to numbers greater than 5. These both have one parameter and they both have a `where`clause, so the dispatcher chooses the first one it encounters:
在下一个示例中,第一个子例程将其参数约束为奇数。第二个子例程将其参数约束为大于5的数字。这两个参数都有一个参数,它们都有一个whereclause,所以调度程序选择它遇到的第一个参数:
multi sub something ( Int:D $a where * % 2 ) { put "Odd number" }
multi sub something ( Int:D $a where * > 5 ) { put "Greater than 5" }
something( 137 );
The argument satisfies either signature. The output shows that the first subroutine ran:
该论点满足任一签名。输出显示第一个子例程运行:
Odd number
Reverse the order of definition:
颠倒定义的顺序:
multi sub something ( Int:D $a where * > 5 ) { put "Greater than 5" }
multi sub something ( Int:D $a where * % 2 ) { put "Odd number" }
something( 137 );
The first defined subroutine still runs even though it’s a different definition:
第一个定义的子例程仍然运行,即使它是一个不同的定义:
Greater than 5
What if you do not want multiple definitions with the same name? Declare one of the subroutines without`multi`:
如果你不希望使用相同名称的多个定义,该怎么办?声明一个没有多个子程序的子程序:
sub something ( Int $a ) { put "Odd arguments" }
multi something ( Int $a where * %% 2 ) { # redefinition!
put "Even argument";
}
You get a compile-time error asking if you meant that to be a multi sub
:
你得到一个编译时错误,询问你是否认为这是一个多子:
===SORRY!=== Error while compiling
Redeclaration of routine 'something' (did you mean to declare a multi-sub?)
4.29. Named Parameters
Named parameters do not depend on their position in the parameter or argument lists. By default they are optional. You can specify them anywhere in the arguments and in any order. These are often used to set options for a routine or method.
命名参数不依赖于它们在参数或参数列表中的位置。默认情况下,它们是可选的你可以在参数中的任何位置以任何顺序指定它们。这些通常用于设置例程或方法的选项。
Specify named parameters with a colon before the parameter variable. In the signature, use the unquoted parameter variable name, the fat arrow, and the value that you want to supply. The order of the names or values does not matter:
在参数变量之前使用冒号指定命名参数。在签名中,使用不带引号的参数变量名称,胖箭头和要提供的值。名称或值的顺序无关紧要:
sub add ( Int:D :$a, Int:D :$b ) {
$a + $b;
}
put add( a => 1, b => 36 ); # 37
put add( b => 36, a => 1 ); # Same thing
For this to work you cannot quote the keys or use variables as the keys. This call is actually two [Pair
](https://docs.raku.org/type/Pair.html) objects treated as positional parameters:
为此,你无法引用键或使用变量作为键。这个调用实际上是两个被视为位置参数的Pair对象:
put add( 'a' => 1, 'b' => 36 ); # Will not work!
put add( $keya => 1, $keyb => 36 ); # Will not work!
More often you’ll use the adverb syntax. With values that are positive integers you can specify the value first and the name after it:
更常见的是,你将使用副词语法。对于正整数值,你可以先指定值,然后指定其后的名称:
put add( :a(1), :b(36) ); # 37
put add( :36b, :1a ); # 37
Default values and other constraints work the same as they do with positional parameters:
默认值和其他约束与位置参数的作用相同:
sub add ( Int:D :$a = 0, Int:D :$b = 0 ) {
$a + $b;
}
put add(); # 0
put add( :36b ); # 36
You don’t have to use the same names for the arguments and the parameter variables. In complicated code in power-of
you might not want to retype $base
or $power
every time. The subroutine still uses the long names for the interface but the implementation can use the short names:
你不必对参数和参数变量使用相同的名称。在功能复杂的代码中,你可能不希望每次都重新键入$ base或$ power。子例程仍然使用接口的长名称,但实现可以使用短名称:
sub power-of ( Int:D :power($n) = 0, Int:D :base($a) ) {
$a ** $n
}
put power-of( base => 2, power => 5 ); # 32
So far these named parameters have all taken values. Without any other constraints and no argument value, a named parameter is a Boolean. The adverb form with no value (and no constraint) gets True
(because that’s what [Pair
](https://docs.raku.org/type/Pair.html)s do):
到目前为止,这些命名参数都采用了值。没有任何其他约束和参数值,命名参数是布尔值。没有值(并且没有约束)的副词形式为True(因为这就是Pairs所做的):
sub any-args ( :$state ) { say $state }
any-args( :state ); # True
A !
in front of the adverb name makes it a False
value:
一个 !在副词名称前面使其成为一个假值:
any-args( :!state ); # False
4.29.1. Required Named Parameters
A positional parameter is required simply because it exists, and you have to mark it as optional to make it such. That’s reversed with named parameters, which are optional unless you say otherwise. The parameters get their default values if you don’t specify them:
仅需要位置参数因为它存在,并且你必须将其标记为可选,以使其成为可能。这与命名参数相反,除非你另有说明,否则这些参数是可选的。如果你不指定参数,参数将获得其默认值:
sub not-required ( :$option ) { say $option; }
not-required(); # (Any)
not-required( :option ); # True
not-required( :!option ); # False
not-required( :5option ); # 5
To make option
mandatory put a !
after it in the signature (this is not the same as !
before an argument):
要强制选择放一个!在签名之后(这与参数之前的!不一样):
sub not-required ( :$option! ) { say $option; }
not-required(); # Error!
The error tells you that you forgot an argument:
该错误告诉你忘记了一个参数:
Required named parameter 'option' not passed
4.29.2. Named Parameters for Free
Rather than define every named parameter, you can accept all of them. Don’t specify any in your parameters and they all show up in %
. This is the equivalent of @
but for named parameters. Each routine gets its own version of this variable:
你可以接受所有参数,而不是定义每个命名参数。不要在参数中指定任何参数,它们都显示在%_中。这相当于@_但是对于命名参数。每个例程都有自己的变量版本:
sub any-args { say %_ }
any-args( genus => 'Hamadryas' );
any-args( genus => 'Hamadryas', species => 'perlicus' );
You didn’t define either :genus
or :species
but they show up in %_
:
你没有定义:genus或:species但是它们出现在%_中:
{genus => Hamadryas}
{genus => Hamadryas, species => perlicus}
A slurpy [Hash
](https://docs.raku.org/type/Hash.html) does the same thing:
一个邋H的哈希做同样的事情:
sub any-args ( *%args ) { say %args }
any-args( genus => 'Hamadryas' );
any-args( genus => 'Hamadryas', species => 'perlicus' );
That’s how that implicit %_
worked. When you use it in a subroutine you automatically get a slurpy for it in the signature:
这就是隐含的%_的工作方式。当你在子例程中使用它时,你会在签名中自动获取它:
sub any-args { say %_ }
sub any-args ( *%_ ) { say %_ }
4.29.3. Mixed Parameters
You can mix positional and named parameters. If you use @
and %
in the code they are both in the implicit signature:
你可以混合位置和命名参数。如果在代码中使用@和%,则它们都在隐式签名中:
sub any-args {
put '@_ => ', @_.gist;
put '%_ => ', %_.gist;
}
any-args( 'Hamadryas', 137, :status, :color('Purple') );
@_ => [Hamadryas 137]
%_ => {color => Purple, status => True}
You can mix in the named parameters in any order that you like. The positional parameters have to be in the right order but named parameters can come between them:
你可以按你喜欢的任何顺序混合命名参数。位置参数必须是正确的顺序,但命名参数可以介于它们之间:
any-args( :color('Purple'), 'Hamadryas', :status, 137 );
It’s the same if you name the parameters yourself:
如果你自己命名参数,则相同:
sub any-args ( *@args, *%named ) {
put '@args => ', @args.gist;
put '%named => ', %named.gist;
}
any-args( :color('Purple'), 'Hamadryas', :status, 137 );
4.30. Return Types
You can constrain the return values of subroutines. If you try to return a value that doesn’t fit the restriction you get a runtime [Exception
](https://docs.raku.org/type/Exception.html). Specify the type after the signature with a -→
. You want this subroutine to return a defined [Int
](https://docs.raku.org/type/Int.html):
你可以约束子例程的返回值。如果你尝试返回不符合限制的值,则会获得运行时异常。使用 - >指定签名后的类型。你希望此子例程返回已定义的Int:
sub returns-an-int ( Int:D $a, Int:D $b --> Int:D ) { $a + $b }
put returns-an-int( 1, 3 );
That works:
这样可行:
4
But what if you make a mistake where you return a Str
?
但是,如果你在返回Str时犯了错误怎么办?
sub returns-an-int ( Int:D $a, Int:D $b --> Int:D ) { ($a + $b).Str }
put returns-an-int( 1, 3 );
At runtime you get an error because the types do not match:
在运行时,你会收到错误,因为类型不匹配:
Type check failed for return value; expected Int but got Str ("4")
An alternate way is to note it with returns
(with an s
at the end) outside of the signature’s parentheses:
另一种方法是在签名的括号外面用返回(在末尾有一个s)注意它:
sub returns-an-int ( Int $a, Int $b ) returns Int { $a + $b }
You might also see these forms that do the same thing:
你可能还会看到这些表单执行相同的操作:
sub returns-an-int ( Int $a, Int $b ) of Int { $a + $b }
my Int sub returns-an-int ( Int $a, Int $b ) { $a + $b }
No matter which way you define the return type you can always return either Nil
or a [Failure
](https://docs.raku.org/type/Failure.html) object (usually to signal that something went wrong). All of these calls “succeed” even though some of them don’t return a [Str
](https://docs.raku.org/type/Str.html):
无论你以何种方式定义返回类型,都可以始终返回Nil或Failure对象(通常表示出现问题)。所有这些调用都“成功”,即使其中一些不返回Str:
sub does-not-work ( Int:D $a --> Str ) {
return Nil if $a == 37;
fail 'Is not a fine number' unless $a == 137;
return 'Worked!'
}
put does-not-work( 37 ).^name; # Nil
put does-not-work( 137 ).^name; # Str
put does-not-work( 538 ).^name; # Failure
You can’t make complex checks in the constraint, but you can define a subset that does these. Here’s one that returns either a [Rat
](https://docs.raku.org/type/Rat.html) or, if you try to divide by zero, an Inf
:
你不能在约束中进行复杂检查,但可以定义执行这些检查的子集。这里有一个返回Rat或者,如果你试图除以零,一个Inf:
subset RatInf where Rat:D | Inf;
sub divide ( Int:D $a, Int:D $b --> RatInf ) {
return Inf if $b == 0;
$a / $b;
}
put divide( 1, 3 ); # <1/3>
put divide( 1, 0 ); # Inf
That Rat:D | Inf
is a [Junction
](https://docs.raku.org/type/Junction.html). You’ll see those in [Chapter 14](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch14.html#camelia-junctions).
4.31. Summary
Much of the work of ensuring your program does the right things can be done with the judicious use of constraints on the inputs and outputs of subroutines. With a little planning these features will catch the cases that you did not expect and that shouldn’t show up in your program. Once they’ve been found you can work your way through the code to find them even sooner—and the sooner you find them, the easier your debugging life should be.
确保你的程序做正确的事情的大部分工作可以通过明智地使用子程序的输入和输出的约束来完成。通过一些计划,这些功能将捕获你不期望的并且不应该出现在你的程序中的情况。一旦找到它们,你就可以通过代码更快地找到它们 - 并且越早找到它们,你的调试生活就越容易。 == 类
A class is the blueprint for an object and manages an object and its behavior. It declares attributes to define what an object will store and methods to define how an object can behave. Classes model the world in a way that makes it easier for your program to do its job.
类是对象的蓝图,用于管理对象及其行为。它声明属性以定义对象将存储的内容以及定义对象行为方式的方法。类以一种使程序更容易完成其工作的方式对世界建模。
I’m mostly going to ignore object-oriented analysis and design. This chapter is about the mechanism of classes and objects. The examples show you how things work and do not endorse a particular way. Use what works for your task and stop using that when it doesn’t.
我大多会忽略面向对象的分析和设计。本章是关于类和对象的机制。这些例子向你展示了如何运作并且不支持某种特定方式。使用适用于你的任务的内容,并在不执行任务时停止使用。
4.32. Your First Class
Declare a class by giving it a name and a [Block
](https://docs.raku.org/type/Block.html) of code:
通过给出一个名称和一个代码块来声明一个类:
class Butterfly {}
That’s it! It looks like this class is empty, but it’s not. You get much of its basic behavior for free even though you don’t see it explicitly. Try calling some methods on it. You can see that it derives from [Any
](https://docs.raku.org/type/Any.html) and [Mu
](https://docs.raku.org/type/Mu.html) and that you can create new objects:
而已!看起来这个类是空的,但事实并非如此。即使你没有明确地看到它,你也可以免费获得许多基本行为。尝试调用一些方法。你可以看到它派生自 [Any
](https://docs.raku.org/type/Any.html) 和 [Mu
](https://docs.raku.org/type/Mu.html),你可以创建新对象:
% raku
> class Butterfly {}
(Butterfly)
> Butterfly.^mro
((Butterfly) (Any) (Mu))
> my $object = Butterfly.new
Butterfly.new
> $object.^name
Butterfly
> $object.defined
True
You can have as many of these class declarations as you like in one file:
你可以在一个文件中包含任意数量的类声明:
class Butterfly {}
class Moth {}
class Lobster {}
These types are available to your program as soon as they are defined in the code, but not before. If you try to use one before you define it you get a compilation error:
只要在代码中定义了这些类型,就可以使用这些类型,但没定义之前不能使用。如果在定义类之前就尝试使用它,则会出现编译错误:
my $butterfly = Butterfly.new; # Too soon!
class Butterfly {}; # Error: Illegally post-declared type
Instead of defining all of your classes at the beginning of the file (and having to scroll past all of them to get to the good stuff), you’re more likely to want one class per file so you can easily find the class definition again. In that case you can use unit
to declare that the entire file is your class definition. You don’t use a [Block
](https://docs.raku.org/type/Block.html):
而不是在文件的开头定义所有类(并且必须滚动浏览所有类以获得好的东西),你更可能每个文件都需要一个类,这样你就可以轻松地再次找到类定义。在这个例子中,你可以使用 unit
声明整个文件是你的类定义。你没使用[Block
](https://docs.raku.org/type/Block.html):
unit class Butterfly;
Put your class in Butterfly.pm6 (or Butterfly.pm) and load it from your program:
将你的类放在 Butterfly.pm6(或 Butterfly.pm)中并从你的程序中加载它:
use Butterfly;
EXERCISE 12.1Create a single-file program that has the Butterfly
, Moth
, and Lobster
empty class definitions. Create a new object for each, even though the objects don’t do anything interesting yet.
练习12.1创建一个具有 Butterfly
,Moth
和 Lobster
的空类定义的单文件程序。为每个类创建一个新对象,即使对象没有做任何有趣的事情。
EXERCISE 12.2Define the Butterfly
, Moth
, and Lobster
classes in separate files named after the classes they contain. The class files should be in the same directory as the program that loads them. Load those files in your program and create new objects for each.
练习12.2将 Butterfly
,Moth
和 Lobster
类定义在以它们包含的类命名的单独文件中。类文件应与加载它们的程序位于同一目录中。在程序中加载这些文件并为每个文件创建新对象。
4.33. 定义方法
Methods are like subroutines but know who called them and can be inherited; instead of sub
you define these with method
. This example uses it to output the type name:
方法就像子程序,但知道是谁调用它们并且可以继承; 你用 method
而不是 sub
来定义方法。此示例使用方法来输出类型名称:
class Butterfly {
method who-am-i () { put "I am a " ~ self.^name }
}
Butterfly.who-am-i;
That self
term is the invocant of the method. That’s the object that called the method. It doesn’t need to be in the signature. It also doesn’t need to be in the method. Calling a method on $
does the same thing (you’ll see why later):
那个 self
是该方法的调用者。这是调用方法的对象。它不需要在签名中。它也不需要在方法中。在 $
上调用方法会做同样的事情(稍后你会明白为什么):
class Butterfly {
method who-am-i () { put "I am a " ~ $.^name }
}
Butterfly.who-am-i; # I am a Butterfly
Give the invocant a different name by putting it before a colon in the signature. C++ people might like $this
:
通过将调用者放在签名中的冒号前面,为调用者指定一个不同的名称。 C++ 人可能会喜欢 $this
:
method who-am-i ( $this : ) { put "I am a " ~ $this.^name }
A backslash makes the invocant name a term so you don’t need a sigil:
反斜杠使调用名称成为一个项,因此你不需要使用 sigil:
method who-am-i ( \this : ) { put "I am a " ~ this.^name; }
The default topic can be the invocant, which means that it’s implicit inside the [Block
](https://docs.raku.org/type/Block.html):
默认主题可以是调用者,这意味着它隐含在 [Block
](https://docs.raku.org/type/Block.html) 中:
method who-am-i ( $_ : ) { put "I am a " ~ .^name; }
If you want to change the invocant name, choose something that describes what it represents:
如果要更改调用者的名称,请选择描述其代表内容的东西:
method who-am-i ( $butterfly : ) { ... }
4.33.1. 私有方法
A private method is available only inside the class where it’s defined. You use these to compartmentalize code that you don’t want code outside the class to know about.
私有方法仅在定义它的类中可用。你可以使用它们来划分你不希望类外部代码知道的代码。
Previously who-am-i
directly called .^name
. That’s a very specific way to figure out the “type.” You might want to change that later or use other methods to figure it out, and other methods in your class may need the same thing. Hide it in a method, what’s-the-name
:
以前我是谁直接调用 .^name
。这是一种非常具体的方法来确定“类型”。你可能希望稍后更改它或使用其他方法来解决它,并且你的类中的其他方法可能需要相同的东西。将其隐藏在一个方法中,what’s-the-name
:
class Butterfly {
method who-am-i () { put "I am a " ~ self.what's-the-name }
method what's-the-name () { self.^name }
}
Butterfly.who-am-i; # I am a Butterfly
put Butterfly.what's-the-name; # Butterfly
That works, but it’s now available as a method that you didn’t intend anyone to use outside of the class. Prefix the method name with a !
to hide it from code outside the class. Replace the method call dot with a !
too:
这起作用了,但它现在可以作为一种方法,你不打算任何人在类外使用。在方法的名字上加上前缀 !
以从类外的代码中隐藏它。也用 !
替换方法调用点:
class Butterfly {
method who-am-i () { put "I am a " ~ self!what's-the-name }
method !what's-the-name () { self.^name }
}
Butterfly.who-am-i; # I am a Butterfly
put Butterfly.what's-the-name; # Butterfly
Now you get an error if you try to use it outside the class:
现在,如果你尝试在类外使用它,则会出现错误:
No such method 'what's-the-name' for invocant of type 'Butterfly'.
4.33.2. Defining Subroutines
A class can contain subroutines. Since subroutines are lexically scoped they are also invisible outside the class. A subroutine can do the same job as a private method. To make this work you need to pass the object as a subroutine argument:
类可以包含子例程。由于子程序是词法作用域的,因此它们在类外也是不可见的。子例程可以执行与私有方法相同的工作。要完成这项工作,你需要将该对象作为子例程参数传递:
class Butterfly {
method who-am-i () { put "I am a " ~ what's-the-name( self ) }
sub what's-the-name ($self) { $self.^name }
}
Butterfly.who-am-i; # I am a Butterfly
4.34. 对象
Objects are particular instances of a class; sometimes those terms are used interchangeably. Each object has its own variables and data, separate from all the others. Each object, however, still shares the behavior of the class.
对象是类的特定实例;有时这些术语可以互换使用。每个对象都有自己的变量和数据,与其他对象分开。但是,每个对象仍然共享该类的行为。
Start with the simplest class, as before. To create an object you need a constructor method. Any method that creates an object is a constructor. By default that is .new
:
像以前一样,从最简单的类开始。要创建对象,你需要一个构造函数方法。创建对象的任何方法都是构造函数。默认情况下是 .new
:
class Butterfly {}
my $butterfly = Butterfly.new;
The object is a defined instance of the class (the type object is the undefined one). The .DEFINITE
method tells you which one you have:
该对象是类的已定义实例(类型对象是未定义的对象)。 .DEFINITE
方法告诉你用的是哪一个:
put $butterfly.DEFINITE
?? 'I have an object' !! 'I have a type';
Every object also has a .defined method, but each class can change what that means. Any object of the [Failure ](https://docs.raku.org/type/Failure.html) class is undefined, so it’s always False as a conditional. Use .DEFINITE to avoid that gotcha.
|
每个对象也有一个 .defined
方法,但每个类都可以改变它的含义。 [Failure
](https://docs.raku.org/type/Failure.html) 类的任何对象都是未定义的,因此它作为条件总是 False
。使用 .DEFINITE
来避免这种问题。
4.34.1. 私有属性
Attributes are per-object data. You declare these with has
. The attribute variables use a twigil to denote their access. Before you see the easy way you should see the hard way so you appreciate it more. The $!
twigil defines a private attribute:
属性是每个对象都具有的数据。你用 has
声明属性。属性变量使用 twigil 来表示它们的访问权限。在你看到简单的方法之前,你应该看到困难的方式让你更加欣赏它。 $!
twigil 定义一个私有属性:
class Butterfly {
has $!common-name;
}
By itself this has
definition doesn’t effectively add anything to your class. Nothing can see the attribute, so you have no way to change its value.
这个 has
定义本身并没有有效地为你的类添加任何东西。什么都看不到属性,所以你无法改变它的值。
The special .BUILD
method is automatically called after .new
with the same arguments. You can define your own .BUILD
to bind or assign a value to your private attribute (or do any other work that you want):
使用相同的参数在 .new
之后自动调用特殊的 .BUILD
方法。你可以定义自己的 .BUILD
来绑定值到你的私有属性上或为你的私有属性赋值(或者执行你想要的任何其他工作):
class Butterfly {
has $!common-name;
method BUILD ( :$common-name ) {
$!common-name = $common-name;
}
}
my $butterfly = Butterfly.new: :common-name('Perly Cracker');
Be careful here. This .BUILD
accepts all named parameters without warning. It doesn’t know which ones you intend to use or what they mean to your class. It’s a default way that almost everything uses to set up objects—but if you misspell a name, you won’t get a warning:
这里要小心。此 .BUILD
接受所有命名参数而不发出警告。它不知道你打算使用哪些或它们对你的类意味着什么。这是几乎所有东西都用来设置对象的默认方式 - 但是如果你拼错了名字,你不会收到警告:
my $butterfly = Butterfly.new: :commen-name('Perly Cracker');
You also don’t get a warning for leaving something out. Maybe you don’t want to require every setting any time you build an object. You might not want this to fail:
你也不会因为遗漏某些属性而得到警告。也许你不希望每次构建对象时就设置好所有的东西。你可能不希望这失败:
my $butterfly = Butterfly.new;
But if you want to require a named parameter you know how to do that. Put a !
after it:
但是如果你想要一个命名参数,你知道如何做到这一点。在命名参数后面放一个 !
:
class Butterfly {
has $!common-name;
method BUILD ( :$common-name! ) { # required now
$!common-name = $common-name;
}
}
For the rest of this example that’s not what you want. You’re going to set default values and provide other ways to change the name.
对于本示例的其余部分,这不是你想要的。你将设置默认值并提供更改名称的其他方法。
You can add an accessor method to allow you to see the name that you’ve stored in the private attribute:
你可以添加一个访问器方法,以允许你查看已存储在私有属性中的名称:
class Butterfly {
has $!common-name;
method BUILD ( :$common-name ) {
$!common-name = $common-name;
}
method common-name { $!common-name }
}
my $butterfly = Butterfly.new: :common-name('Perly Cracker');
put $butterfly.common-name; # Perly Cracker
This is a problem if you don’t supply a :common-name
. There’s nothing in $!common-name
and you didn’t give .BUILD
anything to work with. When you try to output it you get a warning about the empty value:
如果你不提供 :common-name
,则会出现问题。 $!common-name
中没有任何东西,你没有给 .BUILD
任何东西。当你尝试输出它时,你会收到有关空值的警告:
my $butterfly = Butterfly.new;
put $butterfly.common-name; # Warning!
A default value in the common-name
method could solve this. If the attribute is not defined you could return an empty [Str
](https://docs.raku.org/type/Str.html) (or fail
or warn
):
common-name
方法中的默认值可以解决此问题。如果未定义属性,则可以返回空[字符串
](https://docs.raku.org/type/Str.html)(或 fail
或 warn
):
method common-name { $!common-name // '' }
属性可以具有默认值:
class Butterfly {
has $!common-name = '';
...
}
你可以使属性的默认值更有趣而不是使用空的[字符串
](https://docs.raku.org/type/Str.html):
class Butterfly {
has $!common-name = 'Unnamed Butterfly';
...
}
my $butterfly = Butterfly.new;
put $butterfly.common-name; # Unnamed Butterfly!
要更改 $!common-name
的值,你可以使用 rw
trait 标记 .common-name
以使其可读可写。如果你为方法赋值,则更改[块
](块
(https://docs.raku.org/type/Block.html)中的最后一个东西是一个 $!common-name
容器:
class Butterfly {
has $!common-name = 'Unnamed butterfly';
method BUILD ( :$common-name ) {
$!common-name = $common-name;
}
method common-name is rw { $!common-name }
}
my $butterfly = Butterfly.new;
$butterfly.common-name = 'Perly Cracker';
put $butterfly.common-name; # Perly Cracker!
The attributes can be typed like other variables. Constraining the type to [Str
](https://docs.raku.org/type/Str.html) means you can assign only thattype to .common-name
:
可以像其他变量一样类型化。将类型约束为[Str
](https://docs.raku.org/type/Str.html)意味着你只能将字符串类型赋值给 .common-name
:
class Butterfly {
has Str $!common-name = 'Unnamed butterfly';
...
}
练习12.3 实现一个带有 $!common-name
私有属性的 Butterfly
类。添加 $!color
私有属性。创建一个新的 Butterfly
对象,设置其名称和颜色,然后输出这些值。
4.34.2. Public Attributes
But enough of the hard way. Public attributes do a lot of that work for you. Use $.common-name
with a dot instead of a bang (!
). The accessor method is automatically defined for you and the default .BUILD
handles the setup by filling in the attributes from the named parameters in your call to .new
:
但足够艰难的方式。公共属性为你做了很多工作。使用带点的 $.common-name
而不是 !
。.
将自动为你定义存取方法,默认的 .BUILD
通过在你对 .new
的调用中填充命名参数的属性来处理设置:
class Butterfly {
has $.common-name = 'Unnamed Butterfly'
}
my $butterfly = Butterfly.new: :common-name('Perly Cracker');
put $butterfly.common-name; # Perly Cracker
Make it read-write with the rw
trait immediately after the attribute name but before the default value. After you create the object you can assign to the .common-name
method:
在属性名称之后但在默认值之前立即使用 rw
trait 进行读写。创建对象后,可以给 .common-name
方法赋值:
class Butterfly {
has $.common-name is rw = 'An unknown butterfly';
}
my $butterfly = Butterfly.new;
put $butterfly.common-name; # An unknown butterfly
$butterfly.common-name = 'Hamadryas perlicus';
put $butterfly.common-name; # Hamadryas perlicus
The attributes can have types just like other variables. Try to assign the wrong type and you get an exception:
属性可以像其他变量一样具有类型。如果尝试分配错误的类型,你会得到一个异常:
class Butterfly {
has Str $.common-name is rw = 'Unnamed butterfly';
}
my $butterfly = Butterfly.new;
$butterfly.common-name = 137; # Error!
To have a mixture of private and public attributes you have to do some work. You probably don’t want to define your own .BUILD
since you’d have to handle everything that the default one does for you. Instead, you can define a private attribute and assign to it later through a method. An rw
trait on the method either returns or assigns to the value of the last thingy in the [Block
](https://docs.raku.org/type/Block.html):
要拥有私有属性和公共属性的混合,你必须费点劲。你可能不想定义自己的 .BUILD
,因为你必须处理默认的一切。相反,你可以定义私有属性,稍后通过方法为其赋值。方法上的 rw
特质要么返回[块
](块
(https://docs.raku.org/type/Block.html)中最后一个东西赋值:
class Butterfly {
has Str $.common-name is rw = 'Unnamed butterfly';
has Str $!color;
method color is rw { $!color }
}
my $butterfly = Butterfly.new;
$butterfly.common-name = 'Perly Cracker';
$butterfly.color = 'Vermillion';
put "{.common-name} is {.color}" with $butterfly;
4.35. multi Methods
Read-write methods are one way to handle private attributes, but you can also create multi
methods for each case. Although this example looks simple, your validation and conversion requirements can be arbitrarily complex inside the [Block
](https://docs.raku.org/type/Block.html)s:
读写方法是处理私有属性的一种方法,但你也可以为每种情况创建 multi
方法。虽然这个例子看起来很简单,但是你的验证和转换要求在[块
](https://docs.raku.org/type/Block.html)中可以是任意复杂的:
class Butterfly {
has $!common-name = 'Unnamed butterfly';
has $!color = 'White';
multi method common-name () { $!common-name }
multi method common-name ( Str $s ) { $!common-name = $s }
multi method color () { $!color }
multi method color ( Str $s ) { $!color = $s }
}
my $butterfly = Butterfly.new;
$butterfly.common-name: 'Perly Cracker';
$butterfly.color: 'Vermillion';
put $butterfly.common-name; # Perly Cracker!
This gets annoying when you have many attributes. There’s another way that you could do this. Return the object in every method that sets a value. This allows you to chain methods to set many attributes in one statement where you don’t repeat the object each time:
当你有许多属性时,这会很烦人。还有另一种方法可以做到这一点。在每个设置值的方法中返回对象。这允许你链接方法以在一个语句中设置许多属性,每次不重复对象:
class Butterfly {
has $!common-name = 'Unnamed butterfly';
has $!color = 'White';
multi method common-name () { $!common-name; }
multi method common-name ( Str $s ) {
$!common-name = $s; self
}
multi method color () { $!color; }
multi method color ( Str $s ) { $!color = $s; self }
}
my $butterfly = Butterfly
.new
.common-name( 'Perly Cracker' )
.color( 'Vermillion' );
put "{.common-name} is {.color}" with $butterfly;
That looks similar to using do given
to topicalize the object and call methods on it:
这类似于使用 do given
来主题化对象并在其上调用方法:
my $butterfly = do given Butterfly.new {
.common-name( 'Perly Cracker' );
.color( 'Vermillion' );
};
put "{.common-name} is {.color}" with $butterfly;
Which technique you use depends on your task and personal preferences. You haven’t seen this with error handling or complex code, either. Those impact your choice too.
你使用哪种技术取决于你的任务和个人喜好。你还没有看到错误处理或复杂代码。那些也会影响你的选择。
4.36. Inheriting Types
An existing type might already do most of what you want. Instead of redefining everything that class already does, you can extend it, also known as inheriting from it. Declare the class with is
and the type you want to extend:
现有类型可能已经完成了你想要的大部分工作。你可以扩展它,而不是重新定义类已经执行的所有操作,也称为继承它。使用 is
和要扩展的类型声明类:
class Butterfly is Insect {};
You can do this inside the class definition with also
:
你也可以在类定义中使用 also
执行此操作:
class Butterfly {
also is Insect
};
Here, Insect
is a parent class (or super class or base class). Butterfly
is the child class (or derived type). The terminology isn’t particularly important; the base type is the more general one and the derived type is the more specific one.
在这里,Insect
是一个父类(或超类或基类)。 Butterfly
是子类(或派生类型)。术语不是特别重要;基类型是更通用的类型,派生类型是更具体的类型。
Everything you’ve seen in the Butterfly
class so far (a name and a color) applies to any insect. The name and color attributes are general things that describe any insect, so should be in the more general class. The`Butterfly` class now has nothing in it (a “null subclass”), but it should still work the same as it did before:
到目前为止,你在 Butterfly
上看到的所有东西(名称和颜色)都适用于任何昆虫。名称和颜色属性是描述任何昆虫的通用的东西,因此应该在更通用的类中。 Butterfly
类现在没有任何内容(“null子类”),但它应该仍然像以前一样工作:
class Insect {
has $.common-name is rw = 'Unnamed insect';
has $.color is rw = 'Brown';
}
class Butterfly is Insect {}
my $butterfly = Butterfly.new;
$butterfly.common-name = 'Perly Cracker';
$butterfly.color = 'Vermillion';
put "{.common-name} is {.color}" with $butterfly;
Butterfly
can have its own $.color
that overrides the one from Insect
. Declaring the attribute in`Butterfly` effectively hides the one in its parent class:
Butterfly`可以有自己的 `$.color
来覆盖 Insect
的那个属性。在 Butterfly
中声明属性有效地隐藏了其父类中的属性:
class Insect {
has $.common-name is rw = 'Unnamed insect';
has $.color is rw = 'Brown';
}
class Butterfly is Insect {
has $.color is rw = 'Mauve';
}
my $butterfly = Butterfly.new;
$butterfly.common-name = 'Perly Cracker';
# Perly Cracker is Mauve
put "{.common-name} is {.color}" with $butterfly;
Sometimes that’s not the right thing to do. The parent class might need to run some code in its version of the method to make everything else work. Instead of hiding the parent method you want to wrap it (or extend it).
有时这不是正确的事情。父类可能需要在其方法版本中运行一些代码才能使其他所有东西都有效。你想要包装它(或扩展它)的父方法而不是隐藏父方法。
The callsame
routine can do this for you. It redispatches the call with the same arguments. You run the parent method in your child method:
callsame
程序可以为你执行此操作。它使用相同的参数重新调度调用。你在子方法中运行父方法:
class Insect {
has $.common-name is rw = 'Unnamed insect';
has $!color = 'Brown';
method color is rw {
put "In Insect.color!";
$!color
}
}
class Butterfly is Insect {
has $!color = 'Mauve';
method color is rw {
put "In Butterfly.color!";
my $insect-color = callsame;
put "Insect color was {$insect-color}!";
$!color
}
}
my $butterfly = Butterfly.new;
$butterfly.common-name = 'Perly Cracker';
put "{.common-name} is {.color}" with $butterfly;
Inheritance isn’t the only way to add features to your class. You should save inheritance for specific cases where your class is a more specific type of the same thingy.
继承不是向类添加功能的唯一方法。对于特定情况,你应该保存继承,其中你的类是更具体的类型。
EXERCISE 12.4Create classes for the kingdom, phylum, class, order, family, and genus of a Hamadryas butterfly. The phylum inherits from kindgom, the class inherits from phylum, and so on. Each class notes its place in the hierarchy:`class Nymphalidae is Lepidoptera { }`Define a .full-name
method in Hamadryas
to join all the levels together.The genus Hamadryas is classified in Animalia, Arthropodia, Insecta, Lepidoptera, and Nymphalidae.
练习12.4 为Hamadryas 蝴蝶的王国,门,阶级,秩序,家庭和属创建类。门继承自 kindgom,该类继承自门,等等。每个班级都记录了它在层次结构中的位置: class Nymphalidae is Lepidoptera { }
在 Hamadryas
中定义一个完整的名称方法以将所有级别连接在一起.Hamadryas属被分类为Animalia,Arthropodia,Insecta,Lepidoptera和Nymphalidae。
4.36.1. Checking Inheritance
You’ve already seen .^mro
to get a [List
](https://docs.raku.org/type/List.html) of classes. The .isa
method returns True
or False
if the type you specify is in that [List
](https://docs.raku.org/type/List.html). You can test a type or an object with a type object as the argument (a [Str
](https://docs.raku.org/type/Str.html)):
你已经看过 .^mro
获得一个类[列表
](列表
(https://docs.raku.org/type/List.html)中,则 .isa
方法返回 True
或 False
。你可以使用类型对象作为参数([Str
](https://docs.raku.org/type/Str.html))来测试类型或对象:
put Int.isa: 'Cool'; # True
put Int.isa: Cool; # True
put Butterfly.isa: Insect; # True;
put Butterfly.isa: Int # False;
my $butterfly-object = Butterfly.new;
put $butterfly.isa: Insect; # True
Smart matching does the same job. That’s what when
is checking if you give it only a type:
智能匹配可以完成同样的工作。这就是 when
检查你是否只给它一个类型:
if Butterfly ~~ Insect {
put "Butterfly is an Insect";
}
if $butterfly ~~ Insect {
put "Butterfly is an Insect";
}
put do given $butterfly {
when Int { "It's a integer" }
when Insect { "It's an insect" }
}
You may have been wondering about the name of the .^mro
method. That’s for method resolution order in cases where you inherit from multiple classes:
你可能一直想知道 .^mro
方法的名称。这是在你从多个类继承的情况下的方法解析顺序:
class Butterfly is Insect is Flier {...}
I’m not going to tell you more about multiple inheritance in the hopes that you never do it. It’s possible, but you’ll likely solve your problem with the roles you’ll see in [Chapter 13](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch13.html#camelia-roles).
我不会告诉你更多关于多重继承的信息,希望你永远不会这样做。这是可能的,但你可能会用你在第13章中看到的角色来解决你的问题。
4.36.2. Stub Methods
A parent class can define a method but not implement it—this is known as an abstract method (or stub method). Use !!!
inside the [Block
](https://docs.raku.org/type/Block.html) to denote that something later will implement a method with that name:
父类可以定义一个方法但不实现它 - 这称为抽象方法(或存根方法)。在[块
](https://docs.raku.org/type/Block.html)中使用 !!!
表示稍后会实现具有该名称的方法:
class Insect {
has $.color is rw = 'Brown';
method common-name { !!! }
}
class Butterfly is Insect {
has $.color is rw = 'Mauve';
}
my $butterfly = Butterfly.new;
$butterfly.common-name = 'Perly Cracker';
put "{.common-name} is {.color}" with $butterfly;
When you run this the !!!
throws an exception:
当你运行这个代码时 !!!
抛出异常:
Stub code executed
Instead of the !!!
you can use …
. The triple dot calls fail
instead of die
. Either way something else needs to implement that method. A public attribute would do that for you:
你可以使用 …
而不是 !!!
。三点调用 fail
而不是 die
。无论哪种方式,其他东西都需要实现该方法。公共属性会为你执行此操作:
class Butterfly is Insect {
has $.common-name is rw;
has $.color is rw = 'Mauve';
}
4.37. Controlling Object Creation
Sometimes you want more control over your object creation. When you call .new
there are several steps and you’re able to hook into each of them. You don’t need all the gory details at the programmer level so I’ll spare you.
有时你希望更好地控制对象创建。当你调用 .new
时,有几个步骤,你可以接入每个步骤。你不需要程序员级别的所有残酷细节,所以我会饶了你。
When you call .new
you’re reaching into the root of the object system, [Mu
](https://docs.raku.org/type/Mu.html). .new
calls .bless
, which actually creates your object. Now you have an empty object. It’s not quite ready for use yet.
当你调用 .new
时,你正在进入对象系统的根,即 Mu
。 .new
调用 .bless
,它实际上创建了你的对象。现在你有一个空对象。它尚未准备好使用。
.bless
does some more work by calling .BUILDALL
on your empty object, passing it all the same arguments that you passed to .new
. .BUILDALL
visits each class in your inheritance chain, starting with [Mu
](https://docs.raku.org/type/Mu.html). You typically don’t want to mess with .BUILDALL
since it’s driving the process rather than affecting your objects.
.bless
通过在空对象上调用 .BUILDALL
来做更多工作,并将所有传递给 .new
的相同参数传递给它。 .BUILDALL
访问继承链中的每个类,从 [Mu
](https://docs.raku.org/type/Mu.html) 开始。你通常不希望混淆 .BUILDALL
,因为它正在推动流程而不是影响你的对象。
.BUILDALL
calls the .BUILD
method in your class if you’ve defined one. .BUILD
gets the same arguments as .new
. This is how your attributes get their values from your arguments. If no class defined a .BUILD
you get the default one that fills in your attributes from the named parameters.
如果你定义了一个,则 .BUILDALL
会在你的类中调用 .BUILD
方法。 .BUILD
获得与 .new
相同的参数。这是你的属性从参数中获取其值的方式。如果没有类定义 .BUILD
,你将获得从命名参数填充属性的默认类。
The default object creation mechanism wants to work with named parameters. You could rework everything for positional parameters but that would be a lot of work. |
默认对象创建机制希望使用命名参数。你可以为位置参数重做一切,但这将是很多工作。
After .BUILD
is done you have a completely built object that’s ready for use (but not the final object yet). The .TWEAK
method gives you a chance to adjust that object before you move on to the next class to go through the process again.
在 .BUILD
完成后,你有一个完全构建的对象可以使用(但还没有最终的对象)。 .TWEAK
方法让你有机会调整该对象,然后再转到下一个类再次完成该过程。
You should declare both .BUILD
and .TWEAK
with submethod
. This is a hybrid of sub
and method
; it acts just like a method but a subclass doesn’t inherit it (just like you don’t inherit subroutines):
你应该用 submethod
声明 .BUILD
和 .TWEAK
。这是 sub
和 method
的混合;它就像一个方法,但是一个子类不会继承它(就像你不继承子程序一样):
# $?CLASS is a compile-time variable for the current class
# &?ROUTINE is a compile-time variable for the current routine
class Insect {
submethod BUILD { put "In {$?CLASS.^name}.{&?ROUTINE.name}" }
submethod TWEAK { put "In {$?CLASS.^name}.{&?ROUTINE.name}" }
}
class Butterfly is Insect {
submethod BUILD { put "In {$?CLASS.^name}.{&?ROUTINE.name}" }
submethod TWEAK { put "In {$?CLASS.^name}.{&?ROUTINE.name}" }
}
my $b = Butterfly.new;
The .TWEAK
method is called before .BUILDALL
moves on to the next class:
在 .BUILDALL
之前调用 .TWEAK
方法进入下一个类:
In Insect.BUILD
In Insect.TWEAK
In Butterfly.BUILD
In Butterfly.TWEAK
Now that you’ve seen the order in which things happen, let’s look at each step a little more closely.
现在你已经看到了事情发生的顺序,让我们更仔细地看一下每一步。
4.37.1. Building Objects
.BUILD
lets you decide how to treat your newly created object. Start with a submethod
that does nothing:
.BUILD`让你决定如何对待新创建的对象。从一个什么都不做的 `submethod
开始:
class Butterfly {
has $.color;
has $.common-name;
submethod BUILD {} # does nothing
}
my $butterfly = Butterfly.new: :color('Magenta');
put "The butterfly is the color {$butterfly.color}";
The color isn’t set and you get a warning about an uninitialized value:
颜色未设置,你会收到有关未初始化值的警告:
The butterfly is the color
Use of uninitialized value of type Any in string context.
.BUILDALL
found your .BUILD
so it used your version to set up the object. The color value in your call to .new
isn’t assigned to the $!color
attribute because your empty .BUILD
didn’t handle that. You need to do that yourself. By default all the named parameters are in %_
and .BUILD
gets all of the same arguments as`.new`:
.BUILDALL
找到你的 .BUILD
所以它用你的版本来设置对象。调用 .new
的颜色值未分配给 $!color
属性,因为空的 .BUILD
没有处理它。你需要自己做。默认情况下,所有命名参数都在 %_
和 .BUILD
中获取所有相同的参数作为 .new
:
class Butterfly {
has $.color;
has $.common-name;
submethod BUILD {
$!color = %_<color>;
}
}
Use the argument list for .BUILD
to automatically define some named parameters to variables:
使用 .BUILD
的参数列表自动为变量定义一些命名参数:
class Butterfly {
has $.color;
has $.common-name;
submethod BUILD ( :$color ) {
$!color = $color;
}
}
If you don’t specify a color
named argument you get another warning because the value in $color
is uninitialized. In some cases you might want that named parameter to be required, so you put a !
after it:
如果未指定 color
命名参数,则会收到另一个警告,因为 $color
中的值未初始化。在某些情况下,你可能希望该命名参数是必需的,所以你放一个 !
之后:
class Butterfly {
has $.color;
has $.common-name;
submethod BUILD ( :$color! ) {
$!color = $color;
}
}
Other times you might want to set a default value. Another attribute won’t work because the object build process hasn’t set its default value yet:
其他时候你可能想要设置默认值。另一个属性不起作用,因为对象构建过程尚未设置其默认值:
class Butterfly {
has $!default-color = 'Wine'; # Won't work
has $.color;
has $.common-name;
submethod BUILD ( :$color! ) {
$!color = $color // $!default-color; # No setup yet!
}
}
A private method could work; a private method can only be seen from code inside the class and cannot be inherited. A submethod
isn’t inheritable either but is still a public method:
私有方法可以工作;私有方法只能从类中的代码中看到,不能被继承。submethod
也不是可继承的,但仍然是一种公共方法:
class Butterfly {
method default-color { 'Wine' }
has $.color;
has $.common-name;
submethod BUILD ( :$color ) {
$!color = $color // self.default-color;
}
}
Class variables can do the same job. A lexical variable defined in the class [Block
](https://docs.raku.org/type/Block.html) is only visible to the code in the same [Block
](https://docs.raku.org/type/Block.html) and the [Block
](https://docs.raku.org/type/Block.html)s inside it:
类变量可以完成相同的工作。类[Block
](https://docs.raku.org/type/Block.html)中定义的词法变量仅对同一块中的代码及其中的块可见:
class Butterfly {
my $default-color = 'Wine';
has $.color;
has $.common-name;
submethod BUILD ( :$color ) {
$!color = $color // $default-color;
}
}
What’s more interesting to .BUILD
is the extra setup you don’t want to be part of the interface. Perhaps you want to track when you used the default value so you can distinguish it from the case where the specified color happened to be the same:
更有趣的是 .BUILD
是你不希望成为界面一部分的额外设置。你可能希望跟踪何时使用默认值,以便将其与指定颜色恰好相同的情况区分开来:
class Butterfly {
my $default-color = 'Wine';
has $.used-default-color;
has $.color;
submethod BUILD ( :$color ) {
if $color {
$!color = $color;
$!used-default-color = False;
}
else {
$!color = $default-color;
$!used-default-color = True;
}
}
}
my $without = Butterfly.new;
put "Used the default color: {$without.used-default-color}";
my $with = Butterfly.new: :color('Wine');
put "Used the default color: {$with.used-default-color}";
Even though those two butterflies are the same color, you know which one specified a color and which one didn’t:
即使这两只蝴蝶是相同的颜色,你知道哪一种指定了颜色,哪一种没有指定颜色:
Used the default color: True
Used the default color: False
4.37.2. Tweaking Objects
When you create an object you can use .TWEAK
to set either the color you supplied as a named argument or the default color:
创建对象时,可以使用 .TWEAK
设置作为命名参数提供的颜色或默认颜色:
class Insect {
has $!default-color = 'Brown';
has $.common-name is rw = 'Unnamed insect';
has $.color is rw;
submethod TWEAK ( :$color ) {
self.color = $color // $!default-color;
}
}
class Butterfly is Insect {}
my $butterfly = Butterfly.new;
$butterfly.common-name = 'Perly Cracker';
put "{.common-name} is {.color}" with $butterfly;
The output shows that you got the default from Insect
. .TWEAK
ran inside Insect
and set an attribute inside Insect
. The .color
method is defined in Insect
so it works out:
输出显示你从 Insect
获得默认值。 .TWEAK
在 Insect
内部运行并在 Insect
中设置了一个属性。 .color
方法在 Insect
中定义,因此它可以解决:
Perly Cracker is Brown
If you specify a color, that color is actually set:
如果指定颜色,则实际设置该颜色:
my $butterfly = Butterfly.new: :color('Purple');
You can modify Butterfly
to have its own default color and .TWEAK
. The .TWEAK
method is the same but you wouldn’t want to inherit it. It depends on the presence of an attribute that it can’t know the child class has:
你可以修改 Butterfly
以拥有自己的默认颜色和 .TWEAK
。 .TWEAK
方法是相同的,但你不希望继承它。它取决于它无法知道子类具有的属性的存在:
class Butterfly is Insect {
has $.default-color = 'Vermillion';
submethod TWEAK ( :$color ) {
self.color = $color // $!default-color;
}
}
4.38. Private Classes
You can declare classes with my
to make them private to the current scope. At the file level that class is only available in that file. If you load the file that contains it you won’t be able to see it:
你可以使用 my
声明类,使其成为当前范围的私有。在文件级别,该类仅在该文件中可用。如果你加载包含它的文件,你将无法看到它:
# PublicClass.pm6
my class PrivateClass { # Hidden from outside the file
method hello { put "Hello from {self.^name}" }
}
class PublicClass {
method hello { PrivateClass.hello }
}
In your program you can load PublicClass
and call a method on the PublicClass
type. PublicClass`can see `PrivateClass
because it’s in the same file. From your program you can’t call PrivateClass
directly, though. That scope doesn’t know about that type:
在你的程序中,你可以加载 PublicClass
并在 PublicClass
类型上调用方法。 PublicClass
可以看到`PrivateClass`,因为它在同一个文件中。但是,从你的程序中,你无法直接调用 PrivateClass
。该范围不知道该类型:
use PublicClass;
PublicClass.hello; # Hello from PrivateClass
PrivateClass.hello; # Error: Undeclared name: PrivateClass
If you need a class only inside another class (and not the rest of the file), you can declare it inside the class. This can be handy to compartmentalize and organize behavior inside a class:
如果你只需要一个类在另一个类(而不是文件的其余部分)中,你可以在类中声明它。这可以方便地划分和组织类中的行为:
class Butterfly {
my class PrivateHelper {}
}
Private classes are a great tool when you want to compartmentalize some behavior that you need but don’t want to expose to normal users. You can use them for intermediate objects that the main program never need know exist.
当你想要划分某些你需要但不想向普通用户公开的行为时,私有类是一个很好的工具。你可以将它们用于主程序永远不需要知道的中间对象。
EXERCISE 12.5Create a Butterfly
class that contains a private class that tracks when the object was created and updated. Use it to count the number of updates to the class. A method in Butterfly
should access the private class to output a summary.
EXERCISE 12.5创建一个 Butterfly
类,它包含一个私有类,用于跟踪对象何时被创建和更新。用它来计算类的更新次数。 Butterfly
中的方法应该访问私有类以输出摘要。
4.39. Summary
Classes will likely be your main way of organizing information in your programs, though you don’t see it so much in this book because you need to see mostly syntactic topics rather than application design advice. I didn’t have the space for good coverage of object-oriented design or analysis, but you should definitely research those on your own. The right design will make your life so much easier.
类可能是你在程序中组织信息的主要方式,尽管你在本书中没有看到这么多,因为你需要查看主要的语法主题而不是应用程序设计建议。我没有足够的空间来覆盖面向对象的设计或分析,但你绝对应该自己研究它们。正确的设计将使你的生活变得更加轻松。 == 角色
角色是 mixins,可以增强你的类,就像它们的内容被定义在类里一样。一旦定义,它们的源实际上会被遗忘(与父类不同)。你可以使用角色来更改类,从现有类创建新类,以及增强单个对象。它们比继承更灵活,通常是更好的解决方案。角色用于代码重用,而类用于管理对象。
4.40. 给类添加行为
构造一个空的 Butterfly
类。即使没有属性接收参数的值,你也可以为 .new
提供参数:
class Butterfly {}
my $butterfly = Butterfly.new: :common-name('Perly Cracker');
现在给你的蝴蝶命名。名字应该是 Butterfly
类的一部分吗?名字不是对象. Hamadryas guatemalena 是蝴蝶的名字。 Guatemalan Cracker,Calicó 和 Soñadoracomún 也是蝴蝶的名字。这些都是同一只蝴蝶的名字。
最终,你编写的代码必须在该语言框架内运行。语法有时会让你在认知上将事物分开。 |
A name is not a more specific version of something the class already does and it’s not limited to butterflies or butterfly-like things. Many dissimilar things can have a common name—animals, cars, food. Not only that, but different people, cultures, or even sections of your office may choose different names. This fact does not define your thingy or its behavior. It’s not something that makes a butterfly what it is.
名字不是该类已经做过的更具体的版本,它不仅限于蝴蝶或蝴蝶般的东西。许多不同的东西可以有一个共同的名字 - 动物,汽车,食物。不仅如此,不同的人,文化,甚至办公部门都可以选择不同的名字。这个事实并没有定义你的东西或它的行为。这不是让蝴蝶成为现实的东西。
Create a role that contains everything you need for a common name. Everything about a name (and nothing else!) can show up in that role. The role doesn’t care what sort of thingy uses it, whether that’s a butterfly, a car, or a pizza. Declare it with role
just as you would a class:
创建一个包含公共名字所需内容的角色。关于名字的所有内容(没有别的!)都可以显示在该角色中。这个角色并不关心什么样的东西使用它,无论是蝴蝶,汽车还是披萨。用 role
声明一个角色就像声明类一样:
role CommonName {
has $.common-name is rw = 'An unnamed thing';
}
In fact, a role can act just like a class. You can make an object from a role. This puns the role into a class:
事实上,角色就像类一样。你可以从角色创建对象。这个角色变成了一个类:
role CommonName {
has $.common-name is rw = 'An unnamed thing';
}
my $name = CommonName.new: :common-name('Perly Cracker');
put $name.common-name; # Perly Cracker
Apply a role to a class with does
after the class name in the same way you used is
for inheritance:
在类名之后使用 does
以类似于继承使用 is
的方式将角色应用于类:
class Butterfly does CommonName {};
Every Butterfly
object now has a $.common-name
attribute and .new
now sets the :common-name
using either the default or the name you provide:
每个 Butterfly
对象现在都有一个 $.common-name
属性,而 .new
现在使用默认名称或你提供的名称设置 :common-name
:
my $unnamed-butterfly = Butterfly.new;
put $unnamed-butterfly.common-name; # An unnamed thing
my $butterfly = Butterfly.new: :common-name('Perly Cracker');
put $butterfly.common-name; # Perly Cracker
You can use the same role for something completely different. An SSL certificate has a common name, although its semantic meaning is different:
对于完全不同的东西,你可以使用相同的角色。 SSL 证书具有常用名,但其语义含义不同:
class SSLCertificate does CommonName {}
Butterflies and SSL certificates are completely different things and it wouldn’t make sense for them to inherit from the same thing. However, they can use the same role.
蝴蝶和 SSL 证书是完全不同的东西,它们从同一个东西继承是没有意义的。但是,他们可以使用相同的角色。
EXERCISE 13.1Create a ScientificName
role that adds an attribute to store a [Str
](https://docs.raku.org/type/Str.html) for the scientific name. Apply that role to Butterfly
, create an object, and output the scientific name.
练习13.1 创建一个 ScientificName
角色,添加一个属性来存储学名的[字符串
](https://docs.raku.org/type/Str.html)。将该角色应用于 Butterfly
,创建对象并输出学名。
4.40.1. Applying Multiple Roles
You can give a butterfly a scientific name as well as a common name by creating a different role for that. This one has several attributes:
你可以通过为蝴蝶创建不同的角色,为蝴蝶提供学名和常用名。这只蝴蝶有几个属性:
role ScientificName {
has $.kingdom is rw;
has $.phylum is rw;
has $.class is rw;
has $.order is rw;
has $.family is rw;
has $.genus is rw;
has $.species is rw;
}
You can replace the CommonName
role with ScientificName
and things work as before:
你可以使用 ScientificName
替换 CommonName
角色,并且事情像以前一样工作:
class Butterfly does ScientificName {};
my $butterfly = Butterfly.new: :genus('Hamadryas');
put $butterfly.genus; # Hamadryas;
Multiple does
expressions apply multiple roles:
多个 does
表达式应用多个角色:
class Butterfly does ScientificName does CommonName {};
my $butterfly = Butterfly.new:
:genus('Hamadryas'),
:common-name('Perly Cracker')
;
put $butterfly.genus;
put $butterfly.common-name;
Each role inserts its code into Butterfly
so it can respond to methods from either source:
每个角色都将其代码插入 Butterfly
中,以便它可以响应来自任何一个源的方法:
Hamadryas
Perly Cracker
EXERCISE 13.2Create a role Lepidoptera
to represent butterflies. Fill in everything from kingdom Animalia, phylum Athropoda, class Insecta, and order Lepidoptera. Allow the role to change the family, genus, and species. Use that role in your own Butterfly
class. After you get that working add the CommonName
role.
练习13.2 创建一个 Lepidoptera
代表蝴蝶的角色。填写动物界,动物门,昆虫纲和鳞翅目的一切。允许角色改变族,属和物种。在你自己的 Butterfly
类中使用该角色。完成后,添加 CommonName
角色。
4.41. Methods in Roles
You can define methods in roles too. Give the ScientificName
role a .gist
method to create your own human-readable text version of the object:
你也可以在角色中定义方法。为 Scientific Name
角色提供一个 .gist
方法,以创建自己的对象的人类可读文本版本:
role ScientificName {
...; # all the attributes specified earlier
method gist {
join ' > ', $.kingdom, $.genus;
}
}
role CommonName {
has $.common-name is rw;
}
class Butterfly does ScientificName does CommonName {};
my $butterfly = Butterfly.new:
:genus('Hamadryas'),
:common-name('Perly Cracker')
;
put $butterfly.genus;
put $butterfly.common-name;
put $butterfly.gist;
EXERCISE 13.3Update your Lepidoptera
role to have a binomial-name
method that returns a [Str
](https://docs.raku.org/type/Str.html) that combines the genus and species of the butterfly (in biospeak that’s the “binomial name”).
练习13.3 更新你的 Lepidoptera
角色以拥有 binomial-name
方法,它返回一个结合蝴蝶属和种类的[字符串
](https://docs.raku.org/type/Str.html)(在biospeak中是“二项式名称”)。
To reuse these roles you want to make them available for any code to find and load. You can store roles by themselves in files just as you can with classes. Load them with use
and they are available in that scope.
要重用这些角色,你需要使它们可供任何代码查找和加载。你可以像在类中一样将文件存储在文件中。使用 use
加载它们,它们在该范围内可用。
EXERCISE 13.4Separate the Lepidoptera
and CommonName
roles and the Butterfly
class into their own files. Load those files into your program where you create your Butterfly
object. Make this program work:`use Butterfly; my $butterfly = Butterfly.new: :family( 'Nymphalidae' ), :genus( 'Hamadryas' ), :species( 'perlicus' ), ; put $butterfly.binomial-name;`
练习13.4 将 Lepidoptera
和 CommonName
角色还有 Butterfly`类分开到它们自己的文件中。在你要创建 `Butterfly
的地方加载这些文件到你的程序中。使这个程序能工作:
use Butterfly;
my $butterfly = Butterfly.new:
:family( 'Nymphalidae' ),
:genus( 'Hamadryas' ),
:species( 'perlicus' ),
;
put $butterfly.binomial-name;
4.42. De-Conflicting Roles
If two roles try to insert the same names you may have to do extra work. Suppose that both ScientificName`and `CommonName
had a .gist
method:
如果两个角色尝试插入相同的名称,则可能需要执行额外的工作。假设 ScientificName
和 CommonName
都有.gist方法:
role ScientificName {
...; # all the attributes specified earlier
method gist {
join ' > ', $.kingdom, $.genus;
}
}
role CommonName {
has $.common-name is rw;
method gist { "Common name: $.common-name" }
}
class Butterfly does ScientificName does CommonName {};
The .gist
method has an explicit signature and isn’t marked with multi
. When you try to compile this you get an error telling you that two roles tried to insert the same method:
.gist
方法具有显式签名,并且未标记为 multi
。当你尝试编译它时,你会收到一个错误,告诉你两个角色尝试插入相同的方法:
Method 'gist' must be resolved by class Butterfly because
it exists in multiple roles (CommonName, ScientificName)
You can add a .gist
method to Butterfly
. Neither role replaces a method already in the class:
你可以向 Butterfly
添加 .gist
方法。这两个角色都不替换类中已有的方法:
role ScientificName {
...; # all the attributes specified earlier
method gist {
join ' > ', $.kingdom, $.genus;
}
}
role CommonName {
has $.common-name is rw;
method gist { "Common name: $.common-name" }
}
class Butterfly does ScientificName does CommonName {
method gist {
join "\n",
join( ' > ', $.kingdom, $.genus ),
"Common name: $.common-name";
}
};
Or if you want both methods from the roles you can distinguish them with different signatures (and use multi
). Their role names as a type might do:
或者,如果你想要角色中的两种方法,则可以使用不同的签名区分它们(并使用 multi
)。他们的角色名称作为类型可能是:
role ScientificName {
...; # all the attributes specified earlier
multi method gist ( ScientificName ) {
"$.genus $.species";
}
}
role CommonName {
has $.common-name is rw;
multi method gist ( CommonName ) {
"Common name: $.common-name";
}
}
class Butterfly does ScientificName does CommonName {};
my $butterfly = Butterfly.new:
:genus('Hamadryas' ),
:species('perlicus'),
:common-name( 'Perly Cracker' ),
;
put '1. ', $butterfly.gist( CommonName );
put '2. ', $butterfly.gist( ScientificName );
This way you get both methods:
这样你就得到了两种方法:
1. Common name: Perly Cracker
2. Hamadryas perlicus
You can have the same method in the Butterfly
class as long as you declare it with multi
and give it a unique signature:
你可以在 Butterfly
类中使用相同的方法,只要用 multi
声明它并给它一个唯一的签名:
class Butterfly does ScientificName does CommonName {
multi method gist {
join "\n", map { self.gist: $_ },
( ScientificName, CommonName );
}
};
my $butterfly = Butterfly.new:
:genus('Hamadryas'),
:species('perlicus'),
:common-name('Perly Cracker')
;
put '1. ', $butterfly.gist( CommonName );
put '2. ', $butterfly.gist( ScientificName );
put '3. ', $butterfly.gist;
Your output shows all three and you can pick whichever you like:
你的输出显示全部三个,你可以选择你喜欢的任何一个:
1. Common name: Perly Cracker
2. Hamadryas perlicus
3. Hamadryas perlicus
Common name: Perly Cracker
4.43. Anonymous Roles
Not every role needs a name. If you want a role that you don’t expect to use again you can add it directly with but
. You can apply that directly to a class name. This actually creates a new class with the role applied to it. The new class inherits from the original:
并非每个角色都需要一个名字。如果你想要一个不希望再次使用的角色,可以用 but
直接添加它。你可以将其直接应用于类名。这实际上创建了一个应用了角色的新类。新类继承自原始:
class Butterfly {};
my $class-role = Butterfly but role { has $.common-name };
put $class-role.^name; # Butterfly+{<anon|140470326869504>}
say $class-role.^mro; # ((...) (Butterfly) (Any) (Mu))
my $butterfly = $class-role.new:
:common-name( 'Perly Cracker' );
put $butterfly.common-name;
You can do the same thing with less work by removing the variables that stored the classes:
通过删除存中储类的变量,你可以用更少的工作做同样的事情:
my $butterfly2 = ( Butterfly but role { has $.common-name } ).new:
:common-name('Perlicus Cracker');
put $butterfly2.^name;
put $butterfly2.common-name;
That’s still messy. You can apply it to the object directly:
那仍然很混乱。你可以直接将角色应用于对象:
my $butterfly = Butterfly.new;
my $butterfly2 = $butterfly
but role { has $.common-name is rw };
$butterfly2.common-name = 'Perlicus Cracker';
put $butterfly2.^name;
put $butterfly2.common-name;
You can even skip the variable to store the first object. Without the variable to store the initial object you get something a little shorter:
你甚至可以跳过变量来存储第一个对象。如果没有用于存储初始对象的变量,你可以获得更短的内容:
my $butterfly = Butterfly.new
but role { has $.common-name is rw };
$butterfly.common-name = 'Perlicus Cracker';
put $butterfly.^name;
put $butterfly.common-name;
This has the drawback that the original object doesn’t know about the roles, so you can’t set the common name in the constructor. Your role has to allow the object to change the value to set a value.
这样做的缺点是原始对象不知道角色,因此你无法在构造函数中设置公共名称。你的角色必须允许对象更改值以设置值。
Adding a role to an object is handy when you have an object that you may not have created; perhaps it was an argument to your method or the return value from a method you don’t control. In this example you take an argument (and make it is copy
so you can add the role). You call show-common-name
once with a plain Butterfly
. The subroutine sees that the object doesn’t know about common-name
, so it adds it. In your second call to show-common-name
your argument already has the common-name
attribute so it doesn’t need show-common-name
to add it:
当你有一个你可能没有创建的对象时,为一个对象添加一个角色很方便;也许这是你的方法的参数或你不能控制的方法的返回值。在这个例子中,你接受一个参数(并使它可拷贝(is copy
),这样你就可以添加角色)。你用普通的 Butterfly
调用一次 show-common-name
。子例程看到对象不知道 common-name
,所以它添加了它。在第二次调用 show-common-name
时,你的参数已经有了 common-name
属性,所以它不需要 show-common-name
来添加它:
sub show-common-name ( $butterfly is copy ) {
unless $butterfly.can: 'common-name' {
put "Adding role!";
$butterfly = $butterfly
but role { has $.common-name is rw };
$butterfly.common-name = 'Perlicus Cracker';
}
put $butterfly.common-name;
}
# an object without the role
my $butterfly = Butterfly.new;
show-common-name( Butterfly.new );
# an object that already has the role
my $class-role = Butterfly but role { has $.common-name };
show-common-name( $class-role.new: :common-name( 'Camelia' ) );
The output shows that you added the role in your first call but not the second:
Adding role!
Perlicus Cracker
Camelia
When should you apply your role? Whenever it makes sense for your problem.
你应该在什么时候应用你的角色?每当它对你的问题有意义时。
EXERCISE 13.5Take your Lepidoptera
role to its logical conclusion. Start with a new Animalia
role to represent only the kingdom. Create an Arthropoda
role to include the Animalia
role and represent the phylum. Do this all the way down to the Hamadryas genus. From there, create a Hamadrayas
class that inherits from Butterfly`but does all the taxonomic roles down to the genus. From the `Hamadrayas
class you should be able to set a species. Make this program work:`use lib <.>; use Hamadryas; my $cracker = Hamadryas.new: :species( 'perlicus' ), :common-name( 'Perly Cracker' ), ; put $cracker.binomial-name; put $cracker.common-name;`
练习13.5 将你的 Lepidoptera
角色归结为合乎逻辑的结论。从新的 Animalia
角色开始,仅代表王国。创建 Arthropoda
角色以包括 Animalia
角色并代表门。一直这样做到 Hamadryas 属。从那里,创建一个继承自`Butterfly` 的 Hamadrayas
类,但将所有的分类角色归结为属。从 Hamadrayas
类,你应该能够设置一个物种。使这个程序工作:
use lib <.>;
use Hamadryas;
my $cracker = Hamadryas.new:
:species( 'perlicus' ),
:common-name( 'Perly Cracker' ),
;
put $cracker.binomial-name; put $cracker.common-name;
4.44. Summary
You can define common code in a role and reuse it with disparate things. Since it doesn’t create an inheritance relationship it’s perfectly suited for features that don’t define the basic idea of the type.
你可以在角色中定义公共代码,并将其重用于不同的东西。由于它不创建继承关系,因此非常适合未定义类型基本概念的功能。 == Junctions和集合
4.45. Junctions
A [Junction
](https://docs.raku.org/type/Junction.html) is a combination of values that is mostly indistinguishable from a single value. They have their roots in the math of quantum mechanics. You may have heard of Schrödinger’s cat, who is both dead and alive at the same time—an analogy that physicist used to show how ridiculous this all is. Well, the joke was on him.
[Junction
](https://docs.raku.org/type/Junction.html) 是一组值,它们与单个值几乎无法区分。它们根植于于量子力学中的数学。你可能听说过薛定谔的猫,同一个时间它既死又活着 - 这个物理学家用来表示这一切是多么荒谬。那个笑话现在应在 Junction 身上了。
4.45.1. any
The first [Junction
](https://docs.raku.org/type/Junction.html) is the any
. This “any” is lowercase and is not related to the type [Any
](https://docs.raku.org/type/Any.html). It creates a value that can act like, well, any of the ones you gave it:
第一个 [Junction
](https://docs.raku.org/type/Junction.html) 是 any
。这个“any”是小写的,与 [Any
](https://docs.raku.org/type/Any.html) 类型无关。它会创建一个值,就像你给它的任何一个一样:
my $first-junction = any( 1, 3, 7 );
You can make a [Junction
](https://docs.raku.org/type/Junction.html) from an [Array
](https://docs.raku.org/type/Array.html) or any other [Positional
](https://docs.raku.org/type/Positional.html):
你可以从[数组](https://docs.raku.org/type/Array.html)或任何其他 [Positional
](https://docs.raku.org/type/Positional.html) 创建一个 [Junction
](https://docs.raku.org/type/Junction.html):
my $junction = any( @array ); # Array
my $junction = any( 1 .. 10 ); # Range
my $junction = any( 1 ... 10 ); # Sequence
Now you have a [Junction
](https://docs.raku.org/type/Junction.html) of three values. It will only ever have three values. You can’t take one away or add one. There’s no interface to extract them or count them. You’re not supposed to know—or even care—which values are in there. In fact, [Junction
](https://docs.raku.org/type/Junction.html) is the only builtin type that does not inherit from [Any
](https://docs.raku.org/type/Any.html):
现在你有一个三个值的 [Junction
](https://docs.raku.org/type/Junction.html)。它只会有三个值。你不能拿走一个或添加一个。没有接口可以提取它们或计算它们。你不应该知道 - 甚至关心 - 那里有哪些观。事实上, [Junction
](https://docs.raku.org/type/Junction.html) 是唯一不从 [Any
](https://docs.raku.org/type/Any.html) 继承的内置类型:
% raku
To exit type 'exit' or '^D'
> my $first-junction = any( 1, 3, 7 );
any(1, 3, 7)
> $first-junction.^name
Junction
> $first-junction.^mro
((Junction) (Mu))
These are quite handy in complex conditions. Consider the annoying code you’ve had to write to test if a value is one of three possible numbers:
这些在复杂条件下非常方便。考虑一下你必须编写的令人讨厌的代码,以测试值是否是三个可能的数字之一:
my $n = any( 1, 3, 7 );
if $n == 1 || $n == 3 || $n == 7 {
put "n is one of those values";
}
Being clever with a [Hash
](https://docs.raku.org/type/Hash.html) doesn’t actually feel that much more clever:
聪明的 [Hash
](https://docs.raku.org/type/Hash.html) 实际上并没有感觉更聪明:
my Int %hash = map { $_ => True }, (1, 3, 7);
if %hash{$n}:exists {
put "n is one of those values";
}
Not only does the [Junction
](https://docs.raku.org/type/Junction.html) equal any of those values, but it also equals all of them. This looks like a [Block
](https://docs.raku.org/type/Block.html)that would never execute, but it does:
[`Junction`](https://docs.raku.org/type/Junction.html) 不仅等于这些值中的任何一个,而且它也等于所有这些值。这看起来像一个永远不会执行的 [`Block`](https://docs.raku.org/type/Block.html),但它确实:
if $n == 1 && $n == 3 && $n == 7 {
put "n is all of those values";
}
A [Junction
](https://docs.raku.org/type/Junction.html) is much closer to how you’d probably describe this in speech:
[Junction
](https://docs.raku.org/type/Junction.html) 更接近你可能在演讲中描述的方式:
if $n == any( 1, 3, 7 ) {
put "n is one of those values";
}
When you operate on a [Junction
](https://docs.raku.org/type/Junction.html) your code may distribute (autothread) that operation over all of its values to produce a [Junction
](https://docs.raku.org/type/Junction.html) of intermediate results. The first step might look like this:
当你在 [Junction
](https://docs.raku.org/type/Junction.html) 上操作时,你的代码可以在其所有值上分配(自动读取)该操作,以产生中间结果的 [Junction
](https://docs.raku.org/type/Junction.html)。第一步可能如下所示:
if any( 1 == $n, 3 == $n, 7 == $n ) {
put "n is one of those values";
}
These evaluate to their Boolean values. If $n
is 3
one of the comparisons is True
:
这些求值为其布尔值。如果 $n
为 3
,则其中一个比较为 True
:
my $n = 3;
if any( False, True, False ) {
put "n is one of those values";
}
Any True
makes the entire junctive expression True
:
任何一个为 True
会使整个正则表达式为 True
:
my $n = 3;
if True {
put "n is one of those values";
}
You don’t have to define the [Junction
](https://docs.raku.org/type/Junction.html) in the condition. It might already be in a variable and ready for use:
你不必在条件中定义 [Junction
](https://docs.raku.org/type/Junction.html)。它可能已经在变量中并且可以使用:
my $any = any( 1, 3, 7 );
if $n == $any {
put "n is one of those values";
}
Here’s the beauty of [Junction
](https://docs.raku.org/type/Junction.html)s—you don’t have to know that you are using one. Here’s an [Array
](https://docs.raku.org/type/Array.html) that has some “normal” values and one that is a [Junction
](https://docs.raku.org/type/Junction.html):
这是 [Junction
](https://docs.raku.org/type/Junction.html) 的美之所在 - 你不知不觉就在使用它。这是一个具有一些“正常”值的[数组
](https://docs.raku.org/type/Array.html),其中一个元素是 [Junction
](https://docs.raku.org/type/Junction.html):
my @array = 5, any( 1, 7 ), 8, 9;
for @array -> $item {
put "$item was odd" unless $item %% 2;
}
The loop works with single “normal” values as well as [Junction
](https://docs.raku.org/type/Junction.html)s. Notice that the [Junction
](https://docs.raku.org/type/Junction.html) creates two lines of output. The stringification happened for each value:
在这个循环中,单个“正常”值以及 [Junction
](https://docs.raku.org/type/Junction.html) 都起作用。请注意,[Junction
](https://docs.raku.org/type/Junction.html) 创建了两行输出。每个值都发生了字符串化:
5 was odd
1 was odd
7 was odd
9 was odd
That multiple stringification could change in the future; it wasn’t this way when I started the book and it might change again. The .gist
on $item
prevents that:
多重字符串化可能会在未来发生变化; 当我开始写这本书时它不是这样的,它可能会再次改变。 $item
上的 .gist
可以防止多重字符串化:
my @array = 5, any( 1, 7 ), 8, 9;
for @array -> $item {
put "{$item.gist} was odd" unless $item %% 2;
}
5 was odd
any(1, 7) was odd
9 was odd
EXERCISE 14.1Make an any
[Junction
](https://docs.raku.org/type/Junction.html) of the prime numbers between 1 and 10 (so, 2, 3, 5, and 7). Use that [Junction
](https://docs.raku.org/type/Junction.html) to note which numbers from 1 to 10 are prime.
练习14.1使用 1 到 10 之间的素数制作一个 any
[Junction
](https://docs.raku.org/type/Junction.html)(因此,2,3,5和7)。使用该 [Junction
](https://docs.raku.org/type/Junction.html) 来记录从1到10的哪些数字是素数。
There’s a symbolic notation for any
. The |
between values creates a [Junction
](https://docs.raku.org/type/Junction.html). It looks similar to the ||
for the logical OR operator but is not related:
any
有一个象征性的符号。 值之间的 |
创建一个 [Junction
](https://docs.raku.org/type/Junction.html)。它看起来类似于 ||
对于逻辑OR运算符但不相关:
my $n = 3;
my $any = 1 | 3 | 7;
if $n == $any {
put "n is one of those values";
}
Raku uses | , & , and ^ to create [Junction ](https://docs.raku.org/type/Junction.html)s. You might be used to these as numeric bit operators in other languages. You’ll find those now are called +| , +^ , and +& . That leading + denotes the numeric flavor.
|
Raku 使用 |
,&
和 ^
来创建 [Junction
](https://docs.raku.org/type/Junction.html)。你可能习惯使用其他语言中的数字位运算符。你会发现现在称为 +|
,+^
和 +&
。前导 +
表示数字风味。
You can change [Junction
](https://docs.raku.org/type/Junction.html)s by affecting their values. Numerically adding to a [Junction
](https://docs.raku.org/type/Junction.html) adds to every value in it:
你可以通过影响其值来更改 [Junction
](https://docs.raku.org/type/Junction.html)。以数字方式添加到 [Junction
](https://docs.raku.org/type/Junction.html) 会增加其中的每个值:
my $junction = any( 1, 3, 7 );
$junction += 1;
if $junction %% 2 {
put "{$junction.gist} is even";
}
The output shows that you were able to add one to each of the values:
输出显示你可以为每个值加一:
any(2, 4, 8) is even
This generally applies to all of the operations, and you can get quite creative with that. What if you add two any
[Junction
](https://docs.raku.org/type/Junction.html)s? Think about this for a minute before you read ahead to the output:
这通常适用于所有操作,你可以从中获得相当的创意。如果你将两个 any
[Junction
](https://docs.raku.org/type/Junction.html) 相加会怎样?在继续读到输出之前,请考虑一分钟:
my $any-any = any( 6, 7 ) + any( 9, 11 )
put "Result is $any-any";
Now figure out what this means:
现在弄清楚这意味着什么:
Result is any(any(15, 17), any(16, 18))
That’s an any
of any`s! Suppose you wanted to check if that value was less than `17
. This virtual series of steps finds the answer:
这是一个里面含有 any
的 any
。假设你要检查该值是否小于 17
。此虚拟系列步骤可找到答案:
$any-any < 17
any( any(15, 17), any(16, 18) ) < 17
any( any(15, 17) < 17, any(16, 18) < 17 )
any( any(15 < 17, 17 < 17), any(16 < 17, 18 < 17) )
any( any(True,False), any(True, False) )
any( True, True )
True
This has the same effect as any( 15, 16, 17, 18 )
but with more steps involved. That’s a warning. If you aren’t careful you could have an explosion of [Junction
](https://docs.raku.org/type/Junction.html)s in there.
这与任何 any( 15, 16, 17, 18 )
具有相同的效果,但涉及更多步骤。这是一个警告。如果你不小心,你可能会爆发 [Junction
](https://docs.raku.org/type/Junction.html)。
4.45.2. all
An all
[Junction
](https://docs.raku.org/type/Junction.html) requires that each of its values satisfy the condition or method you apply:
all
[Junction
](https://docs.raku.org/type/Junction.html) 要求其每个值满足你应用的条件或方法:
my $all-of-u = all( <Danaus Bicyclus Amauris> );
if $all-of-u.contains: 'u' {
put "Everyone has a u";
}
Perhaps you want to check that all of the values are a particular type. In this example there’s a [Str
](https://docs.raku.org/type/Str.html) in @mixed-types
:
也许你想要检查所有值是否为特定类型。在这个例子中,@mixed-types
中有一个 [Str
](https://docs.raku.org/type/Str.html):
my @mixed-types = <1 2/3 4+8i Hello>;
if all(@mixed-types) ~~ Numeric {
put "Every value is a numeric thingy";
}
else {
put "One of these things is not like the others";
}
The Hello
cannot become a number, so a smart match against [Numeric
](https://docs.raku.org/type/Numeric.html) fails. The entire [Junction
](https://docs.raku.org/type/Junction.html) evaluates to False
because one of its values does.
Hello
无法成为数字,因此与 [Numeric
](https://docs.raku.org/type/Numeric.html) 的智能匹配失败。整个 [Junction
](https://docs.raku.org/type/Junction.html) 的计算结果为 False
,因为其值之一是 False
。
The all
is much easier to read than almost anything else that might accomplish the task. Comparing the result of a .grep
to the original number of elements in the source is too much typing:
all
都比其他任何可能完成任务的东西更容易阅读。将 .grep
的结果与源中元素的原始数量进行比较打字太多了:
if @mixed-types.grep( * !~~ Numeric ) == +@mixed-types {
put "One of these things is not a number";
}
You can create an all
[Junction
](https://docs.raku.org/type/Junction.html) with the &
:
你可以使用 &
创建一个 all
[Junction
](https://docs.raku.org/type/Junction.html):
my $all-of-u = 'Danaus' & 'Bicyclus' & 'Amauris';
if $all-of-u.contains: 'u' {
put "Everyone has a u";
}
EXERCISE 14.2Using all
, test if all the numbers you specify on the command line are prime.
练习14.2使用 all
,测试你在命令行中指定的所有数字是否为素数。
4.45.3. one
The one
[Junction
](https://docs.raku.org/type/Junction.html) allows only one of its values to satisfy its condition. If more than one would make the condition True
the [Junction
](https://docs.raku.org/type/Junction.html) fails:
`one` [`Junction`](https://docs.raku.org/type/Junction.html) 只允许其中一个值满足其条件。如果不止一个条件为 `True`,则 [`Junction`](https://docs.raku.org/type/Junction.html) 失败:
put one( 1, 2, 3 ) %% 2 ?? # True
"Exactly one is even"
!!
"More (or less) than one is even";
If more than one thing in the one
is True
, then the entire [Junction
](https://docs.raku.org/type/Junction.html) is False
:
如果 one
中的多个是 True
,则整个 [Junction
](https://docs.raku.org/type/Junction.html) 为 False
:
one( True, True, False ).so # False;
You can create a one
[Junction
](https://docs.raku.org/type/Junction.html) with the ^
:
你可以使用 ^
创建 one
[Junction
](https://docs.raku.org/type/Junction.html):
put ( 1 ^ 2 ^ 3 ) %% 2 ?? # True
"Exactly one is even"
!!
"More (or less) than one is even";
4.45.4. none
The none
[Junction
](https://docs.raku.org/type/Junction.html) requires that all of the values cause its condition to be False
. That means that everything should evaluate to False
. There’s no symbolic operator version for this type:
none
[Junction
](https://docs.raku.org/type/Junction.html) 要求所有值都导致其条件为 False
。这意味着一切都应该计算为 False
。该类型没有符号运算符版本:
put none( 1, 2, 3 ) %% 5 ?? True
"Exactly one is even"
!!
"More (or less) than one is even";
EXERCISE 14.3Use none
to test if no numbers you specify on the command line are prime. Once you’ve done that, use none`to test that some numbers in an [`Array
](https://docs.raku.org/type/Array.html) are prime.
练习14.3如果你在命令行中指定的数字不是素数,则使用 none
来测试。完成后,使用 none
测试,数组中的某些数字是素数。
4.45.5. Some Junctive Tricks
[Junction
](https://docs.raku.org/type/Junction.html)s aren’t designed for introspection and you aren’t supposed to care if the value is in a [Junction
](https://docs.raku.org/type/Junction.html). This isn’t too hard to work around, though.
[Junction
](https://docs.raku.org/type/Junction.html) 不是为内省而设计的,你不应该关心这个值是否在一个 [Junction
](https://docs.raku.org/type/Junction.html) 中。不过,这并不难解决。
You can apply an operation to each value with a hyperoperator ([Chapter 6](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch06.html#camelia-lists)). This one adds one to each element:
你可以使用超运算符对每个值应用操作(第6章)。这个为每个元素加一:
my $junction = any( 1, -3, 7 );
say $junction »+« 1;
The new [Junction
](https://docs.raku.org/type/Junction.html) has new values. You still aren’t supposed to know what these new values are, but something must know what they are to add one to them:
新的 [Junction
](https://docs.raku.org/type/Junction.html) 有新的值。你仍然不应该知道这些新值是什么,但肯定知道它们加一之后是什么:
any(2, -2, 8)
The »+«
surrounds the +
because that’s an infix operator and expects arguments on either side of it. You can call a method (a postfix thing) on each item:
»+«
包围着 +
,因为这是一个中缀运算符,并期望它的任何一边都有参数。你可以在每一项上调用一个方法(一个后缀的东西):
$junction>>.is-prime; # any((True), (False), (False))
That method could be .take
, which adds the value to the list that gather
makes. This means that the values can escape the [Junction
](https://docs.raku.org/type/Junction.html):
该方法可以是 .take
,它将值添加到 gather
制作的列表中。这意味着值可以避开 [Junction
](https://docs.raku.org/type/Junction.html):
my $junction = any( 1, -3, 7 );
my @values = gather $junction».take;
put "Values are @values[]";
Don’t make a habit of this because it’s slightly naughty. You aren’t supposed to know how to do this.
不要养成这个习惯,因为它有点顽皮。你不应该知道如何做到这一点。
A [Junction
](https://docs.raku.org/type/Junction.html) is handy to allow a combination of types in a type constraint. Use the subset
of the [Junction
](https://docs.raku.org/type/Junction.html)of both types as the constraint:
[Junction
](https://docs.raku.org/type/Junction.html) 可以方便地在类型约束中组合类型。使用两种类型的 [Junction
](https://docs.raku.org/type/Junction.html) 的 subset
子集作为约束:
subset IntInf where Int | Inf;
sub add ( IntInf $a, IntInf $b ) { $a + $b }
put add( 1, 3 ); # 4
put add( 1, Inf ); # Inf
EXERCISE 14.4Rewrite the number-guessing game from [Chapter 2](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch02.html#camelia-guessing) to have three secret numbers. This time the hints are a bit trickier. If any of the secret numbers are smaller than the guess, tell the person that one or some of them are smaller. Do the same with larger numbers. For a single guess, some numbers may be larger and others smaller. When the person has guessed all of the secret numbers, end the game. Is it easier to use given-when
or if
? Using all of the [Junction
](https://docs.raku.org/type/Junction.html) types may make this easier.
练习14.4 重写第2章的猜数游戏,得到三个秘密数字。这次提示有点棘手。如果任何一个秘密数字小于猜测的值,请告诉该人其中一个或一些较小。用更大的数字做同样的事情。对于单个猜测,一些数字可能更大而其他数字更小。当该人猜出所有秘密数字时,结束游戏。是否更容易使用 - given-when
或 if
?使用所有 [Junction
](https://docs.raku.org/type/Junction.html) 类型可以使这更容易。
[Table 14-1](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch14.html#camelia-junctions-TABLE-junctions) provides a summary of [Junction
](https://docs.raku.org/type/Junction.html)s.
| Junction | Operator | Description |
| -------- | -------- | ------------------------------------ |
| any
| \| | Any of the values will work. |
| all
| & | All of the values must work. |
| one
| ^ | Exactly one of the values will work. |
| none
| | None of the values can work. |
4.46. Sets
[Set
](https://docs.raku.org/type/Set.html)s are another way to combine values. They aren’t like [Junction
](https://docs.raku.org/type/Junction.html)s, where several values can act like one value; they combine zero or more values as its own thingy that you can inspect. Each value can be in the [Set
](https://docs.raku.org/type/Set.html)only once (although there are weighted [Set
](https://docs.raku.org/type/Set.html)s I won’t write about), and once created the [Set
](https://docs.raku.org/type/Set.html) is fixed.
[Set
](https://docs.raku.org/type/Set.html) 是另一种组合值的方法。它们不像 [Junction
](https://docs.raku.org/type/Junction.html),其中几个值可以像一个值一样;它们将零个或多个值组合为你可以检查的自己的东西。每个值都只可以在 [Set
](https://docs.raku.org/type/Set.html) 中一次(虽然有加权集合我不会写),一旦创建了集合就固定了。
A [Set ](https://docs.raku.org/type/Set.html) is a type of [Associative ](https://docs.raku.org/type/Associative.html), so many of the things you already know about those work on [Set ](https://docs.raku.org/type/Set.html)s.
|
集合是一种[关联](集合(https://docs.raku.org/type/Set.html)的内容。
You can create a [Set
](https://docs.raku.org/type/Set.html) with a routine or a coercer. Each thingy in the [List
](https://docs.raku.org/type/List.html) is a member of the [Set
](https://docs.raku.org/type/Set.html). These are the same:
你可以使用例程或强制器创建[集合
](https://docs.raku.org/type/Set.html)。 [列表
](https://docs.raku.org/type/List.html)中的每个东西都是 [集合
](https://docs.raku.org/type/Set.html)成员。这些都是一样的:
set( 1, 2, 3 )
(1, 2, 3).Set
You can store any combination or mixture of thingys, including type objects:
你可以存储任何组合或混合物,包括类型对象:
set( <♠ ♣ ♥ ♦> )
set( Int, 3, Inf, 'Hamadryas', $(1,2,3) )
A [Set
](https://docs.raku.org/type/Set.html) stores a thingy only once. It’s either in the [Set
](https://docs.raku.org/type/Set.html) or it isn’t, so it doesn’t need duplicates:
put set( 1, 2, 3 ).elems; # 3
put set( 1, 2, 2, 3, 3, 3 ).elems; # 3
You can check that a value is in the [Set
](https://docs.raku.org/type/Set.html) with the (elem)
operator:
你可以使用`(elem)运算符检查[`集合
](https://docs.raku.org/type/Set.html)中的值:
my $set = <♠ ♣ ♥ ♦>.Set;
put 'Number is in the set' if '♥' (elem) $set;
There’s also the fancy Unicode ∈
operator that tests if the thingy is in the set (or is a “member” of the set):
还有花哨的 Unicode ∈
运算符,它测试东西是否在集合中(或者是集合的“成员”):
put 'Number is in the set' if '♥' ∈ $set;
These operators know that they need [Set
](https://docs.raku.org/type/Set.html)s, so they coerce what you give them:
这些运算符知道他们需要[集合
](https://docs.raku.org/type/Set.html),所以他们强迫你给他们的东西:
put 'Number is in the set' if '♥' ∈ <♠ ♣ ♥ ♦>;
With that operator the [Set
](https://docs.raku.org/type/Set.html) is the second operand. The order of operands is reversed for the (cont)
and ∋
operators. Now you test that a [Set
](https://docs.raku.org/type/Set.html) contains an element:
使用该运算符,[集合
](https://docs.raku.org/type/Set.html)是第二个操作数。对于`(cont)和 `∋
运算符,操作数的顺序是相反的。现在,你测试一个[集合
](https://docs.raku.org/type/Set.html)包含一个元素:
put 'Number is in the set' if $set (cont) '♥';
put 'Number is in the set' if $set ∋ '♥';
You can test that a thingy is not a member of a [Set
](https://docs.raku.org/type/Set.html) by either prefacing the ASCII operator with a !
or using the Unicode version with the line through it:
你可以通过在 ASCII 操作符前面添加一个 !
来测试一个东西不是一个[集合
](https://docs.raku.org/type/Set.html)的成员!或者使用带有直线穿过的Unicode 版本:
put 'Number is not in the set' if '♥' !(elem) $set;
put 'Number is not in the set' if '♥' ∉ $set;
put 'Number is not in the set' if $set !(cont) '♥';
put 'Number is not in the set' if $set ∌ '♥';
You can compare [Set
](https://docs.raku.org/type/Set.html)s to other [Set
](https://docs.raku.org/type/Set.html)s. Another [Set
](https://docs.raku.org/type/Set.html) that contains only some of the members is a subset. A “strict” or “proper” subset is one that is smaller than the [Set
](https://docs.raku.org/type/Set.html) and only contains elements of the [Set
](https://docs.raku.org/type/Set.html). Another way to say that is a proper subset is always smaller. The (<)
(or ⊂
) operator does that with the opening of the angle toward the larger [Set
](https://docs.raku.org/type/Set.html). The order of elements does not matter:
你可以将[集合
](集合
(集合
(https://docs.raku.org/type/Set.html)是子集。 “严格”或“适当”子集是小于[集合
](集合
(https://docs.raku.org/type/Set.html)的元素。另一种说法是适当的子集总是更小。 (<)
(或 ⊂
)运算符通过朝向较大的[集合
](https://docs.raku.org/type/Set.html)开放角度来执行此操作。元素的顺序无关紧要:
set( 1, 3 ) (<) set( 1, 3, 7 ); # True
set( 3, 1, 7 ) (<) set( 1, 3 ); # False (not smaller)
set( 5, 7 ) ⊂ set( 1, 3, 7 ); # False (5 not in set)
A !
in front of the ASCII operator or a line through the Unicode operator negates the condition:
ASCII 运算符前面的 !
或通过带有直线穿过的 Unicode 运算符否定条件:
set( 1, 3 ) !(<) set( 1, 3, 7 ); # False
set( 3, 1, 7 ) !(<) set( 1, 3 ); # True
set( 5, 7 ) ⊄ set( 1, 3, 7 ); # True
Use the (>=)
or ⊆
operators if you want to allow the [Set
](https://docs.raku.org/type/Set.html)s to be the same size:
如果要允许[集合
](https://docs.raku.org/type/Set.html)具有相同的大小,请使用((>=)
或 ⊆
运算符:
set( 1, 3 ) (<=) set( 1, 3, 7 ); # True
set( 1, 3, 7 ) (<=) set( 1, 3, 7 ); # True
set( 3, 1, 7 ) ⊆ set( 1, 3 ); # False (subset has 7)
Negate those in the same way:
以同样的方式否定这些:
set( 1, 3 ) !(<=) set( 1, 3, 7 ); # False
set( 1, 3, 7 ) !(<=) set( 1, 3, 7 ); # False
set( 3, 1, 7 ) ⊈ set( 1, 3 ); # True
You can also have supersets. That’s just a matter of which one you allow to be the larger [Set
](https://docs.raku.org/type/Set.html). So far you’ve seen examples where you expected the larger [Set
](https://docs.raku.org/type/Set.html) to be to the right of the operator. Flip those operators around so you expect the larger [Set
](https://docs.raku.org/type/Set.html) to be on the left:
你也可以有超集。这只是一个你允许成为更大的[集合
](集合
(集合
(https://docs.raku.org/type/Set.html)位于左侧:
set( 3, 1, 7 ) (>) set( 1, 3 ); # False (not smaller)
set( 3, 1, 7 ) ⊃ set( 1, 3 ); # False (not smaller)
set( 3, 1, 7 ) !(>) set( 1, 3 ); # True
set( 3, 1, 7 ) ⊅ set( 1, 3 ); # True
[Table 14-2](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch14.html#camelia-junctions-TABLE-set-operations) shows the rest of the [Set
](https://docs.raku.org/type/Set.html) operations.
| Operation | Operator | Code number | Description |
| ---------------------- | -------- | ----------- | --------------------------------------------------------- |
| $a (elem) $set
| ∈ | U+2208 | $a
is a member of $set
|
| $a !(elem) $set
| ∉ | U+2209 | $a
is not a member of $set
|
| $set (cont) $a
| ∋ | U+220B | $set
contains $a
|
| $set !(cont) $a
| ∌ | U+220C | $set
does not contain $a
|
| $set-a (<) $set-b
| ⊂ | U+2282 | $set-a
is a proper subset of $set-b
|
| $set-a !(<) $set-b!
| ⊄ | U+2284 | $set-a
is not a proper subset of $set-b
|
| $set-a (⇐) $set-b!
| ⊆ | U+2286 | $set-a
is the same or is a subset of $set-b
|
| $set-a !(⇐) $set-b!
| ⊈ | U+2288 | $set-a
is not the same and isn’t a subset of $set-b
|
| $set-a (>) $set-b!
| ⊃ | U+2283 | $set-a
is a proper superset of $set-b
|
| $set-a !(>) $set-b!
| ⊅ | U+2285 | $set-a
is not a proper superset of $set-b
|
| $set-a (>=) $set-b!
| ⊇ | U+2287 | $set-a
is the same or is a superset of $set-b
|
| $set-a !(>=) $set-b!
| ⊉ | U+2289 | $set-a
is not the same and isn’t a superset of $set-b
|
EXERCISE 14.5In [Chapter 9](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch09.html#camelia-hashes) you used a [Map
](https://docs.raku.org/type/Map.html) to check allowed values. Do the same thing with a [List
](https://docs.raku.org/type/List.html). Prompt for some starting colors (perhaps all on one line that you break up into elements). Continue to prompt for colors and report if the color was one of the initial colors. Can you do this ignoring case?
练习14.5 在第9章中,你使用了 [Map
](https://docs.raku.org/type/Map.html) 来检查允许的值。用[列表
](https://docs.raku.org/type/List.html)做同样的事情。提示一些起始颜色(可能在一行中分解为元素)。继续提示颜色并报告颜色是否为初始颜色之一。你能忽略这个案子吗?
4.46.1. Set Operations
You can operate on two [Set
](https://docs.raku.org/type/Set.html)s to create new [Set
](https://docs.raku.org/type/Set.html)s. A union is a combination of two [Set
](https://docs.raku.org/type/Set.html)s. Each element still shows up only once:
你可以操作两个[集合
](集合
(集合
(https://docs.raku.org/type/Set.html)的组合。每个元素仍然只显示一次:
set(1,2) (|) set(3,7); # set(1 2 3 7)
set(1,2) ∪ set(3,7); # set(1 2 3 7)
The intersection makes the [Set
](https://docs.raku.org/type/Set.html) of the elements they have in common:
交集制作一个它们共有的元素[集合
](https://docs.raku.org/type/Set.html):
set(1,3) (&) set(3,7); # set(3)
set(1,2) ∩ set(3,7); # set()
The set difference creates a [Set
](https://docs.raku.org/type/Set.html) made up of the elements from the first [Set
](https://docs.raku.org/type/Set.html) that aren’t in the second. The `∖`isn’t the ASCII backslash; it’s (U+2216 SET MINUS):
差集创建一个由第一个[集合
](集合
(集合
(https://docs.raku.org/type/Set.html)。 ∖
不是 ASCII 反斜杠; 它是(U+2216 SET MINUS):
set( <a b> ) (-) set( <b c> ); # set(a)
set( <A b> ) ∖ set( <x y> ); # set(A b)
The symmetric set difference does a similar thing in both directions. It creates a [Set
](https://docs.raku.org/type/Set.html) containing all the elements of either [Set
](https://docs.raku.org/type/Set.html) that don’t show up in the other:
对称差集在两个方向上做类似的事情。它创建一个[集合
](集合
(集合
(https://docs.raku.org/type/Set.html)中的所有元素:
set( <a b> ) (^) set( <b c> ); # set(a c)
set( <A b> ) ⊖ set( <x y> ); # set(A b x y)
These operations are summarized in [Table 14-3](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch14.html#camelia-junctions-TABLE-set-creators).
| Operation | Operator | Code number | Description | | --------- | -------- | ----------- | ------------------------ | | (\|) | ∪ | U+222A | Union (combination) | | (&) | ∩ | U+2229 | Intersection (overlap) | | (-) | ∖ | U+2216 | Set difference | | (^) | ⊖ | U+2296 | Symmetric set difference |
EXERCISE 14.6Create two [Set
](https://docs.raku.org/type/Set.html)s of 10 numbers between 1 and 50. Find their intersection and union.
练习14.6在1到50之间创建两组10个数字。找到它们的交集和联合。
4.47. Summary
[Junction
](https://docs.raku.org/type/Junction.html)s make several values pretend to be a single value, in such a way that you can’t tell which value it is or how many values there are. You create the [Junction
](https://docs.raku.org/type/Junction.html) in a way that makes the values all work together or separately. A [Set
](https://docs.raku.org/type/Set.html) also combines values but lets you look inside to see what those values are. You can combine [Set
](https://docs.raku.org/type/Set.html)s in various ways to create new ones. This is handy to tell what’s in, out, or common.
[Junction
](https://docs.raku.org/type/Junction.html)使多个值假装成单个值,这样你就无法判断它是哪个值或者有多少个值。你可以通过使值全部一起或单独工作的方式创建 [Junction
](https://docs.raku.org/type/Junction.html)。 [集合
](集合
(集合
(https://docs.raku.org/type/Set.html)。这可以很方便地分辨出进出的内容。
== 正则表达式
Regular expressions (or regexes) are patterns that describe a possible set of matching texts. They are a little language of their own, and many characters have a special meaning inside patterns. They may look cryptic at first, but after you learn them you have quite a bit of power.
Forget what you’ve seen about patterns in other languages. The Raku pattern syntax started over. It’s less compact but also more powerful. In some cases it acts a bit differently.
This chapter shows simple patterns that match particular characters or sets of characters. It’s just the start. In [Chapter 16](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch16.html#camelia-regex2) you’ll see fancier patterns and the side effects of matching. In [Chapter 17](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch17.html#camelia-grammars) you’ll take it all to the next level.
正则表达式(或正则表达式)是描述可能的匹配文本集的模式。它们是自己的一种语言,许多字符在模式中具有特殊含义。它们起初可能看起来很神秘,但是在你学习它们之后你会有相当大的力量。
忘记你在其他语言中看到的关于模式的内容。 Raku模式语法重新开始。它不那么紧凑,但也更强大。在某些情况下,它的作用有点不同。
本章介绍与特定字符或字符集匹配的简单模式。这只是一个开始。在第16章中,你将看到更漂亮的模式和匹配的副作用。在第17章中,你将把它全部提升到一个新的水平。
4.48. The Match Operator
A pattern describes a set of text values. The simple pattern abc
describes all the values that have an a
next to a b
next to a c
. The trick then is to decide if a particular value is in the set of matching values. There are no half or partial matches; it matches or it doesn’t.
A pattern inside m/…/
immediately applies itself to the value in $_
. If the pattern is in the [Str
](https://docs.raku.org/type/Str.html) the match operator returns something that evaluates to True
in a condition:
模式描述了一组文本值。简单模式abc描述了c旁边的b旁边的所有值。然后,技巧是确定特定值是否在匹配值集合中。没有半场比赛或部分比赛;它匹配或不匹配。
m /…/中的模式立即将其自身应用于$ _中的值。如果模式在Str中,则匹配运算符返回在条件中评估为True的值:
$_ = 'Hamadryas';
if m/Hama/ { put 'It matched!'; }
else { put 'It missed!'; }
That’s a bit verbose. The conditional operator takes care of that:
这有点冗长。条件运算符负责:
put m/Hama/ ?? 'It matched!' !! 'It missed!';
You don’t have to match against $_
. You can use the smart match to apply it to a different value. That’s the target:
你不必匹配$ _。你可以使用智能匹配将其应用于其他值。这是目标:
my $genus = 'Hamadryas';
put $genus ~~ m/Hama/ ?? 'It matched!' !! 'It missed!';
That target could be anything, including an [Array
](https://docs.raku.org/type/Array.html) or [Hash
](https://docs.raku.org/type/Hash.html). These match a single item:
该目标可以是任何东西,包括数组或哈希。这些匹配单个项目:
$genus ~~ m/Hama/;
@animals[0] ~~ m/Hama/;
%butterfly<Hamadryas> ~~ m/perlicus/;
But you can also match against multiple items. The object on the left side of the smart match decides how the pattern applies to the object. This matches if any of the elements in @animals
matches:
但你也可以匹配多个项目。智能匹配左侧的对象决定模式如何应用于对象。如果@animals中的任何元素匹配,则匹配:
if @animals ~~ m/Hama/ {
put "Matches at least one animal";
}
This is the same as matching against a [Junction
](https://docs.raku.org/type/Junction.html):
这与针对Junction的匹配相同:
if any(@animals) ~~ m/Hama/ {
put "Matches at least one animal";
}
The match operator is commonly used in the condition inside a .grep
:
匹配运算符通常用于.grep中的条件:
my @hama-animals = @animals.grep: /Hama/;
4.48.1. Match Operator Syntax
The match operator can use alternate delimiters, similar to the quoting mechanism:
匹配运算符可以使用备用分隔符,类似于引用机制:
m{Hama}
m!Hama!
Whitespace inside the match operator doesn’t matter. It’s not part of the pattern (until you say so, as you’ll see later). All of these are the same, including the last example with vertical whitespace:
匹配运算符内的空格并不重要。它不是模式的一部分(直到你这么说,你将在后面看到)。所有这些都是相同的,包括最后一个带有垂直空格的例子:
m/ Hama /
m{ Hama }
m! Hama !
m/
Hama
/
You can put spaces between alphabetic characters, but you’ll probably get a warning because Raku wants you to put those together:
你可以在字母字符之间放置空格,但你可能会收到警告,因为Raku希望你将它们放在一起:
m/ Ha ma /
If you want a literal space inside the match operator you can escape it (along with other things you’ll see later):
如果你想在匹配运算符中使用文字空间,你可以将其转义(以及稍后你会看到的其他内容):
m/ Ha\ ma /
Quoting whitespace makes it literal too (the space around the quoted whitespace is still insignificant), or you can quote it all together:
引用空格也使它成为字面值(引用的空格周围的空间仍然无关紧要),或者你可以将它们全部引用:
m/ Ha ' ' ma /
m/ 'Ha ma' /
You need to quote or escape any character that’s not alphabetic or a number, even if those characters aren’t “special.” The other unquoted characters may be metacharacters that have special meaning in the pattern language.
你需要引用或转义任何非字母或数字的字符,即使这些字符不是“特殊”。其他未加引号的字符可能是在模式语言中具有特殊含义的元字符。
4.48.2. Successful Matches
If the match operator succeeds it returns a [Match
](https://docs.raku.org/type/Match.html) object, which is always a True
value. If you put
that object it shows you the part of the [Str
](https://docs.raku.org/type/Str.html) that matched. The say
calls .gist
and the output is a bit different:
如果匹配运算符成功,则返回Match对象,该对象始终为True值。如果你放置该对象,它会向你显示匹配的Str部分。说调用.gist和输出有点不同:
$_ = 'Hamadryas';
my $match = m/Hama/;
put $match; # Hama
say $match; # ?Hama?
The output of say
gets interesting as the patterns get more complicated. That makes it useful for the regex chapters, and you’ll see more of that here compared to the rest of the book.
If the match does not succeed it returns [Nil
](https://docs.raku.org/type/Nil.html), which is always False
:
随着模式变得更加复杂,say的输出变得有趣。这使得它对正则表达式章节很有用,并且与本书的其余部分相比,你将在这里看到更多。
如果匹配不成功,则返回Nil,它始终为False:
$_ = 'Hamadryas';
my $match = m/Hama/;
put $match.^name; # Nil
It’s usually a good idea to check the result before you do anything with it:
在对它做任何事情之前检查结果通常是个好主意:
if my $match = m/Hama/ { # matched
say $match;
}
You don’t need the $match
variable though. The result of the last match shows up in the special variable $/
, which you’ll see more of later:
你不需要 $match
变量。最后一个匹配的结果显示在特殊变量$ /中,稍后你会看到更多:
if m/Hama/ { # matched
say $/;
}
4.48.3. Defining a Pattern
Useful patterns can get quite long and unwieldy. Use rx//
to define a pattern (a [Regex
](https://docs.raku.org/type/Regex.html)) for later use. This pattern is not immediately applied to any target. This allows you to define a pattern somewhere that doesn’t distract from what you are doing:
有用的模式可能会变得非常冗长和笨拙。使用rx //定义模式(正则表达式)供以后使用。此模式不会立即应用于任何目标。这允许你在某个地方定义一个不会分散你正在做的事情的模式:
my $genus = 'Hamadryas';
my $pattern = rx/ Hama /; # something much more complicated
$genus ~~ $pattern;
and reuse the pattern wherever you need it:
并在任何需要的地方重用模式:
for lines() -> $line {
put $line if $line ~~ $pattern;
}
It’s possible to combine saved patterns into a larger one. This allows you to decompose complicated patterns into smaller, more tractable ones that you can reuse later (which you’ll do extensively in [Chapter 17](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch17.html#camelia-grammars)):
可以将保存的模式组合成更大的模式。这允许你将复杂的模式分解为更小,更易处理的模式,以后可以重复使用(你将在第17章中进行广泛的讨论):
my $genus = 'Hamadryas';
my $hama = rx/Hama/;
my $dryas = rx/dryas/;
my $match = $genus ~~ m/$hama$dryas/;
say $match;
Rather than storing a variable in an object, declare a lexical pattern with regex
. This looks like a subroutine because it has a [Block
](https://docs.raku.org/type/Block.html) but it’s not code inside; it’s a pattern and uses that slang:
不是将变量存储在对象中,而是使用正则表达式声明词法模式。这看起来像一个子程序,因为它有一个Block,但它不是代码;这是一种模式并使用俚语:
my regex hama { Hama }
Use this in a pattern by surrounding it with angle brackets:
通过用尖括号包围它,在图案中使用它:
my $genus = 'Hamadryas';
put $genus ~~ m/<hama>/ ?? 'It matched!' !! 'It missed!';
You can define multiple named regexes and use them together:
你可以定义多个已命名的正则表达式并将它们一起使用:
my regex hama { Hama }
my regex dryas { dryas }
$_ = 'Hamadryas';
say m/<hama><dryas>/;
Each named regex becomes a submatch. You can see the structure when you output it with say
. It shows the overall result and the results of the subpatterns too:
每个命名的正则表达式都成为一个子匹配。用say输出它时可以看到结构。它还显示了整个结果和子模式的结果:
?Hamadryas?
hama => ?Hama?
dryas => ?dryas?
Treat the [Match
](https://docs.raku.org/type/Match.html) object like a [Hash
](https://docs.raku.org/type/Hash.html) (although it isn’t) to get the parts that matched the named regexes. The name of the regex is the “key”:
将Match对象视为Hash(尽管不是),以获得与命名正则表达式匹配的部分。正则表达式的名称是“关键”:
$_ = 'Hamadryas';
my $result = m/<hama><dryas>/;
if $result {
put "First: $result<hama>";
put "Second: $result<dryas>";
}
4.48.4. Predefined Patterns
[Table 15-1](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch15.html#camelia-regex1-TABLE-predefined_patterns) shows several of the predefined patterns that are ready for you to use. You can define your patterns in a library and export them just like you could with subroutines:
# Patterns.pm6
my regex hama is export { Hama }
Load the module and those named regexes are available to your patterns:
加载模块和那些名为正则表达式的模式可用:
use lib <.>;
use Hama;
$_ = 'Hamadryas';
say m/ <hama> /;
| Predefined pattern | What it matches |
| ------------------ | ------------------------------------------------------------ |
| <alnum>
| Alphabetic and digit characters |
| <alpha>
| Alphabetic characters |
| <ascii>
| Any ASCII character |
| <blank>
| Horizontal whitespace |
| <cntrl>
| Control characters |
| <digit>
| Decimal digits |
| <graph>
| <alnum>
+ <punct>
|
| <ident>
| A valid identifier character |
| <lower>
| Lowercase characters |
| <print>
| <graph>
+ <space>
, but without <cntrl>
|
| <punct>
| Punctuation and symbols beyond ASCII |
| <space>
| Whitespace |
| <upper>
| Uppercase characters |
| <|wb>
| Word boundary (an assertion rather than a character) |
| <word>
| <alnum>
+ Unicode marks + connectors, like ‘_’ (extra) |
| <ws>
| Whitespace (required between word characters, optional otherwise) |
| <ww>
| Within a word (an assertion rather than a character) |
| <xdigit>
| Hexadecimal digits [0-9A-Fa-f]
|
EXERCISE 15.1Create a program that uses a regular expression to output all of the matching lines from the files you specify on the command line.
练习15.1创建一个程序,该程序使用正则表达式输出你在命令行中指定的文件中的所有匹配行。
4.49. Matching Nonliteral Characters
You don’t have to literally type a character to match it. You might have an easier time specifying its code point or name. You can use the same \x[CODEPOINT]
or \c[NAME]
that you saw in double-quoted [Str
](https://docs.raku.org/type/Str.html)s in [Chapter 4](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch04.html#camelia-strings).
If you specify a name it must be all uppercase.
You could match the initial capital H by name, even though you have to type a literal H
in the name:
你不必逐字输入匹配它的字符。你可以更轻松地指定其代码点或名称。你可以使用在第4章中双引号Strs中看到的相同\ x [* CODEPOINT ]或\ c [ NAME *]。
如果指定名称,则必须全部为大写。
你可以按名称匹配初始大写字母H,即使你必须在名称中键入文字H:
my $pattern = rx/
\c[LATIN CAPITAL LETTER H] ama
/;
$_ = "Hamadryas";
put $pattern ?? 'Matched!' !! 'Missed!';
You can do the same thing with the code point. If you specify a code point use the hexadecimal number (with either case):
你可以使用代码点执行相同的操作。如果指定代码点,请使用十六进制数字(两种情况):
my $pattern = rx/
\x[48] ama
/;
$_ = "Hamadryas";
put $pattern ?? 'Matched!' !! 'Missed!';
This makes more sense if you want to match a character that’s either hard to type or hard to read. If the [Str
](https://docs.raku.org/type/Str.html) has the 🐱 character (U+1F431 CAT FACE), you might not be able to distinguish that from 😸 (U+1F638 GRINNING CAT FACE WITH SMILING EYES) without looking very closely. Instead of letting another programmer mistake your intent, you can use the name to save some eyestrain:
my $pattern = rx/
\c[CAT FACE] # or \x[1F431]
/;
$_ = "This is a catface: 🐱";
put $pattern ?? 'Matched!' !! 'Missed!';
4.49.1. Matching Any Character
Patterns have metacharacters that match something other than their literal selves. Some of these are listed in [Table 15-2](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch15.html#camelia-regex1-TABLE-chars_to_escape) (and most you won’t see in this chapter). The .
matches any character (including a newline). This pattern matches any target that has at least one character:
模式具有与其文字自我匹配的元字符。其中一些列在表15-2中(大多数情况下,你不会在本章中看到)。这个。匹配任何字符(包括换行符)。此模式匹配具有至少一个字符的任何目标:
m/ . /
To match a [Str
](https://docs.raku.org/type/Str.html) with an a and a c separated by a character, put the dot between them in the pattern. This skips the lines that don’t match that pattern:
要将Str与由字符分隔的a和c匹配,请在模式中将它们放在它们之间。这会跳过与该模式不匹配的行:
for lines() {
next unless m/a.c/;
.put
}
ESCAPING CHARACTERS
Some characters have special meaning in patterns. The colon introduces an adverb and the #
starts a comment. To match those as literal characters you need to escape them. A backslash will do:
有些字符在模式中有特殊含义。冒号引入了一个副词,#开始发表评论。要将它们作为文字字符进行匹配,你需要将它们转义。反斜杠可以:
my $pattern = rx/ \# \: Hama \. /
This means to match a literal backslash, you need to escape that too:
这意味着匹配文字反斜杠,你也需要逃避它:
my $pattern = rx/ \# \: Hama \\ /
You can do the same thing with the other pattern metacharacters. To match a literal dot, escape it:
你可以使用其他模式元字符执行相同的操作。要匹配文字点,请将其转义:
my $pattern = rx/ \. /
The backslash only escapes the character that comes immediately after it. You can’t escape a literal space character, and you can’t escape a character that isn’t special. [Table 15-2](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch15.html#camelia-regex1-TABLE-chars_to_escape) shows what you need to escape, even though I haven’t shown you most of those features yet.
反斜杠只会逃避紧随其后的字符。你无法转义文字空格字符,也无法转义不特殊的字符。表15-2显示了你需要逃脱的内容,即使我还没有向你展示大部分功能。
| Metacharacter | Why it’s special |
| --------------------------- | ------------------------------------------ |
| #
| Starts a comment |
| \
| Escapes the next character or a shortcut |
| .
| Matches any character |
| :
| Starts an adverb, or prevents backtracking |
| (
and )
| Starts a capture |
| <
and >
| Used to create higher-level thingys |
| [
, ]
, and '
| Used for grouping |
| +
, |
, &
, -
, and ^
| Set operations |
| ?
, *
, +
, and %
| Quantifiers |
| |
| Alternation |
| ^
and $
| Anchors |
| $
| Starts a variable or named capture |
| =
| Assigns to named captures |
Characters inside quotes are always their literal selves:
引号内的字符总是它们的文字自我:
my $pattern = rx/ '#:Hama' \\ /
You can’t use the single quotes to escape the backslash since a single backslash will still try to escape the character that comes after it.
你不能使用单引号来转义反斜杠,因为单个反斜杠仍会尝试转义后面的字符。
MATCHING LITERAL SPACES
You have a tougher time if you want to match literal spaces. You can’t escape a space with \
because unspace isn’t allowed in a pattern. Instead, put quotes around the literal space:
如果你想匹配文字空间,你会有更艰难的时间。你无法使用\来转义空格,因为模式中不允许使用空格。相反,在文字空间周围加上引号:
my $pattern = rx/ Hamadryas ' ' laodamia /;
Or put the entire sequence in quotes:
或者将整个序列放在引号中:
my $pattern = rx/ 'Hamadryas laodamia' /;
Those single quotes can quickly obscure what belongs where; it can be helpful to spread the pattern across lines and note what you are trying to do:
那些单引号很快就会模糊属于哪里;将图案分布在线条上并记下你要做的事情会很有帮助:
my $pattern = rx/
Hamadryas # genus
' ' # literal space
laodamia # species
/;
You can make whitespace significant with the :s
adverb:
你可以使用:s副词使空白显着:
my $pattern = rx:s/ Hamadryas laodamia /;
my $pattern = rx/ :s Hamadryas laodamia /;
The :s
is the short form of :sigspace
:
:s
是 sigspace 的缩写形式:sigspace:
my $pattern = rx:sigspace/ Hamadryas laodamia /;
my $pattern = rx/ :sigspace Hamadryas laodamia /;
Notice that this will match Hamadryas laodamia
, even though the pattern has whitespace at the beginning and end. The :s
turns the whitespace in the pattern into a subrule <.ws>
:
请注意,这将匹配Hamadryas laodamia,即使该模式在开头和结尾都有空格。 :s将模式中的空格转换为子规则<.ws>:
$_ = 'Hamadryas laodamia';
my $pattern = rx/ Hamadryas <.ws> laodamia /;
if m/$pattern/ {
say $/; # ?Hamadryas laodamia?
}
You can combine adverbs, but they each get their own colon. Order does not matter. This pattern has significant whitespace and is case insensitive:
你可以结合副词,但每个副词都有自己的冒号。订单无关紧要。此模式具有重要的空白并且不区分大小写:
my $pattern = rx:s:i/ Hamadryas Laodamia /;
4.49.2. Matching Types of Characters
So far, you’ve matched literal characters. You typed out the characters you wanted, and escaped them in some cases. There are some sets of characters that are so common they get shortcuts. These start with a backslash followed by a letter that connotes the set of characters. [Table 15-3](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch15.html#camelia-regex1-TABLE-character_class_shortcuts) shows the list of shortcuts.
If you want to match any digit, you can use \d
. This matches anything that is a digit, not just the Arabic digits:
到目前为止,你已经匹配了字面字符。你键入了所需的字符,并在某些情况下将其转义。有一些字符组很常见,它们可以获得快捷方式。它们以反斜杠开头,后跟一个表示字符集的字母。表15-3显示了快捷方式列表。
如果要匹配任何数字,可以使用\ d。这匹配任何数字,而不仅仅是阿拉伯数字:
/ \d /
Each of these shortcuts comes with a complement. \D
matches any nondigit.
这些快捷方式中的每一个都有补充。 \ D匹配任何非数字。
| Shortcut | Characters that match |
| -------- | ------------------------------------------------ |
| \d
| Digits (Unicode property N
) |
| \D
| Anything that isn’t a digit |
| \w
| Word characters: letters, digits, or underscores |
| \W
| Anything that isn’t a word character |
| \s
| Any kind of whitespace |
| \S
| Anything that isn’t whitespace |
| \h
| Horizontal whitespace |
| \H
| Anything that isn’t horizontal whitespace |
| \v
| Vertical whitespace |
| \V
| Anything that isn’t vertical whitespace |
| \t
| A tab character (specifically, only U+0009) |
| \T
| Anything that isn’t a tab character |
| \n
| A newline or carriage return/newline pair |
| \N
| Anything that isn’t a newline |
EXERCISE 15.2Write a program that outputs only those lines of input that contain three decimal digits in a row. You wrote most of this program in the previous exercise.
练习15.2编写一个程序,只输出那些包含三行十进制数字的输入行。你在上一个练习中写了大部分这个程序。
UNICODE PROPERTIES
The Unicode Character Database (UCD) defines the code points and their names and assigns them one or more properties. Each character knows many things about itself, and you can use some of that information to match them. Place the name of the Unicode property in <:…>
. That colon must come right after the opening angle bracket. If you wanted to match something that is a letter, you could use the property Letter
:
Unicode字符数据库(UCD)定义代码点及其名称,并为它们分配一个或多个属性。每个角色都知道很多关于自身的事情,你可以使用其中的一些信息来匹配它们。将Unicode属性的名称放在<:…>中。结肠必须在开角支架后面。如果你想匹配一个字母的东西,你可以使用属性字母:
/ <:Letter> /
Instead of matching a property, you can match characters that don’t have that particular property. Put a !
in front of the property name to negate it. This matches characters that aren’t the title-case letters:
你可以匹配没有该特定属性的字符,而不是匹配属性。放一个!在属性名称前面否定它。这匹配不是标题大小写字母的字符:
/ <:!TitlecaseLetter> /
Each property has a long form, like Letter
, and a short form, in this case L
. There are other properties, such as Uppercase_Letter
and Lu
, or Number
and N
:
每个属性都有一个长格式,如Letter和短格式,在本例中为L.还有其他属性,如Uppercase_Letter和Lu,或Number和N:
/ <:L> /
/ <:N> /
You can match the characters that belong to certain Unicode blocks or scripts:
你可以匹配属于某些Unicode块或脚本的字符:
<:Block('Basic Latin')>
<:Script<Latin>>
Even though you can abbreviate these property names I’ll use the longer names in this book. See the documentation for the other properties.
即使你可以缩写这些属性名称,我也会在本书中使用较长的名称。请参阅其他属性的文档。
COMBINING PROPERTIES
One property might not be enough to describe what you want to match. To build fancier ones, combine them with character class set operators. These aren’t the same operators you saw in [Chapter 14](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch14.html#camelia-junctions); they’re special to character classes.
The +
creates the union of the two properties. Any character that has either property will match:
一个属性可能不足以描述你想要匹配的内容。要构建更高级的,将它们与字符类集合运算符组合。这些与第14章中看到的操作符不同;他们对角色课很特别。
+创建两个属性的并集。任何具有任何属性的字符都将匹配:
/ <:Letter + :Number> /
/ <:Open_Punctuation + :Close_Punctuation> /
Subtract one property from another with -
. Any character with the first property that doesn’t have the second property will match this. The following example matches all the identifier characters (in the UCD sense, not the Raku sense). There are the characters that can start an identifier and those that can be in the other positions:
用 - 减去另一个属性。具有第一个属性但没有第二个属性的任何字符都将与此匹配。以下示例匹配所有标识符字符(在UCD意义上,而不是Raku意义上)。可以启动标识符的字符和可以位于其他位置的字符:
/ <:ID_Continue - :Number> /
You can shorten this to not match a character without a particular property. It looks like you leave off the first part of the subtraction; the -
comes right after the opening angle bracket. That implies you’re subtracting from all characters. This matches all the characters that don’t have the Letter
property:
你可以将此缩短为与没有特定属性的角色不匹配。看起来你放弃了减法的第一部分; - 在打开角度支架后面。这意味着你要从所有角色中减去。这匹配所有没有Letter属性的字符:
/ <-:Letter> /
EXERCISE 15.3Write a program to count all of the characters that match either the Letter
or Number
properties. What percentage of the code points between 1 and 0xFFFD are either letters or numbers? The .chr
method may be handy here.
练习15.3编写一个程序来计算与Letter或Number属性匹配的所有字符。 1和0xFFFD之间的代码点百分比是字母还是数字? .chr方法在这里可能很方便。
4.49.3. User-Defined Character Classes
You can define your own character classes. Put the characters that you want to match inside <[…]>
. These aren’t the same square brackets that you saw earlier for grouping; these are inside the angle brackets. This character class matches either a
, b
, or 3
:
你可以定义自己的角色类。将要匹配的字符放在<[…]>中。这些与你之前看到的用于分组的方括号不同;这些都在尖括号内。此字符类匹配a,b或3:
/ <[ab3]> /
As with everything else so far, this matches one character and that one character can be any of the characters in the character class. This character class matches either case at a single position:
与目前为止的所有其他内容一样,它匹配一个字符,并且一个字符可以是字符类中的任何字符。此字符类匹配单个位置的任一个案例:
/ <[Hh]> ama / # also / [ :i h ] ama /
You could specify the hexadecimal value of the code point. The whitespace is insignificant:
你可以指定代码点的十六进制值。空白是微不足道的:
/ <[ \x[48] \x[68] ]> ama /
The character name versions work too:
角色名称版本也适用:
/ <[
\c[LATIN CAPITAL LETTER H]
\c[LATIN SMALL LETTER H]
]>
/
You can make a long list of characters:
你可以制作一长串字符:
/ <[abcdefghijklmnopqrstuvwxyz]> / # from a to z
Inside the character class the is just a
. If you try to put a comment in there all of the characters in your message become part of the character class:
在角色类中,#只是一个#。如果你尝试在其中放置注释,则消息中的所有字符都将成为字符类的一部分:
/ <[
\x[48] # uppercase
\x[68] # lowercase
]>
/
You’ll probably get warnings about repeated characters if you try to do that.
如果你尝试这样做,你可能会收到有关重复字符的警告。
CHARACTER CLASS RANGES
But that’s too much work. You can use ..
to specify a range of characters. The literal characters work as well as the hexadecimal values and the names. Notice you don’t quote the literal characters in these ranges:
但那工作太多了。你可以使用..指定一系列字符。文字字符以及十六进制值和名称都起作用。请注意,你不引用这些范围中的文字字符:
/ <[a..z]> /
/ <[ \x[61] .. \x[7a] ]> /
/ <[ \c[LATIN SMALL LETTER A] .. \c[LATIN SMALL LETTER Z] ]> /
The range doesn’t have to be the only thing in the square brackets:
范围不一定是方括号中的唯一内容:
/ <[a..z 123456789]> /
You could have two ranges:
你可以有两个范围:
/ <[a..z 1..9]> /
NEGATED CHARACTER CLASSES
Sometimes it’s easier to specify the characters that can’t match. You can create a negated character class by adding a -
between the opening angle bracket and the opening square bracket. This example matches any character that is not a
, b
, or 3
:
有时,指定无法匹配的字符会更容易。你可以通过在开角括号和开始方括号之间添加 - 来创建否定字符类。此示例匹配任何不是a,b或3的字符:
/ <-[ab3]> /
Space inside a character class is also insignificant:
字符类中的空格也是微不足道的:
/ <-[ a b 3 ]> /
You can use a negated character class of one character. Quotes inside the character class are literal characters because Raku knows you aren’t quoting:
你可以使用一个字符的否定字符类。字符类中的引号是文字字符,因为Raku知道你没有引用:
/ <-[ ' ]> / # not a quote character
This one matches any character that is not a newline:
这个匹配任何不是换行符的字符:
/ <-[ \n ]> / # not a newline
The predefined character class shortcuts can be part of your character class:
预定义的字符类快捷方式可以是你的角色类的一部分:
/ <-[ \d \s ]> / # digits or whitespace
Like the Unicode properties, you can combine sets of characters:
与Unicode属性一样,你可以组合字符集:
/ <[abc] + [xyz]> / # but, also <[abcxyz]>
/ <[a..z] - [ijk]> / # easier than two ranges
EXERCISE 15.4Create a program to output all the input lines. Skip any line that contains a letter unless it’s a vowel. Also skip any lines that are blank (that is, only have whitespace).
练习15.4创建一个程序来输出所有输入行。跳过包含字母的任何行,除非它是元音。也跳过任何空白行(即只有空格)。
4.50. Matching Adverbs
You can change how the match operator works by applying adverbs, just like you changed how Q
worked in [Chapter 4](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch04.html#camelia-strings). There are several, but you’ll only see the most commonly used here.
你可以通过应用副词来更改匹配运算符的工作方式,就像你在第4章中更改Q的工作方式一样。有几个,但你只会看到此处最常用的。
4.50.1. Matching Either Case
So far a character in your pattern matches exactly the same character in the target. An H
only matches an uppercase H and not any other sort of H:
到目前为止,模式中的字符与目标中的字符完全匹配。 H只匹配大写的H而不是任何其他类型的H:
my $pattern = rx/ Hama /;
put 'Hamadryas' ~~ $pattern; # Matches
Change your pattern by one character. Instead of an uppercase H
, use a lowercase one:
将模式更改为一个字符。而不是大写的H,使用小写的:
my $pattern = rx/ hama /;
put 'Hamadryas' ~~ $pattern; # Misses because h is not H
The pattern is case sensitive, so this doesn’t match. But you can make it case insensitive with an adverb. The :i`adverb makes the literal alphabetic characters match either case. You can put the adverb right after the `rx
or the m
:
该模式区分大小写,因此不匹配。但是你可以用副词区分大小写。 :iadverb使文字字母符合两种情况。你可以把副词放在rx或m之后:
my $pattern = rx:i/ hama /;
put 'Hamadryas' ~~ $pattern; # Matches, :i outside
This is the reason you can’t use the colon as the delimiter!
When you use an adverb on the outside of the pattern, that adverb applies to the entire pattern. You can also put the adverb on the inside of the pattern:
这就是你不能使用冒号作为分隔符的原因!
在模式外部使用副词时,该副词适用于整个模式。你也可以把副词放在模式的内部:
my $pattern = rx/ :i hama /;
put 'Hamadryas' ~~ $pattern; # Matches, :i inside
Isn’t that interesting? Now you start to see why whitespace isn’t counted as part of the pattern. There’s much more going on besides literal matching of characters.
The adverb applies from the point of its insertion to the end of the pattern. In this case it applies to the entire pattern because the :i
is at the beginning. Put that adverb later in the pattern, and it applies from there to the rest of the pattern. Here the ha
only match lowercase because the adverb shows up later. The rest of the pattern after the :i
is case insensitive:
那不是很有趣吗?现在你开始明白为什么空格不算作模式的一部分。除了字符的字面匹配之外还有更多的事情要做。
副词从插入点到模式结尾。在这种情况下,它适用于整个模式,因为:i在开头。将该副词放在模式中,然后从那里应用到模式的其余部分。 ha只与小写匹配,因为副词会在稍后出现。在以下情况之后的其余模式:i不区分大小写:
my $pattern = rx/ ha :i ma /; # final ma case insensitive
You can group parts of patterns with square brackets. This example groups the am
but doesn’t do much else because there’s nothing else special going on:
你可以使用方括号对部分图案进行分组。这个例子对am进行分组,但没有做太多其他事情,因为没有其他特别的事情:
my $pattern = rx/ h [ am ] a /;
An adverb inside a group applies only to that group:
组内的副词仅适用于该组:
my $pattern = rx/ h [ :i am ] a /;
The rules are the same: the adverb applies from the point of its insertion to the end of the group:
规则是相同的:副词从插入点到组尾:
my $pattern = rx/ h [ a :i m ] a /; # matches haMa or hama
At this point, you’re probably going to start mixing up what’s going on. There’s another reason whitespace doesn’t matter—you can add comments to your pattern:
在这一点上,你可能会开始混淆正在发生的事情。空白无关紧要的另一个原因 - 你可以为你的模式添加注释:
my $pattern = rx/
h
[ # group this next part
a
:i # case insensitive to end of group
m
] # end of group
a
/;
Everything from the #
character to the end of the line is a comment. You can use embedded comments too:
从#字符到行尾的所有内容都是注释。你也可以使用嵌入式注释:
my $pattern = rx/
:i #`( case insensitive ) Hama
/;
These aren’t particularly good comments because you’re annotating what the syntax already denotes. As a matter of good practice, you should comment what you are trying to match rather than what the syntax does. However, the world isn’t going to end if you leave a reminder for yourself of what a new concept does.
EXERCISE 15.5Write a program that outputs only the lines of input that contain the text ei
. You’ll probably want to save this program to build on in later exercises.
这些并不是特别好的注释,因为你正在注释语法已经表示的内容。作为一种良好实践,你应该评论你要匹配的内容而不是语法的内容。但是,如果你给自己留下一个新概念的提醒,世界就不会结束。
练习15.5编写一个只输出包含文本ei的输入行的程序。你可能想要保存这个程序,以便在以后的练习中继续使用。
4.50.2. Ignoring Marks
The :ignoremark
adverb changes the pattern so that accents and other marks don’t matter. The marks can be there or not. It works if the marks are in the target or the pattern:
:ignoremark副词会更改模式,以便重音和其他标记无关紧要。标记可以存在与否。如果标记在目标或模式中,它可以工作:
$_ = 'húdié'; # ??
put m/ hudie / ?? 'Matched' !! 'Missed'; # Missed
put m:ignoremark/ hudie / ?? 'Matched' !! 'Missed'; # Matched
$_ = 'hudie';
put m:ignoremark/ húdié / ?? 'Matched' !! 'Missed'; # Matched
It even works if both the target and the pattern have different marks in the same positions:
如果目标和模式在相同位置具有不同的标记,它甚至可以工作:
$_ = 'hüdiê';
put m:ignoremark/ húdié / ?? 'Matched' !! 'Missed'; # Matched
Some adverbs can show up inside the pattern. They apply to the parts of the pattern that come after them:
一些副词可以出现在模式中。它们适用于它们之后的模式部分:
$_ = 'hüdiê';
put m/ :ignoremark hudie / ?? 'Matched' !! 'Unmatched'; # Matched
4.50.3. Global Matches
A pattern might be able to match several times in the same text. The :global
adverb gets all of the nonoverlapping [Match
](https://docs.raku.org/type/Match.html)es. It returns a [List
](https://docs.raku.org/type/List.html):
模式可能能够在同一文本中多次匹配。 :全局副词获取所有不重叠的匹配。它返回一个List:
$_ = 'Hamadryas perlicus';
my $matches = m:global/ . s /;
say $matches; # (?as? ?us?)
No matches gets you an empty [List
](https://docs.raku.org/type/List.html):
没有匹配得到一个空列表:
$_ = 'Hamadryas perlicus';
my $matches = m:global/ six /;
say $matches; # ()
The match operator can find overlapping matches too. Use :overlap
to return a potentially longer list. The ?uta?
and ?ani?
here both match the same a:
匹配运算符也可以找到重叠匹配。使用:重叠以返回可能更长的列表。 ??和?ani?这里两个匹配相同的a:
$_ = 'Bhutanitis thaidina';
my $global = m:global/ <[aeiou]> <-[aeiou]> <[aeiou]> /;
say $global; # (?uta? ?iti? ?idi?)
my $overlap = m:overlap/ <[aeiou]> <-[aeiou]> <[aeiou]> /;
say $overlap; # (?uta? ?ani? ?iti? ?idi? ?ina?)
4.51. Things That Use Patterns
There are many features that you haven’t been able to use so far because you hadn’t seen regexes yet. Now you’ve seen regexes, so you can see these things. There are a couple of [Str
](https://docs.raku.org/type/Str.html) methods that work with a pattern to transform values. This section is a taste of the features you’ll use most often.
The .words
and .comb
methods break up text. The .split
method is the general case of that. It takes a pattern to decide how to break up the text. Whatever it matches are the parts that disappear. You could break up a line on tabs, for instance:
到目前为止,你还无法使用许多功能,因为你尚未看到正则表达式。现在你已经看过正则表达式,所以你可以看到这些东西。有一些Str方法可以使用模式来转换值。本节介绍了你最常使用的功能。
my @words = $line.split: / \t /;
.grep
can use the match operator to select things. If the match operator succeeds it returns something that’s True
, and that element is part of the result:
`.grep`可以使用匹配运算符来选择事物。如果匹配运算符成功,则返回一些True,并且该元素是结果的一部分:
my @words-with-e = @word.grep: /:i e/;
Or, to put it all together:
或者,把它们放在一起:
my @words-with-e = $line.split( / \t / ).grep( /:i e/ );
.split
can specify multiple possible separators. Not all of them need be matches. This breaks up a line on a literal comma or whitespace:
`.split`可以指定多个可能的分隔符。并非所有人都需要匹配。这会在文字逗号或空格上划分一行:
my @words-with-e = $line
.split( [ ',', / \s / ] )
.grep( /:i e/ );
.comb
does a job similar to .split
, but it breaks up the text by keeping the parts that matched. This keeps all the nonoverlapping groups of three digits and discards everything else:
`.comb`的工作类似于.split,但它通过保留匹配的部分来分解文本。这将保留所有三个数字的非重叠组,并丢弃其他所有内容:
my @digits = $line.comb: /\d\d\d/;
With no argument .comb
uses the pattern of the single .
to match any character. This breaks up a [Str
](https://docs.raku.org/type/Str.html) into its characters without discarding anything:
没有参数.comb使用单一模式。匹配任何角色。这会将Str分解为其角色而不丢弃任何内容:
my @characters = $line.comb: /./;
4.51.1. Substitutions
The .subst
method works with a pattern to substitute the matched text with other text:
my $line = "This is PERL 6";
put $line.subst: /PERL/, 'Perl'; # This is Raku
This one makes the substitution for the first match:
这个替换第一场比赛:
my $line = "PERL PERL PERL";
put $line.subst: /PERL/, 'Perl'; # Perl PERL PERL
Use the :g
adverb to make all possible substitutions:
使用:g副词进行所有可能的替换:
my $line = "PERL PERL PERL";
put $line.subst: /PERL/, 'Perl'; # Perl Perl Perl
Each of these returns the modified [Str
](https://docs.raku.org/type/Str.html) and leaves the original alone. Use .subst-mutate
to change the original value:
其中每个都返回修改后的Str并单独留下原始文件。使用.subst-mutate更改原始值:
my $line = "PERL PERL PERL";
$line.subst-mutate: /PERL/, 'Perl', :g;
put $line; # Perl Perl Perl
These will be much more useful with the regex features you’ll see in the next chapter.
EXERCISE 15.6Using .split
, output the third column of a tab-delimited file. The butterfly census file you made at the end of [Chapter 9](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch09.html#camelia-hashes) would do nicely here.
对于你将在下一章中看到的正则表达式功能,这些功能将更加有用。
EXERCISE 15.6使用.split,输出制表符分隔文件的第三列。你在第9章结尾处制作的蝴蝶人口普查文件在这里做得很好。
4.52. Summary
You haven’t seen the full power of regexes in this chapter since it was mostly about the mechanism of applying the patterns to text. That’s not a big deal—the patterns can be much more sophisticated, but the mechanisms are the same. In the next chapter you’ll see most of the fancier features you’ll regularly use.
在本章中你没有看到正则表达式的全部功能,因为它主要是关于将模式应用于文本的机制。这不是什么大问题 - 模式可以更复杂,但机制是相同的。在下一章中,你将看到你经常使用的大多数更高级的功能。
5. 更漂亮的正则表达式
You won’t see all the rest of the regular expression syntax in this chapter, but you’ll see the syntax you’ll use the most. There’s much more to patterns, but this should get you most of the way through common problems. With grammars ([Chapter 17](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch17.html#camelia-grammars)), the power of even simple patterns will become apparent.
在本章中,你不会看到所有其他正则表达式语法,但你将看到最常用的语法。模式有很多,但这应该可以解决常见问题。使用 grammars(第17章),即使是简单模式的威力也会变得明显。
5.1. 量词
Quantifiers allow you to repeat a part of a pattern. Perhaps you want to match several of the same letter in a row—an a followed by one or more b’s then another a. You don’t care how many b’s there are as long as there’s at least one of them. The +
quantifier matches the immediately preceding part of the pattern one or more times:
量词允许你重复模式的一部分。也许你想要连续匹配几个相同的字母 - 一个 a 后跟一个或多个 b,然后是另一个*a*。你不在乎有多少 b,只要有至少一个 b 就好了。 +
量词与紧接其前的部分模式匹配一次或多次:
my @strings = < Aa Aba Abba Abbba Ababa >;
for @strings {
put $_, ' ', m/ :i ab+ a / ?? 'Matched!' !! 'Missed!';
}
The first [Str
](https://docs.raku.org/type/Str.html) here doesn’t match because there isn’t at least one b. All of the others have an a followed by one or more b*s and another *a:
这里的第一个[字符串
](https://docs.raku.org/type/Str.html)不匹配,因为没有至少一个 b。所有其他字符串都有一个 a 后跟一个或多个 b,还有另一个 a:
Aa Missed!
Aba Matched!
Abba Matched!
Abbba Matched!
Ababa Matched!
A quantifier only applies to the part of the pattern immediately in front of it—that’s the b
, not the ab
. Group the ab
and apply the quantifier to the group (which counts as one thingy):
量词仅适用于紧接在其前的部分模式 - 即 b
,而不是 ab
。将 ab
分组并将量词应用于组(计为一个东西):
my @strings = < Aa Aba Abba Abbba Ababa >;
for @strings {
put $_, ' ', m/ :i [ab]+ a / ?? 'Matched!' !! 'Missed!';
}
Now different [Str
](https://docs.raku.org/type/Str.html)s match. The ones with repeated b’s don’t match because the quantifier applies to the [ab]`group. Only two of the [`Str
](https://docs.raku.org/type/Str.html)s have repeated ab’s:
现在匹配的是不同的[字符串
](https://docs.raku.org/type/Str.html)了。重复 b 的那些不匹配,因为量词应用于 [ab]
组。只有两个[字符串
](https://docs.raku.org/type/Str.html)重复了 ab:
Aa Missed!
Aba Matched!
Abba Missed!
Abbba Missed!
Ababa Matched!
EXERCISE 16.1Using butterfly_census.txt (the file you made at the end of [Chapter 9](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch09.html#camelia-hashes)), use a regex to count the number of distinct butterfly species whose names have two or more consecutive i’s. Use the +
quantifier in your pattern.
练习16.1使用 butterfly_census.txt(你在第9章末尾创建的文件),使用正则表达式来计算名称有两个或更多个连续 i 的不同蝴蝶物种的数量。在模式中使用 +
量词。
5.1.1. Zero or More
The quantifier is like
+
but matches *zero or more times. This makes that part of the pattern optional. If it matches it can repeat as many times as it likes. Perhaps you want to allow the letter a between b’s. The a’s can be there or not be there:
量词类似于
+
但匹配零次或多次。这使得该模式的一部分可选。如果它匹配,它可以重复任意次数。也许你想允许 *b 之间有字母 a。 a 可以在那里或不在那里:
my @strings = < Aba Abba Abbba Ababa >;
for @strings {
put $_, ' ', m/ :i ba*b / ?? 'Matched!' !! 'Missed!';
}
The [Str
](https://docs.raku.org/type/Str.html)s with consecutive b’s match because they have zero a’s between the b’s, but the [Str
](https://docs.raku.org/type/Str.html) with bab also matches because it has zero or more a’s between them:
带有连续 b 的[字符串
](https://docs.raku.org/type/Str.html)匹配了,因为它们在 b 之间没有 a,但是带有 bab 的[字符串
](https://docs.raku.org/type/Str.html)也匹配了,因为它们之间有零或多个 a:
Aba Missed!
Abba Matched!
Abbba Matched!
Ababa Matched!
EXERCISE 16.2Adapt your solution from the previous exercise to find the butterfly species names that have consecutive a’s that may be separated by either n or s.
练习16.2 从上一个练习中获取解决方案,找到具有连续 a 的蝴蝶种类名称,这些名称可以用 n 或 s 分隔。
5.1.2. Greediness
The +
and quantifiers are greedy; they match as much of the text as they can. Sometimes that’s too much. Change the earlier example to match another *b after the quantifier. Now there must be at least two b’s in a row:
+
和 量词是贪婪的;他们尽可能多地匹配文本。有时匹配太多了。更改前面的示例以匹配量词后的另一个 *b。现在必须连续至少有两个 b:
my @strings = < Aba Abba Abbba Ababa >;
for @strings {
put $_, ' ', m/ :i ab+ ba / ?? 'Matched!' !! 'Missed!';
}
The first [Str
](https://docs.raku.org/type/Str.html) doesn’t match because it doesn’t have one or more b’s followed by another b. It’s the same for the last [Str
](https://docs.raku.org/type/Str.html). The middle two [Str
](https://docs.raku.org/type/Str.html)s have enough b’s to satisfy both parts of the pattern:
第一个[字符串
](https://docs.raku.org/type/Str.html)不匹配,因为它没有一个或多个 b,后面再跟另一个 b。对于最后一个[字符串
](字符串
(https://docs.raku.org/type/Str.html)有足够的 b 来满足模式的两个部分:
Aba Missed!
Abba Matched!
Abbba Matched!
Ababa Missed!
But think about how this works inside the matcher. When it sees the b+
it matches as many b’s as it can. In Abbba
, the b+
starts by matching bbb
. The b+
part of the pattern is satisfied. The matcher moves on to the next part of the pattern, which is another b
. The text doesn’t have any leftover b’s to satisfy that part because the greedy quantifier matched them all.
The match doesn’t fail because of another tactic the matcher can use: it can backtrack on the quantifier that just matched to force it to give up some of the text. The b+
needs one or more b’s. Whether it matched two or three doesn’t matter, because either satisfies that. Backing up one position in the text leaves a b for the next part to match. Once it backs up it tries the next part of the pattern.
但想想在匹配器中如何工作。当它看到 b+
时,它尽可能多地匹配 b。在 Abbba
中,b+
从匹配 bbb
开始。满足模式的 b+
部分。匹配器移动到模式的下一部分,这是另一个 b
。该文本没有任何剩余的 b 来满足该部分,因为贪婪的量词把它们全部匹配完了。
匹配不会因为匹配器可以使用的另一种策略而失败:它可以回溯刚刚匹配的量词,迫使它放弃一些文本。 b+
需要一个或多个 b。它是否匹配两个或三个并不重要,因为要么满足这一点。在文本中回退一个位置会空出一个 b 以供下一部分匹配。一旦它回退它就会尝试模式的下一部分。
5.1.3. Zero or One
The ?
quantifier matches zero or once only; it makes the preceding part of the pattern optional. In this pattern you can have one or two b’s because you used ?
to make one of them optional:
?
量词匹配零或一次;它使模式的前一部分可选。在这种模式中,你可以使用一个或两个 b,因为你使用过 ?
使其中一个可选:
my @strings = < Aba Abba Abbba Ababa >;
for @strings {
put $_, ' ', m/ :i ab? ba / ?? 'Matched!' !! 'Missed!';
}
Now the first [Str
](https://docs.raku.org/type/Str.html) can match because the first b can match zero times. The third [Str
](https://docs.raku.org/type/Str.html) can’t match because there is more than one b and the ?
can’t match more than one of them:
现在第一个[字符串
](https://docs.raku.org/type/Str.html)可以匹配,因为第一个 b 可以匹配零次。第三个[字符串
](https://docs.raku.org/type/Str.html)无法匹配,因为有多个 b 并且 ?
不能匹配多个 b:
Aba Matched!
Abba Matched!
Abbba Missed!
Ababa Matched!
5.1.4. Minimal and Maximal
If you want to match an exact number of times use . With a single number after it the
matches exactly that number of times. This matches exactly three b’s:
如果要匹配确切的次数,请使用 。在它之后有一个数字,
恰好匹配那个次数。这恰好与三个 b 匹配:
my @strings = < Aba Abba Abbba Ababa >;
for @strings {
put $_, ' ', m/ :i ab**3 a / ?? 'Matched!' !! 'Missed!';
}
There’s only one [Str
](https://docs.raku.org/type/Str.html) that matches:
只有一个[字符串
](https://docs.raku.org/type/Str.html)匹配:
Aba Missed!
Abba Missed!
Abbba Matched!
Ababa Missed!
You can use a range after the **
. The quantified part must match at least the range minimum and will only match as many repetitions as the range maximum:
你可以在 **
之后使用范围。量化部分必须至少匹配范围最小值,并且只匹配范围最大值的重复次数:
my @strings = < Aba Abba Abbba Ababa Abbbba >;
for @strings {
put $_, ' ', m/ :i a b**2..3 a / ?? 'Matched!' !! 'Missed!';
}
Two [Str
](https://docs.raku.org/type/Str.html)s match—the ones with two or three consecutive b’s:
两个[字符串
](https://docs.raku.org/type/Str.html)匹配 - 具有两个或三个连续 b 的那个:
Aba Missed!
Abba Matched!
Abbba Matched!
Ababa Missed!
Abbbba Missed!
An exclusive range works too. Match two or three times by excluding the 1
and 4
endpoints to get the same output:
排除范围也有效。通过排除 1
和 4
端点来匹配两到三次以获得相同的输出:
my @strings = < Aba Abba Abbba Ababa >;
for @strings {
put $_, ' ', m/ :i ab**1^..^4 a / ?? 'Matched!' !! 'Missed!';
}
EXERCISE 16.3Output all the lines from the butterfly census file that have four vowels in a row.
EXERCISE 16.4Output all the lines from the butterfly census file that have exactly four repetitions of an a followed by a nonvowel (such as in Paralasa).
练习16.3 输出蝴蝶人口普查文件中连续有四个元音的所有行。
练习16.4 输出蝴蝶人口普查文件中的所有行,这些行恰好有四个重复的 a 后跟一个非元音(例如在 Paralasa 中)。
5.2. Controlling Quantifiers
Adding a ?
after any quantifier makes it match as little as possible—the greedy quantifiers become nongreedy. The modified quantifier stops matching when the next part of the pattern can match.
These two patterns look for an H, some stuff, and then an s. The first one is greedy and matches all the way to the final s. The second one is nongreedy and stops at the first s it encounters. The greedy case matches the entire text but the nongreedy case matches only the first word:
在任何量词后面添加 ?
使得它尽可能少地匹配 - 贪婪的量词变得不贪婪。当模式的下一部分可以匹配时,修改的量词停止匹配。
这两个模式寻找 H,然后是一些东西,然后是一个 s。第一个是贪婪的,一直匹配到最后的 s。第二个是非贪婪的,并在它遇到的第一个 s 后停止。贪婪的案例匹配整个文本,但非贪婪的案例只匹配第一个单词:
$_ = 'Hamadryas perlicus';
say "Greedy: ", m/ H .* s /; # Greedy: 「Hamadryas perlicus」
say "Nongreedy: ", m/ H .*? s /; # Nongreedy: 「Hamadryas」
You’ll probably find that you often want to make the quantifiers nongreedy.
EXERCISE 16.5Output all the text in the input that appears between underscores. The Butterflies_and_Moths.txt file has some interesting nongreedy matches.
你可能会发现你经常想让量词不贪婪。
练习16.5 输出输入中出现在下划线之间的所有文本。 Butterflies_and_Moths.txt 文件有一些有趣的非贪婪匹配。
5.2.1. Turning Off Backtracking
The :
modifier lets you turn off backtracking by preventing a quantifier from unmatching what it has already matched. In both of these patterns the .+
can match everything to the end of the [Str
](https://docs.raku.org/type/Str.html). The first one has to unmatch some of that to allow the rest of the pattern to match. The second one uses .+:
, which means it can’t give back any of the text to allow the first s to match, so that match fails:
:
修饰符允许你通过阻止量词取消匹配已匹配的内容来关闭回溯。在这两种模式中,.+
可以匹配所有东西直到[字符串
](https://docs.raku.org/type/Str.html)的末尾。第一个必须与其中一些取消匹配,以允许模式的其余部分匹配。第二个使用 .+:
,这意味着它无法归还任何文本以允许第一个匹配,因此匹配失败:
$_ = 'Hamadryas perlicus';
say "Backtracking: ",
m/ H .+ s \s perlicus/; # Backtracking: 「Hamadryas perlicus」
say "Nonbacktracking: ",
m/ H .+: s \s perlicus/; # Nonbacktracking: Nil
The :
can go immediately after the . Each tries to match groups of three characters with a def at the end. The first one matches the entire [
Str
](https://docs.raku.org/type/Str.html) because it’s greedy, but then backs up enough to allow def to match. The second one uses :
, so it refuses to unmatch the def and the pattern fails:
:
可以直接跟在 后面。每个尝试匹配三个字符的组然后是末尾的 def。第一个匹配整个[
字符串
](https://docs.raku.org/type/Str.html),因为它是贪婪的,但后来回退足够多的字符以允许 def 匹配。第二个使用 :
,因此它拒绝取消匹配 def , 模式就失败了:
$_ = 'abcabcabcdef';
say "Backtracking: ",
m/ [ ... ] ** 3..4 def /; # 「abcabcabcdef」
say "Nonbacktracking: ",
m/ [ ... ] **: 3..4 def /; # Nil
[Table 16-1](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch16.html#camelia-regex2-TABLE-regex_summary) summarizes the behavior of the different types of quantifiers.
| Quantifier | Example | Meaning |
| ----------- | ------------- | -------------------------------- |
| ?
| b?
| 零个或一个 b |
| |
b
| 零个或多个 b |
| +
| b+
| 一个或多个 b |
| N
| b
4
| 正好 4 个 b |
| M..N
| b
2..4
| 两到四个 b |
| M..N
| b
1..5
| 带有排除范围的两到四个 b |
| ??
| b??
| 零个 b (不常见的情况) |
| ?
| b
?
| 零个或多个 b,非贪婪的 |
| +?
| b+?
| 一个或多个 b,非贪婪的 |
| ?:
| b?:
| 零个或多个 b,没有回溯 |
| :
| b
?
| 零个或多个 b,贪婪的,没有回溯 |
| +:
| b+?
| 一个或多个 b,没有回溯 |
| : M..N
| b
2..4
| 两到四个 b,贪婪的,没有回溯 |
5.3. Captures
When you group with parentheses instead of square brackets you capture parts of the text:
当你使用圆括号而不是方括号分组时,你可以捕获文本的一部分:
say 'Hamadryas perlicus' ~~ / (\w+) \s+ (\w+) /;
In the .gist
output you see the captures labeled with whole numbers starting from zero. The captures are numbered by their position in their subpattern from left to right:
在 .gist
输出中,你会看到标记从零开始的整数的捕获。捕获按照从左到右的子模式中的位置进行编号:
「Hamadryas perlicus」
0 => 「Hamadryas」
1 => 「perlicus」
You can access the captures with postcircumfix indices (but only if the match succeeds). This looks like a [Positional
](https://docs.raku.org/type/Positional.html) but isn’t, but that’s a distinction you don’t need to worry about here. The output shows the same captures you saw before:
你可以使用 postcircumfix 索引访问捕获(但仅在匹配成功时)。这看起来像一个 [Positional
](https://docs.raku.org/type/Positional.html),但不是,但这是一个区别,这里你不需要担心。输出显示你之前看到的相同捕获:
my $match = 'Hamadryas perlicus' ~~ / (\w+) \s+ (\w+) /;
if $match {
put "Genus: $match[0]"; # Genus: Hamadryas
put "Species: $match[1]"; # Species: perlicus
}
The special variable $/
already stores the result of the last successful match. You can access elements in it directly:
特殊变量 $/
已经存储了上次成功匹配的结果。你可以直接访问其中的元素:
$_ = 'Hamadryas perlicus';
if / (\w+) \s+ (\w+) / {
put "Genus: $/[0]"; # Genus: Hamadryas
put "Species: $/[1]"; # Species: perlicus
};
It gets better. There’s a shorthand to access the captures in $/
. The number variables $0
and $1
are actually $/[0]
and $/[1]
(and this is true for as many captures as you create):
它变得更好了。有一个简写来访问 $/
中的捕获。数字变量 $0
和 $1
实际上是 $/[0]
和 $/[1]
(对于你创建的捕获次数,这是正确的):
$_ = 'Hamadryas perlicus';
if / (\w+) \s+ (\w+) / {
put "Genus: $0"; # Genus: Hamadryas
put "Species: $1"; # Species: perlicus
};
If a previous match fails then $/
is empty and you don’t see the values from the previous successful match. An unsuccessful match resets to $/
to nothing:
如果先前的匹配失败,则 $/
为空,并且你看不到上一次成功匹配的值。不成功的匹配将 $/
重置为空:
my $string = 'Hamadryas perlicus';
my $first-match = $string ~~ m/(perl)(.*)/;
put "0: $0 | 1: $1"; # 0: perl | 1: icus
my $second-match = $string ~~ m/(ruby)(.*)/;
put "0: $0 | 1: $1"; # 0: | 1: -- nothing in these variables
5.3.1. Named Captures
Instead of relying on the numbered captures, you can give them names. These become keys in a [Hash
](https://docs.raku.org/type/Hash.html) in the [Match
](https://docs.raku.org/type/Match.html) object. Label a capture with a $<
LABEL>=
in front of the capturing parentheses:
你可以为它们命名,而不是依赖于编号的捕获。这些成为 [Match
](https://docs.raku.org/type/Match.html) 对象中 [Hash
](https://docs.raku.org/type/Hash.html) 的键。在捕获的圆括号前用 $ <LABEL>=
标记捕获:
$_ = 'Hamadryas perlicus';
if / $<genus>=(\w+) \s+ $<species>=(\w+) / {
put "Genus: $/<genus>"; # Genus: Hamadryas
put "Species: $/<species>"; # Species: perlicus
};
The output is often much easier to understand when you label the captures. It’s also easier to modify the pattern without disrupting later code, since the positions of labels don’t matter.
As before, you can leave off the slash in $/
but only if you use the angle brackets. This looks like [Associative
](https://docs.raku.org/type/Associative.html) indexing even though the [Match
](https://docs.raku.org/type/Match.html) isn’t an [Associative
](https://docs.raku.org/type/Associative.html) type:
标记捕获时,输出通常更容易理解。在不破坏后续代码的情况下修改模式也更容易,因为标签的位置无关紧要。
和以前一样,只要使用尖括号,就可以省略 $/
中的斜杠。即使 [Match
](https://docs.raku.org/type/Match.html) 不是[关联
](关联
(https://docs.raku.org/type/Associative.html)索引:
$_ = 'Hamadryas perlicus';
if / $<genus>=(\w+) \s+ $<species>=(\w+) / {
put "Genus: $<genus>"; # Genus: Hamadryas
put "Species: $<species>"; # Species: perlicus
};
A label name in a variable works, but in that case you can’t leave off the /
:
变量中的标签名称有效,但在这种情况下,你不能省略 /
:
$_ = 'Hamadryas perlicus';
my $genus-key = 'genus';
my $species-key = 'species';
if / $<genus>=(\w+) \s+ $<species>=(\w+) / {
put "Genus: $/{$genus-key}"; # Genus: Hamadryas
put "Species: $/{$species-key}"; # Species: perlicus
};
If you save the result the names are in your [Match
](https://docs.raku.org/type/Match.html) in the same way they show up in $/
:
如果你将结果保存,则名称在你的[Match
](https://docs.raku.org/type/Match.html)中的方式与它们在 $/
中显示的方式相同:
my $string = 'Hamadryas perlicus';
my $match = $string ~~ m/ $<genus>=(\w+) \s+ $<species>=(\w+) /;
if $match {
put "Genus: $match<genus>"; # Genus: Hamadryas
put "Species: $match<species>"; # Species: perlicus
};
You don’t even need to know the names because you can get those from the [Match
](https://docs.raku.org/type/Match.html). Calling .pairs
returns all the names:
你甚至不需要知道这些名字,因为你可以从[Match
](https://docs.raku.org/type/Match.html)中得到这些名字。调用 .pairs
返回所有名称:
my $string = 'Hamadryas perlicus';
my $match = $string ~~ m/ $<genus>=(\w+) \s+ $<species>=(\w+) /;
put "Keys are:\n\t",
$match
.pairs
.map( { "{.key}: {.value}" } )
.join( "\n\t" );
The put
shows everything without knowing the names in advance:
put
会在事先不知道名字的情况下显示所有内容:
Keys are:
species: perlicus
genus: Hamadryas
When patterns get too complex (say, something that you have to spread over multiple lines) the numbered [Match
](https://docs.raku.org/type/Match.html) variables will probably proliferate beyond your ability to track them. Names do a much better job of reminding you which capture contains what.
当模式变得过于复杂时(比如,你必须分散在多行上),编号的[Match
](https://docs.raku.org/type/Match.html)变量可能会超出你跟踪它们的能力。名称可以更好地提醒你哪个捕获包含什么。
5.3.2. A Capture Tree
Inside capture parentheses you can have additional capture parentheses. Each group gets its own numbering inside the group that contains it:
在捕获圆括号内,你可以使用其他捕获圆括号。每个组在包含它的组内获得自己的编号:
my $string = 'Hamadryas perlicus';
say $string ~~ m/(perl (<[a..z]>+))/;
The output shows that there are two `$0`s and one of them is subordinate to the other. The captures are nested so the results are nested:
输出显示有两个 $0
,其中一个从属于另一个。捕获是嵌套的,因此结果是嵌套的:
「perlicus」
0 => 「perlicus」
0 => 「icus」
To access the top-level match, use $/[0]
or $0
. To get the nested matches you access the next level with the appropriate subscript:
要访问顶级匹配,请使用 $/[0]
或 $0
。要获取嵌套匹配,你可以使用相应的下标访问下一级别:
my $string = 'Hamadryas perlicus';
$string ~~ m/(perl (<[a..z]>+))/;
# explicit $/
say "Top match: $/[0]"; # Top match: perlicus
say "Inner match: $/[0][0]"; # Inner match: icus
# or skip the $/
say "Top match: $0"; # Top match: perlicus
say "Inner match: $0[0]"; # Inner match: icus
This works for named captures in the same way. The outer captures include the inner text as well as the inner captures:
这适用于以相同方式命名的捕获。外部捕获包括内部文本以及内部捕获:
my $string = 'Hamadryas perlicus';
$string ~~ m/
$<top> = (perl
$<inner> = (<[a..z]>+)
)
/;
# explicit $/
say "Top match: $/<top>"; # Top match: perlicus
say "Inner match: $/<top><inner>"; # Inner match: icus
# or skip the $/
say "Top match: $<top>"; # Top match: perlicus
say "Inner match: $<top><inner>"; # Inner match: icus
It’s not one or the other. You can mix number variables and labels if that makes sense:
它不是一个或另一个。如果有意义,你可以混合数字变量和标签:
my $string = 'Hamadryas perlicus';
$string ~~ m/
( perl $<inner> = (<[a..z]>+) )
/;
# explicit $/
say "Top match: $/[0]"; # Top match: perlicus
say "Inner match: $/[0]<inner>"; # Inner match: icus
# or skip the $/
say "Top match: $0"; # Top match: perlicus
say "Inner match: $0<inner>"; # Inner match: icus
This nesting makes it very easy to construct your pattern. The numbering is localized to the level you are in. If you add other captures to the pattern they only disturb their level.
EXERCISE 16.6Extract from the Butterflies_and_Moths.txt file all the scientific names between underscores (such as Crocallis elinguaria
). Capture the genus and species separately. Which genus has the most species?
这种嵌套使得构建模式变得非常容易。编号已本地化到你所在的层级。如果你在模式中添加其他捕获,则只会影响其层级。
练习16.6 从 Butterflies_and_Moths.txt 文件中提取下划线之间的所有科学名称(例如 Crocallis elinguaria
)。分别捕获属和种。哪个属种类最多?
5.3.3. Backreferences
The result of a capture is available inside your patterns. You can use that to match something else in the same pattern. Use the [Match
](https://docs.raku.org/type/Match.html) variables to refer to the part that you want:
捕获的结果可在模式中使用。你可以使用它来匹配相同模式中的其他内容。使用[Match
](https://docs.raku.org/type/Match.html)变量来引用所需的部分:
my $line = 'abba';
say $line ~~ / a (.) $0 a /;
The output shows the entire match and the capture:
输出显示整个匹配和捕获:
「abba」
0 => 「b」
Refer to captures at the same level with the number variables. The $0
and $1
are backreferences to parts of the pattern that have already matched:
请参阅与数字变量在同一级别的捕获。 $0
和 $1
是对已经匹配的模式部分的反向引用:
my $line = 'abccba';
say $line ~~ / a (.)(.) $1 $0 a /;
There are only two captures in the output:
输出中只有两个捕获:
「abccba」
0 => 「b」
1 => 「c」
If the capture is nested you have to do a bit more work. You might think you can subscript the capture variable, but can you see why it fails silently?
如果捕获是嵌套的,则必须做更多的工作。你可能认为可以下标捕获变量,但是你能看到它为什么会无声地失败吗?
my $line = 'abcca';
say $line ~~ / a (.(.)) $0[0] a /; # does not match!
Those square brackets are pattern metacharacters and not postcircumfix indexers! You think that you have an element in $0
, but it’s really $0
stringified followed by a group that is the literal text 0
.
To get around this parsing problem surround the subscript access in $()
so the pattern sees it as one thing. There’s one more trick to make it work out. Backreferences are only valid at a sequence point where the match operator has filled in all the details. An empty code block can force that:
那些方括号是模式元字符而不是 postcircumfix 索引器!你认为你在 $0
有一个元素,但它实际上是 $0
字符串化后跟一个文字文本 0
的组。
为了解决这个解析问题围绕 $()
中的下标访问,所以模式将其视为一件事。还有一个技巧可以让它成功。反向引用仅在匹配运算符填充了所有详细信息的序列点有效。空代码块可以强制执行:
my $line = 'abcca';
say $line ~~ / a (.(.)) {} $($0[0]) a /; # matches
Now the $0[0]
can match the c:
现在 $0[0]
可以匹配 c:
「abcca」
0 => 「bc」
0 => 「c」
5.4. Surrounders and Separators
To match something that has prefix and suffix characters, you could type out the pattern in the order it appears in the [Str
](https://docs.raku.org/type/Str.html). Here’s an example that matches a word in literal parentheses:
要匹配具有前缀和后缀字符的内容,你可以按照它在[字符串
](https://docs.raku.org/type/Str.html)中出现的顺序输出模式。这是一个与字面括号中的单词匹配的示例:
my $line = 'outside (pupa) outside';
say $line ~~ / '(' \w+ ')' /; # 「(pupa)」
That’s not the best way to communicate that you want to match something in parentheses, though. The start and end characters aren’t next to each other in the pattern; you have to read ahead then surmise that the parentheses are circumfix parts of the same idea.
Instead, connect the beginning and end patterns with ~
, then put the interior pattern after that. This describes something surrounded by parentheses subordinate to the structure:
不过,这不是你想要在括号中匹配内容的最佳沟通方式。开始和结束字符在模式中不是彼此相邻的;你必须提前阅读,然后推测括号是同一个想法的一部分。
相反,用 ~
连接开始和结束模式,然后在之后放置内部模式。这描述了从属于结构的括号所包围的东西:
my $line = 'outside (pupa) outside';
say $line ~~ / '(' ~ ')' \w+ /;
This is automatically nongreedy; it does not grab everything until the last closing parenthesis:
这是自动非贪婪的;在最后一个右括号之前它不会抓取所有内容:
my $line = 'outside (pupa) space (pupa) outside';
say $line ~~ m/ '(' ~ ')' \w+ /; # 「(pupa)」
A global match will still find all the instances:
全局匹配仍将找到所有实例:
my $line = 'outside (pupa) space (pupa) outside';
say $line ~~ m:global/ '(' ~ ')' \w+ /; # (「(pupa)」 「(pupa)」)
Going the other way, suppose that you want to match a series of things that are separated by other characters. A line of comma-separated values is such a thing:
换句话说,假设你想要匹配由其他字符分隔的一系列事物。一行以逗号分隔的值是这样的:
my $line = 'Hamadryas,Leptophobia,Vanessa,Gargina';
To match the letters separated by commas, you could match the first group of letters then every subsequent occurrence of a comma and another group of letters:
要匹配用逗号分隔的字母,你可以匹配第一组字母,然后匹配每个后续的逗号和另一组字母:
say $line ~~ / (\w+) [ ',' (\w+) ]+ /;
That works, but it’s annoying because you have to use \w+
twice even though it’s describing the same thing. The %
modifies a quantifier so that the pattern on the right comes between each group:
这是有效的,但它很烦人,因为你必须使用 \w+
两次,即使它描述同样的事情。 %
修饰量词,使右侧的模式位于每个组之间:
say $line ~~ / (\w+)+ % ',' /;
The output shows that you matched each group of letters:
输出显示你匹配了每组字母:
「Hamadryas,Leptophobia,Vanessa,Gargina」
0 => 「Hamadryas」
0 => 「Leptophobia」
0 => 「Vanessa」
0 => 「Gargina」
A double percent allows a trailing separator in the overall match:
双百分号允许在整体匹配中使用尾分隔符:
my $line = 'Hamadryas,Leptophobia,Vanessa,';
say $line ~~ / (\w+)+ %% ',' /;
Notice that it matches that comma that follows Vanessa but does not create an empty capture after it:
请注意,它与 Vanessa 后面的逗号匹配,但不会在其后创建空捕获:
「Hamadryas,Leptophobia,Vanessa,」
0 => 「Hamadryas」
0 => 「Leptophobia」
0 => 「Vanessa」
Although you’d think that CSV files should be simple, they aren’t. In the wild all sorts of weird things happen. The [Text::CSV ](https://modules.raku.org/dist/Text::CSV:cpan:HMBRAND) module handles all of those tricky bits. Use that instead of doing it yourself.
|
虽然你认为 CSV 文件应该很简单,但事实并非如此。在野外,各种各样奇怪的事情都会发生。 [Text::CSV
](https://modules.raku.org/dist/Text::CSV:cpan:HMBRAND) 模块处理所有这些棘手的部分。使用它而不是自己做。
5.5. 断言
Assertions don’t match text; they require that a certain condition be true at the current position in the text. They match a context instead of characters. Specify these in your pattern to allow the matcher to fail faster. You don’t need to scan the entire text if the pattern should only work at the beginning of the text.
断言不匹配文本;他们要求在文本的当前位置某个条件为真。它们匹配上下文而不是字符。在模式中指定这些以允许匹配器更快地失败。如果模式仅适用于文本的开头,则无需扫描整个文本。
5.5.1. 锚点
An anchor prevents the pattern from floating over the text to find a place where it can start matching. It requires that a pattern match at a particular position. If the pattern doesn’t match at that position the match can immediately fail and save itself the work of scanning the text.
The ^
forces your pattern to match at the absolute beginning of the text. This matches because the Hama comes at the beginning of the text:
锚点可防止模式浮动在文本上以找到可以开始匹配的位置。它要求在特定位置匹配模式。如果模式在该位置不匹配,则匹配可能立即失败并自行保存扫描文本的工作。
^
强制你的模式在文本的绝对开头匹配。下面这个会匹配,因为 Hama 出现在文本的开头:
say 'Hamadryas perlicus' ~~ / ^ Hama /; # 「Hama」
Trying to match perl after ^
fails because that pattern is not at the beginning of the text:
尝试匹配 ^
后面的 perl 会失败,因为该模式不在文本的开头:
say 'Hamadryas perlicus' ~~ / ^ perl /; # Nil (fails)
Without the anchor the match would drift over the text looking at each position to check for perl. That’s extra work (and probably incorrect) if you know that you want to match at the beginning. Once the match fails at the beginning it’s immediately done.
The $
is the end-of-string anchor and does something similar at the end of the text:
没有锚点,匹配将漂移在文本上,查看每个位置以检查 perl。如果你知道你想在开始时匹配,这是额外的工作(可能是不正确的)。一旦匹配在开始时失败,匹配立即结束。
$
是字符串结尾的锚点,并在文本末尾执行类似的操作:
say 'Hamadryas perlicus' ~~ / icus $ /; # 「icus」
This one doesn’t match because there’s more text after icus:
这个不匹配,因为 icus 之后有更多的文本:
say 'Hamadryas perlicus navitas' ~~ / icus $ /; # Nil (fails)
There are anchors for the beginning and end of a line; that could be different from the beginning and end of the text. A line ends with a newline and that newline might be in the middle of your multiline text, like in this one (remember that the here doc strips the indention):
行的开头和结尾都有锚点;这可能与文本的开头和结尾不同。行以换行符结尾,换行符可能位于多行文本的中间,就像在这一行中一样(请记住,here doc 删除了缩进):
$_ = chomp q:to/END/; # chomp removes last newline
Chorinea amazon
Hamadryas perlicus
Melanis electron
END
The beginning-of-line anchor, ^^
, matches after the absolute beginning of the text or immediately after any newline. These both work because Chorinea is at the start of the text and the start of the first line:
行首的锚点 ^^
在文本的绝对开头之后或在任何换行符之后立即匹配。下面这两者都有效,因为 Chorinea 位于文本的开头和第一行的开头:
say m/ ^ Chorinea /; # 「Chorinea」
say m/ ^^ Chorinea /; # 「Chorinea」
Likewise, the end-of-line anchor, $$
, matches before any newline or at the absolute end of the text. These also both work because electron is at the end of the text and the end of the last line:
同样,行尾锚点 $$
在任何换行符之前或文本的绝对末尾匹配。下面这些也都有效,因为 electron 在文本的末尾和最后一行的结尾:
say m/ electron $ /; # 「electron」
say m/ electron $$ /; # 「electron」
Hamadryas can’t match at the absolute beginning of the text but it can match at the beginning of a line:
Hamadryas 在文本的绝对开头不能匹配,但它可以在一行的开头匹配:
say m/ ^ Hamadryas /; # Nil
say m/ ^^ Hamadryas /; # 「Hamadryas」
Similarly, perlicus can’t match at the absolute end of the text but it can match at the end of a line:
同样,perlicus 在文本的绝对末尾不能匹配,但它可以在一行的末尾匹配:
say m/ perlicus $ /; # Nil
say m/ perlicus $$ /; # 「perlicus」
5.5.2. Conditions
Word boundaries exist when a non-“word” character is next to a “word” character (in either order). Those terms are a bit fuzzy, since you likely think of word characters as the alphabetic characters. They are, however, the ones that match \w
, which includes numbers and other things. The beginning and the end of the [Str
](https://docs.raku.org/type/Str.html) count as nonword characters.
EXERCISE 16.7Output all the “word” characters that are not alphabetic characters. How many of them are there? The [Range](https://docs.raku.org/type/Range.html)0 .. 0xFFFF
and the .chr
method should be helpful.
Assert a word boundary with <|w>
. Suppose that you want to match the name Hamad. Without a word boundary that would match in Hamadryas, but that’s not what you want. The word boundary keeps it from showing up in the middle of another word:
*当非“单词”字符紧邻“单词”字符时(以任一顺序),则存在单词边界。这些术语有点模糊,因为你可能会将单词字符视为字母字符。然而,它们是匹配 \w
的,包括数字和其他东西。 [字符串
](https://docs.raku.org/type/Str.html)的开头和结尾计为非单词字符。
练习16.7 输出所有非字母字符的“单词”字符。他们中有多少个? [Range](https://docs.raku.org/type/Range.html) 0 .. 0xFFFF
和 .chr
方法应该会有所帮助。
用 <|w>
断言单词边界。假设你想要匹配 Hamad 这个名字。没有在 Hamadryas 中匹配的单词边界,但这不是你想要的。单词边界使它不会出现在另一个单词的中间:
$_ = 'Hamadryas';
say m/ Hamad /; # 「Hamad」
say m/ Hamad <|w> /; # Nil
That second pattern can’t match because Hamadryas has a word character (a letter) following Hamad. The next example matches because a space follows Hamad:
第二种模式无法匹配,因为 Hamadryas 在 Hamad 之后有一个单词字符(一个字母)。下一个例子匹配,因为 Hamad 后面有一个空格:
my $name = 'Ali Hamad bin Perliana';
say $name ~~ / Hamad <|w> /; # 「Hamad」
Word boundaries on each side isolate a word. These matches look for dry
as its own word because it has word boundaries on each side. The first one fails because it’s in the middle of a bigger word:
每一边的单词边界隔离一个单词。这些匹配寻找 day
作为它们自己的单词,因为它的每一边都有单词边界。第一个失败,因为它在一个更大的单词的中间:
$_ = 'Hamadryas';
say m/ <|w> dry <|w> /; # Nil
$_ = 'The flower is dry';
say m/ <|w> dry <|w> /; # 「dry」
Instead of <|w>
you can use the <<
or >>
to point to where the nonword characters should be:
你可以使用 <<
或 >>
代替 <|w>
来指向非单词字符的位置:
$_ = 'The flower is dry';
say m/ << dry >> /; # 「dry」
The arrows can point either way, but always toward the nonword characters:
箭头可以指向任一方向,但始终指向非单词字符:
$_ = 'a!bang';
say m/ << .+ >> /; # 「a!bang」 - greedy
say m/ << .+? >> /; # 「a」 - nongreedy
say m/ >> .+ >> /; # 「!bang」
say m/ >> .+ << /; # 「!」
The opposite of a word boundary assertion is <!|w>
. That means that both sides of the assertion must be the same type of character—either both word characters or both nonword characters. Now the results are flipped:
单词边界断言的反义词是 <!|w>
。这意味着断言的两边必须是相同类型的字符 - 两个单词字符或两个非单词字符。现在翻转结果:
$_ = 'Hamadryas';
say m/ <!|w> dry <!|w> /; # 「dry」
$_ = 'The flower is dry';
say m/ <!|w> dry <!|w> /; # Nil
5.5.3. 代码断言
Code assertions are perhaps the most amazing and powerful part of regular expressions. You can inspect what’s happened so far and use arbitrarily complex code to decide if you accept that. If your code evaluates to `True`you satisfy the assertion and the pattern can keep matching. Otherwise, your pattern fails.
Your code for the assertion shows up in <?{}>
. You can put almost anything you like in there:
代码断言可能是正则表达式中最令人惊讶和最强大的部分。你可以检查到目前为止发生了什么,并使用任意复杂的代码来决定你是否接受。如果你的代码求值为 True
则你满足断言,并且模式可以保持匹配。否则,你的模式将失败。
你的断言代码显示在 <?{}>
中。你可以把几乎任何你喜欢的东西放在那里:
'Hamadryas' ~~ m/ <?{ put 'Hello!' }> /; # Hello!
This matches no characters in Hamadryas but is also not the null pattern (which is not valid). From inside the assertion you get Hello!
as output:
这与 Hamadryas 中的任何字符都不匹配,但也不是空模式(它是无效的)。从断言内你得到 Hello!
作为输出:
put
'Hamadryas' ~~ m/ <?{ put 'Hello!' }> /
?? 'Worked' !! 'Failed';
This first outputs from inside the assertion:
这首先从断言内部输出:
Hello!
Worked!
Change the assertion so that False
is the last expression:
更改断言,以便 False
是最后一个表达式:
put
'Hamadryas' ~~ m/ <?{ put 'Hello!'; False }> /
?? 'Worked' !! 'Failed';
You get much more output. As the code assertion fails the match cursor moves along the text and tries again. Each time the code assertion returns False
it tries again. It keeps doing that until it gets to the end of the [Str
](https://docs.raku.org/type/Str.html):
你得到更多的输出。由于代码断言失败,匹配游标沿文本移动并再次尝试。每次代码断言返回 False
时,它再次尝试。它一直这样做,直到它到达[字符串
](https://docs.raku.org/type/Str.html)的结尾:
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
Hello!
Failed
Here’s something more complex. Suppose you want to match even numbers only. You could create a pattern that looks for an even digit at an end of a [Str
](https://docs.raku.org/type/Str.html):
这是更复杂的事情。假设你只想匹配偶数。你可以创建一个模式,在[字符串
](https://docs.raku.org/type/Str.html)的末尾查找偶数:
say '538' ~~ m/ ^ \d* <[24680]> $ /; # 「538」
With a code assertion you don’t care which digits you match as long as they are even. This makes the pattern a bit simpler by showing the complexity as code. Your intent may be clearer this way:
使用代码断言,只要它们是偶数,就不关心匹配哪些数字。通过将复杂性显示为代码,这使得模式更简单。你的意图可能会更加清晰:
say '538' ~~ m/ ^ (\d+) <?{ $0 %% 2 }> /;
There’s a capture and that text also is divisible by two, so that match succeeds:
有一个捕获,该文本也可被 2 整除,因此匹配成功:
「538」
0 => 「538」
It stills works if the characters aren’t the ASCII decimal digits:
如果字符不是 ASCII 十进制数字,它仍然有效:
say '١٣٨' ~~ m/ ^ (\d+) <?{ $0 %% 2 }> /;
Or even:
甚至:
say '١٣٨' ~~ m/ ^ (\d+) <?{ $0 %% ٢ }> /;
匹配IPV4 地址
Consider a pattern to match a dotted-decimal IP address. There are four decimal numbers from 0 to 255, such as 127.0.0.1 (the loopback address). You could write a pattern without an assertion, but you have to figure out how to restrict the range of the number:
考虑匹配点分十进制 IP 地址的模式。从 0 到 255 有四个十进制数,例如 127.0.0.1(环回地址)。你可以编写一个没有断言的模式,但你必须弄清楚如何限制数字的范围:
my $dotted-decimal = rx/ ^
[
|| [ <[ 0 1 ]> <[ 0 .. 9 ]> ** 0..2 ] # 0 to 199
|| [
2
[
|| <[ 0 .. 4 ]> <[ 0 .. 9 ]> # 200 to 499
|| 5 <[ 0 .. 5 ]> # 250 to 255
]
]
] ** 4 % '.'
$
/;
say '127.0.0.1' ~~ $dotted-decimal; # 「127.0.0.1」
Matching on text to suss out numerical values means careful handling of each character position. That’s a lot of work and uses a feature you haven’t seen yet (alternations are coming up). You could reduce that to almost nothing with a code assertion that looks at the text you just matched and tells the pattern if you want to accept it:
匹配文本以取代数值意味着仔细处理每个字符位置。这是很多工作,并使用了你还没有看到的功能(备选分支即将到来)。你可以使用代码断言将其减少到几乎为零,该代码断言查看你刚匹配的文本并告诉模式你是否要接受它:
my $easier = rx/
^
( <[0..9]>+: <?{ 0 <= $/ <= 255 }> ) ** 4 % '.'
$
/;
The assertion is <?{ 0 ⇐ $/ ⇐ 255 }>
. That $/
is the [Match
](https://docs.raku.org/type/Match.html) for only that level of parentheses. This allows you to be sloppy in the pattern for matching digits. You don’t care if you match 4, 5, or 20 digits because the code assertion will check that.
If that code assertion fails after matching digits, you don’t want to give back some of the digits to try again. You know the next thing must be the .
between groups of digits. To prevent any backtracking you use the :
on that `+`quantifier. You don’t need this to get the right match but it creates less work to ultimately fail.
The %
modifies the ** 4
quantifier so a literal .
shows up between each of the four groups of digits.
断言是 <?{ 0 ⇐ $/ ⇐ 255 }>
那个 $/
是只有那个括号级别的[Match
](https://docs.raku.org/type/Match.html)。这允许你在匹配数字的模式中马虎。你不关心是否匹配 4, 5 或 20 位数字,因为代码断言将检查该数字。
如果代码断言在匹配数字后失败,则你不希望归还一些数字再次尝试。你知道下一件事必须是数字组之间的 .
。要防止任何回溯,请在量词 +
上使用 :
。你不需要这个来获得正确的匹配,但它创造的工作量最少,最终失败。
%
修饰 ** 4
量词,所以字面 .
显示在四组数字中的每一组之间。
5.6. Alternations
Sometimes there are several distinct patterns that might match at the same position. An alternation is a way to specify that. There are two ways to do this: it can match the first alternative that succeeds or it can match the longest one.
有时,有几种不同的模式可能在同一位置匹配。交替是一种指定它的方式。有两种方法可以做到这一点:它可以匹配成功的第一个选项,也可以匹配最长的选项。
5.6.1. First Match
If you’ve used regexes in other languages you’re probably used to alternations where the leftmost alternative that can match is the one that wins. Set up this type of alternation with a ||
between the possibilities:
如果你已经在其他语言中使用了正则表达式,那么你可能会习惯于可以匹配的最左侧备选分支胜出的备选分支。使用 ||
在可能的备选分支之间设置此类备选分支:
my $pattern = rx/ abc || xyz || 1234 /;
Either abc
, xyz
, or 1234
can match:
my @strings = < 1234 xyz abc 789 >;
for @strings {
put "$_ matches" if $_ ~~ $pattern;
}
The first three [Str
](https://docs.raku.org/type/Str.html)s match because they have at least one of the alternatives:
1234 matches
xyz matches
abc matches
The alternation has an interesting feature: you can start it with a ||
with nothing before it. This is the same pattern and does not create an empty alternative at the beginning:
备选分支有一个有趣的特点:你可以以一个前面什么都没有的 ||
开始。这是相同的模式,并且不会在开头创建一个空的备选分支:
my $pattern = rx/ || abc || xyz || 1234 /;
This looks better spread out so each alternation gets its own line. The reformatted pattern starts with ||
and has a more pleasing parallel structure that allows you to remove lines without disturbing the other alternatives:
这看起来更好地展开,因此每个备选分支单独占一行。重新格式化的模式以 ||
开头并且有一个更令人愉悦的并行结构,允许你删除行而不会打扰其他备选分支:
my $pattern = rx/
|| abc
|| xyz
|| 1234
/;
Instead of placing a ||
between each alternative, you can put it before a bunch of alternatives. Do that with an [Array
](https://docs.raku.org/type/Array.html) directly in your pattern:
你可以把 ||
放在一堆备选分支之前而不是在每个备选分支之间放置 ||
。直接在你的模式中使用[数组
](https://docs.raku.org/type/Array.html)执行此操作:
my $pattern = rx/ || @(<abc xyz 1234>) /;
An existing variable after the ||
does the same thing:
||
之后的现有变量做同样的事情:
my @variable = <abc xyz 1234>;
my $pattern = rx/ || @variable /;
You aren’t interpolating that [Array
](https://docs.raku.org/type/Array.html). The pattern uses the current value of the [Array
](https://docs.raku.org/type/Array.html) when it matches. In this example the [Array
](https://docs.raku.org/type/Array.html) has 1234
as the last element when you define the pattern. Before you use the pattern you change that last element:
你没有插值该[数组
](数组
(数组
(https://docs.raku.org/type/Array.html) 在定义模式时将 1234
作为最后一个元素。在使用该模式之前,你需要更改最后一个元素:
my @strings = < 1234 xyz abc 56789 >;
my @variable = <abc xyz 1234>;
my $pattern = rx/ || @variable /;
put "Before:";
for @strings {
put "\t$_ matches" if $_ ~~ $pattern;
}
# change the array after making the pattern
@variable[*-1] = 789;
put "After:";
for @strings {
put "\t$_ matches" if $_ ~~ $pattern;
}
The output shows that you matched with the current value of the variable instead of its value when you created the pattern. Different values match after you change the [Array
](https://docs.raku.org/type/Array.html):
输出显示你匹配的变量的当前值,而不是匹配该模式创建时的值。更改[数组
](https://docs.raku.org/type/Array.html)后,匹配到不同的值:
Before:
1234 matches
xyz matches
abc matches
After:
xyz matches
abc matches
56789 matches
EXERCISE 16.8Output all the lines from the butterfly census file that have the genus Lycaena, Zizeeria, or Hamadryas. How many different species did you find?
练习16.8 输出蝴蝶人口普查文件中有 Lycaena,Zizeeria 或 Hamadryas 属的所有行。你找到了多少种不同的物种?
5.6.2. Longest Token Matching
Some alternations might have “better” possibilities that could match. Rather than choosing the first specified possibility you can tell the match operator to try all of them, then choose the “best” one. This is generally calledlongest token matching (LTM), but it finds the best, not longest, match.
LTM alternation uses a single |
. In this pattern all of the alternatives can match. The first possibility it could match is the single a
. The “best” match is abcd
, though. That’s the match you see in the output:
一些备选分支可能具有可以匹配的“更好”的可能性。你可以告诉匹配操作符尝试所有这些,然后选择“最佳”的可能性,而不是选择第一个指定的可能性。这通常称为最长令牌匹配(LTM),但它找到最佳匹配,而不是最长匹配。
LTM 备选分支使用单个 |
。在这种模式中,所有备选分支都可以匹配。它可以匹配的第一种可能性是单个 a
。不过,“最佳”匹配是 abcd
。这是你在输出中看到的匹配:
my $pattern = rx/
| a
| ab
| abcd
/;
say 'abcd' ~~ $pattern; # 「abcd」
An [Array
](https://docs.raku.org/type/Array.html) variable works just like it did in the ||
examples:
[数组
](https://docs.raku.org/type/Array.html)变量就像在 ||
例子中一样工作:
my @variable = <a ab abcd>;
my $pattern = rx/ | @variable /;
say 'abcd' ~~ $pattern; # 「abcd」
What makes one possibility better than another? There are some rules that decide this. Better patterns have longer tokens, and that’s where the confusion comes in. It’s not actually about how much text it matches; it’s about the pattern.
This next part will probably be more than you’ll ever want to know. A pattern can have both declarative and procedural elements. In short, some parts of the pattern merely describe some text and other parts force the match operator to do something. The abc
is declarative. The {}
inline code is an action.
Consider this example. The longest text that might match is Hamadry. That alternative has the {True}
inline code block in it, though. The second alternative is simply Hamad
, and that is the one that matches:
是什么让一种可能性比另一种更好?有一些规则可以决定这一点。更好的模式有更长的令牌,这就是困惑的来源。实际上并不是它匹配多少文本;这是关于模式的。
下一部分可能比你想知道的要多。模式可以同时具有声明和过程元素。简而言之,模式的某些部分仅描述一些文本,而其他部分则强制匹配操作符执行某些操作。 abc
是声明性的。 {}
内联代码是一个动作。
请看这个例子。可能匹配的最长文本是 Hamadry。但是,该备选分支中包含 {True}
内联代码块。第二个备选分支只是 Hamad
,那是匹配的:
say 'Hamadryas perlicus sixus' ~~ m/
| Hama{True}dry
| Hamad
/; # 「Hamad」
When the match operator is deciding which one has priority it looks for the pattern that has the longest declarative part. The first one has Hama
; the second one has Hamad
. That makes the second one the longer token. It’s about the pattern, not the target text. (Ignore that you haven’t read a definition of a token yet.)
Sometimes the two patterns can have the same size tokens, like these two alternatives. One has a character class and the other a literal d. The more specific one (the literal) wins:
当匹配运算符决定哪一个具有优先级时,它会查找具有最长声明部分的模式。第一个有 Hama
;第二个有 Hamad
。这使得第二个有更长的令牌。它是关于模式,而不是关于目标文本。 (忽略你还没有读过令牌的定义。)
有时这两种模式可以具有相同大小的令牌,就像这两个备选分支一样。一个有字符类,另一个有字面值 d。更具体的一个(字面的)获胜:
$_ = 'Hamadryas perlicus sixus';
say 'Hamadryas perlicus sixus' ~~ m/
| Hama<[def]>{put "first"}
| Hamad {put "second"}
/; # 「Hamad」
The code [Block
](https://docs.raku.org/type/Block.html)s are only there to show which alternative was “best”:
second
「Hamad」
Change that around to see it still choose the more specific one:
改变它,看它仍然选择更具体的一个:
$_ = 'Hamadryas perlicus sixus';
say 'Hamadryas perlicus sixus' ~~ m/
| Hamad {put "first"}
| Hama<[def]>{put "second"}
/; # 「Hamad」
Now the first alternative is more specific and it is “best”:
现在第一个备选分支更具体,它是“最好的”:
first
「Hamad」
So what counts as a token? It’s the longest stretch of things that aren’t procedural. As I write this, however, the documentation avoids defining that. It requires deep knowledge of what happens in the guts of the language. It’s a big ugly topic that I’ll now ignore, although the book Mastering Regular Expressions by Jeffrey E.F. Friedl(O’Reilly) will tell you most of what you need to know. Perhaps the confusion will sort itself out by the time you read this.
什么算作 token 呢?这是最长的一些不是程序性的东西。然而,当我写这篇文章时,文档避免了定义它。它需要深入了解语言的内容。虽然 Jeffrey E.F.Friedl(O’Reilly)的“掌握正则表达式”这本书将告诉你大部分你需要知道的东西,但我现在忽略了一个很大的丑陋主题。也许当你读到这篇文章时,这种困惑会自行解决。
All of that is to say that the match operator looks at each |
alternative and can choose to do the one it thinks provides the best match. The match operator does not have to do them in the order that you typed them.
所有这一切都是说匹配运算符会查看每个 |
备选分支,并可以选择做它认为提供最佳匹配的那个。匹配运算符不必按你键入的顺序执行它们。
5.7. Summary
In this chapter you saw the common regex features that will solve most of your pattern problems. You can repeat parts of a pattern, capture and extract parts of the text, define alternate patterns that can match, and specify conditions within the pattern. There is much more that patterns can do for you. Practice what you’ve read here and delve into the documentation to discover more.
在本章中,你看到了可以解决大多数模式问题的常见正则表达式功能。你可以重复模式的某些部分,捕获和提取文本的某些部分,定义可以匹配的备选分支模式,以及指定模式中的条件。模式可以为你做更多的事情。练习你在这里阅读的内容并深入研究文档以发现更多信息。 == Grammars
Grammars are patterns on a higher plane of existence. They integrate and reuse pattern fragments to parse and react to complicated formats. This feature is at the core of Raku in a very literal sense; the language itself is implemented as a grammar. Once you start using it you’ll probably prefer it to regexes for all but the most simple problems.
Grammars 是存在于更高层面上的模式。它们集成并重用模式片段来解析复杂的格式并做出反应。从字面意义上讲,这个功能是Raku的核心;语言本身是作为语法实现的。一旦你开始使用它,你可能更喜欢它除了最简单的问题之外的所有正则表达式。
5.8. A Simple Grammar
A grammar is a special sort of package. It can have methods and subroutines but mostly comprises special pattern methods called regex
, token
, and rule
. Each of these define a pattern and apply different modifiers.
Raku tends to refer to regex , token , and rule declarations as “rules,” which can be a bit imprecise at times. In this book, you can tell the difference between the language keyword and the general term by the typesetting. I’ll try to not present an ambiguous situation.
|
Start with something simple (too simple for grammars). Define a TOP
pattern that matches digits as the starting point. That name is special because .parse
uses it by default. In this example, you declare that with regex
:
Grammar 是一种特殊的包。它可以有方法和子程序,但主要包括称为 regex
,token
和 rule
的特殊模式方法。其中每个都定义了一个模式并应用了不同的修饰符。
Raku 倾向于将 regex ,token 和 rule 声明称为“规则”,有时可能有点不精确。在本书中,你可以通过排版来区分语言关键字和一般术语。我会尽量不提出模棱两可的情况。
|
从简单的东西开始(对于 grammar 来说太简单了)。定义匹配数字作为起点的 TOP
模式。该名称很特殊,因为 .parse
默认使用它。在此示例中,你使用 regex
声明一个 TOP
:
grammar Number {
regex TOP { \d }
}
my $result = Number.parse( '7' ); # works
put $result ?? 'Parsed!' !! 'Failed!'; # Parsed!
This succeeds. .parse
applies the grammar to the entire value of 7
. It starts with the parts that TOP
describes. It can match a digit, and the value you pass to .parse
is a digit.
When .parse
succeeds, it returns a [Match
](https://docs.raku.org/type/Match.html) object (it returns Nil
when it fails). Try it with a different value. Instead of a single digit, try several digits:
这成功了。 .parse
将 grammar 应用于整个值 7
. 它从 TOP
描述的部分开始。它可以匹配一个数字,你传递给 .parse
的值是一个数字。
当 .parse
成功时,它返回一个 [Match
](https://docs.raku.org/type/Match.html) 对象(当它失败时返回 Nil
)。尝试使用不同的值。尝试几个数字而不是单个数字:
my $result = Number.parse( '137' ); # fails (extra digits)
put $result ?? 'Parsed!' !! 'Failed!'; # Failed!
This time .parse
doesn’t succeed. It starts matching with the first character and ends matching on the last character. It asserts that the text starts, there is a single digit, and the text ends. If .parse
sees that there are some characters before or after its match, it fails. It matches everything or not at all. It’s almost the same thing as explicitly using anchors:
这次 .parse
没有成功。它开始与第一个字符匹配,并在最后一个字符上结束匹配。它断言文本开始,有一个数字,文本结束。如果 .parse
看到匹配之前或之后有一些字符,则会失败。它匹配全部或根本不匹配。它与显式地使用锚点几乎相同:
grammar Number {
regex TOP { ^ \d+ $ } # explicitly anchored
}
But TOP
is only the default starting point for a grammar. You can tell .parse
where you’d like to start. This version defines the same pattern but calls it digits
instead of TOP
:
但 TOP
是仅有的 grammar 的默认起点。你可以告诉 .parse
你想要开始的地方。此版本定义相同的模式但称为 digits
而不是 TOP
:
grammar Number {
regex digits { \d+ }
}
Tell .parse
where to start with the :rule
named argument:
使用 :rule
命名参数告诉 .parse
从哪里开始:
my @strings = '137', '137 ', ' 137 ';
for @strings -> $string {
my $result = Number.parse( $string, :rule<digits> );
put "「$string」 ", $result ?? 'Parsed!' !! 'Failed!';
}
The first element of @strings
parses because it is only digits. The other ones fail because they have extra characters:
@strings
的第一个元素解析成功了因为它只是数字。其他的失败了因为他们有额外的字符:
「137」 parsed!
「137 」 failed!
「 137 」 failed!
Declare digits
with rule
instead of regex
. This implicitly allows whitespace after any part of your pattern:
使用 rule
而不是 regex
声明 digits
。这隐式地允许在模式的任何部分之后有空格:
grammar Number {
rule digits { \d+ } # not anchored, and works
}
Now the second [Str
](https://docs.raku.org/type/Str.html) matches too because the implicit whitespace can match the space at the end (but not the beginning):
现在第二个 [Str
](https://docs.raku.org/type/Str.html) 也匹配,因为隐式空格可以匹配末尾的空格(但不是开头):
「137」 parsed!
「137 」 parsed!
「 137 」 failed!
The rule
applies :sigspace
to its pattern. It’s the same thing as adding that adverb to the pattern:
该 rule
将 :sigspace
应用到其模式。将该副词添加到模式中是一回事:
grammar Number {
regex digits { :sigspace \d+ }
}
:sigspace
inserts the predefined <.ws>
after pattern tokens. Since there’s a dot before the name ws
, the <.ws>
does not create a capture. It’s the same as adding optional whitespace explicitly:
:sigspace`在模式标记之后插入预定义的 `<.ws>
。由于名称 ws
之前有一个点号,<.ws>
不会创建捕获。它与显式添加可选空格相同:
grammar Number {
regex digits { \d+ <.ws> }
}
Instead of showing Parsed!
, you can on success output the [Match
](https://docs.raku.org/type/Match.html) object you stored in $result
:
你可以在成功输出存储在 $result
中的 [Match
](https://docs.raku.org/type/Match.html) 对象,而不是显示 Parsed!
grammar Number {
regex digits { \d+ <.ws> }
}
my @strings = '137', '137 ', ' 137 ';
for @strings -> $string {
my $result = Number.parse( $string, :rule<digits> );
put $result ?? $result !! 'Failed!';
}
The output isn’t that different, but instead of its success status you see the text that matched:
输出没有那么不同,但你可以看到匹配到的文本,而不是其成功状态:
「137」
「137 」
Failed!
Modify the grammar to remove that dot from <.ws>
so it captures whitespace and try again:
修改 grammar 以从 <.ws>
中删除该点号,以便捕获空格并再次尝试:
grammar Number {
regex digits { \d+ <ws> }
}
Now the output shows the nested levels of named captures:
现在输出显示了命名捕获的嵌套级别:
「137」
ws => 「」
「137 」
ws => 「 」
Failed!
This still doesn’t match the [Str
](https://docs.raku.org/type/Str.html) with leading whitespace. The parser couldn’t match that since rule
only inserts <.ws>
after explicit parts of the pattern. To match leading whitespace you need to add something to the front of the pattern. The beginning-of-string anchor does that, and now there’s something that <.ws>
can come after:
这仍然与带有前导空格的 [Str
](https://docs.raku.org/type/Str.html) 不匹配。解析器无法匹配,因为 rule
仅在模式的显式部分之后插入 <.ws>
。要匹配前导空格,你需要在模式的前面添加一些内容。字符串开头的锚点就是这样,现在有一些 <.ws>
后面可以出现的东西:
grammar Number {
rule digits { ^ \d+ } # ^ <.ws> \d+ <.ws>
}
There’s also the zero-width always-matches token, <?>
:
还有零宽度始终匹配的 token 标记,<?>
:
grammar Number {
rule digits { <?> \d+ } # <?> <.ws> \d+ <.ws>
}
Most of the time you don’t want to play these games. If you want leading whitespace, you can note that explicitly (and you probably don’t want to capture it):
大多数时候你不想玩这些游戏。如果你想要前导空格,你可以显式地注意到(并且你可能不想捕获它):
grammar Number {
rule digits { <.ws> \d+ } # <.ws> \d+ <.ws>
}
Use token
instead of rule
if you don’t want any implicit whitespace:
如果你不想要任何隐式空格,请使用 token
而不是 rule
:
grammar Number {
token digits { \d+ } # just the digits
}
You’ll see another feature of rule
and token
later in this chapter.
你将在本章后面看到 rule
和 token
的另一个功能。
EXERCISE 17.1Write a grammer to match octal digits, with or without a leading 0
or 0o
. Your grammar should parse numbers such as 123
, 0123
, and 0o456
, but not 8
, 129
, or o345
.
练习17.1写一个 grammar 来匹配八进制数字,带或不带前导 0
或 0o
。你的 grammar 应该解析诸如 123
, 0123
和 0o456
之类的数字,但不能解析 8
,129
或 o345
。
5.9. Multiple Rules
Grammars wouldn’t be useful if you were limited to one rule. You can define additional rules and use them inside other rules. In the first exercise you had only the TOP
rule but you could separate the pattern into parts. Break up the pattern in TOP
into rules for prefix
and digits
. It’s this decomposability that makes it so easy to solve hard parsing problems:
如果你只限于一条规则,那么 grammar 就没用了。你可以定义其他规则并在其他规则中使用它们。在第一个练习中,你只有 TOP
规则,但你可以将模式分成几部分。将 TOP
中的模式分解为 `prefix`和`digits`的规则。正是这种可分解性使得解决困难的解析问题变得如此简单:
grammar OctalNumber {
regex TOP { <prefix>? <digits> }
regex prefix { [ 0o? ] }
regex digits { <[0..7]>+ }
}
my $number = '0o177';
my $result = OctalNumber.parse( $number );
say $result // "failed";
The stringified [Match
](https://docs.raku.org/type/Match.html) object shows the overall match and the named subcaptures:
字符串化的 [Match
](https://docs.raku.org/type/Match.html) 对象显示整体匹配和命名的子捕获:
「0o177」
prefix => 「0o」
digits => 「177」
You can access the pieces:
你可以访问这些部分:
put "Prefix: $result<prefix>";
put "Digits: $result<digits>";
EXERCISE 17.2Create a grammar to match a Raku variable name with a sigil (ignore sigilless variables, because that’s too easy). Use separate rules to match the sigil and the identifier. Here is a list of candidates to check if you don’t come up with your own:`my @candidates = qw/ sigilless $scalar @array %hash $123abc $abc'123 $ab’c123 $two-words $two- $-dash /;`
You can suppress some of those named captures by prefixing the rule with a dot. You probably don’t care about the prefix, so don’t save it:
练习17.2 创建一个 grammar,匹配带有 sigil 的 Raku 变量名(忽略无符号变量,因为这太简单了)。使用单独的规则来匹配 sigil 和标识符。这是一个候选人列表,检查你是否没有自己的:my @candidates = qw/ sigilless $scalar @array %hash $123abc $abc'123 $ab’c123 $two-words $two- $-dash /;
你可以通过在规则前加一个点号来抑制某些命名捕获。你可能不关心前缀,所以不要保存它:
grammar OctalNumber {
regex TOP { <.prefix>? <digits> }
regex prefix { [ 0o? ] }
regex digits { <[0..7]>+ }
}
my $number = '0o177';
my $result = OctalNumber.parse( $number );
say $result // "failed";
The output doesn’t include the prefix information:
输出不包含前缀信息:
「0o177」
digits => 「177」
This doesn’t make much of a difference in this small example, but imagine a complicated grammar with many, many rules. That brings you to the next big feature of grammars. Besides the grammar itself, you can specify an action class that processes the rules as the grammar successfully parses them.
这在这个小例子中并没有太大的区别,但想象一下复杂的 grammar 有很多很多规则。这将带你进入 grammar 的下一个重要特征。除 grammar 本身外,你还可以指定一个 action 类来处理规则,因为 grammar 会成功解析它们。
5.10. Debugging Grammars
There are two modules that can help you figure out what’s going on in your grammar. Both are much more impressive in your terminal.
有两个模块可以帮助你弄清楚 grammar 中发生了什么。两者在你的终端中都更令人印象深刻。
5.10.1. Grammar::Tracer
The Grammar::Tracer
module shows you the path through a grammar (and applies to any grammar in its scope). Merely loading the module is enough to activate it:
Grammar::Tracer
模块向你显示 grammar 的路径(并适用于其作用域内的任何 grammar)。仅加载模块就足以激活它:
use Grammar::Tracer;
grammar OctalNumber {
regex TOP { <prefix>? <digits> }
regex prefix { [ 0o? ] }
regex digits { <[0..7]>+ }
}
my $number = '0o177';
$/ = OctalNumber.parse( $number );
say $/ // "failed";
The first part of the output is the trace. It shows which rule it’s in and the result. In this example each one matches:
输出的第一部分是跟踪。它显示了它所在的规则和结果。在这个例子中,每个规则都匹配:
TOP
| prefix
| * MATCH "0o"
| digits
| * MATCH "177"
* MATCH "0o177"
「0o177」
prefix => 「0o」
digits => 「177」
Changing the data to include invalid digits, such as 0o178
, means the grammar will fail. In the trace you can see it matches up to 0o17
but can’t continue, so you know where in your [Str
](https://docs.raku.org/type/Str.html) things went wrong. It could be that the grammar should not match the text or the grammar is not as accommodating as it should be:
更改数据以包含无效数字(例如 0o178
)意味着 grammar 将失败。在跟踪中,你可以看到它最多匹配到 0o17
但无法继续,因此你就知道 [Str
](https://docs.raku.org/type/Str.html) 中的哪些地方出错了。可能是 grammar 不应该与文本匹配,或者 grammar 不应该像它应该的那样适应:
TOP
| prefix
| * MATCH "0o"
| digits
| * MATCH "17"
* MATCH "0o17"
digits
* FAIL
digits
* MATCH "0"
failed
Instead of adding Grammar::Tracer
to your program you can load it from the command line with the `-M`switch. You probably don’t mean to leave it in anyway:
你可以使用 -M
开关从命令行加载 Grammar::Tracer
,而不是将 Grammar::Tracer
添加到程序中。你可能并不是故意把它留下来:
% raku -MGrammar::Tracer program.p6
5.10.2. Grammar::Debugger
The Grammar::Debugger
module does the same thing as Grammar::Tracer
(they come together in the same distribution) but allows you to proceed one step at a time. When you start it you get a prompt; type h
to get a list of commands:
Grammar::Debugger
模块与 Grammar::Tracer
(它们在同一个发行版中)执行相同的操作,但允许你一次执行一个步骤。当你启动它时,你得到一个提示; 键入 h
以获取命令列表:
% raku -MGrammar::Debugger test.p6
TOP
> h
r run (until breakpoint, if any)
<enter> single step
rf run until a match fails
r <name> run until rule <name> is reached
bp add <name> add a rule name breakpoint
bp list list all active rule name breakpoints
bp rm <name> remove a rule name breakpoint
bp rm removes all breakpoints
q quit
Typing Enter with no command single-steps through the parse process and gives you a chance to inspect the text and the state of the parser. The rf
command will get you to the next failing rule:
在没有命令的情况下键入回车键单步执行解析过程,并让你有机会检查文本和解析器的状态。` rf` 命令会使你进入下一个失败的规则:
> rf
| prefix
| * MATCH "0o"
| digits
| * MATCH "17"
* MATCH "0o17"
digits
* FAIL
>
5.11. A Simple Action Class
A grammar does its work by descending into its rules to take apart text. You can go the opposite way by processing each part of the parsed text to build a new [Str
](https://docs.raku.org/type/Str.html) (or data structure, or whatever you like). You can tell .parse
to use an action class to do this.
grammar 通过下降到它的规则中分解文本来完成其工作。你可以通过处理已解析文本的每个部分来构建新的 [Str
](https://docs.raku.org/type/Str.html)(或数据结构,或任何你喜欢的任何内容)。你可以告诉 .parse
使用 action 类来执行此操作。
Here’s a simple action class, OctalActions
. It doesn’t need to have the same name as the grammar, but the method names are the same as the rule names. Each method takes a [Match
](https://docs.raku.org/type/Match.html) object argument. In this example, the signature uses $/
, which is a variable with a few advantages that you’ll see in a moment:
这是一个简单的 action 类 OctalActions
。它不需要与 grammar 具有相同的名称,但方法名称与规则名称相同。每个方法都接收 [Match
](https://docs.raku.org/type/Match.html) 对象参数。在此示例中,签名使用 $/
,这是一个具有一些优势的变量,稍后你将看到:
class OctalActions {
method digits ($/) { put "Action class got $/" }
}
grammar OctalNumber {
regex TOP { <.prefix>? <digits> }
regex prefix { [ 0o? ] }
regex digits { <[0..7]>+ }
}
Tell .parse
which class to use with the :actions
named parameter. The name does not need to correspond to the grammar:
使用 :actions
命名参数告诉 .parse
使用哪个类。该名称不需要与 grammar 对应:
my $number = '0o177';
my $result = OctalNumber.parse(
$number, :actions(OctalActions)
);
say $result // "failed";
This action class doesn’t do much. When the digits
rule successfully matches it triggers the rule of the same name in the action class. That method merely outputs the argument:
这个 action 类做的不多。当 digits
规则成功匹配时,它会触发 action 类中相同名称的规则。该方法仅输出参数:
Action class got 177
「0o177」
digits => 「177」
EXERCISE 17.3Implement your own action class for the OctalNumber
grammar. When the digits
method matches, output the decimal version of the number. The parse-base
routine from [Str
](https://docs.raku.org/type/Str.html) may be useful. For extra credit, take one number per line from standard input and turn them into decimal numbers.
练习17.3 为 OctalNumber
grammar 实现自己的 action 类。当 digits
方法匹配时,输出数字的十进制版本。 [Str
](https://docs.raku.org/type/Str.html) 的 parse-base
例程可能很有用。如需额外学分,请从标准输入中每行获取一个数字并将其转换为十进制数字。
5.11.1. Creating an Abstract Syntax Tree
Actions shouldn’t output information directly. Instead, they can add values to the [Match
](https://docs.raku.org/type/Match.html) object. Calling make`in the action method sets a value in the abstract syntax tree (or `.ast
) slot of the [Match
](https://docs.raku.org/type/Match.html). You can access that with .made
:
Action 不应直接输出信息。相反,他们可以向 [Match
](https://docs.raku.org/type/Match.html) 对象添加值。在 action 方法中调用 make
会在 [Match
](https://docs.raku.org/type/Match.html) 的抽象语法树(或 .ast
)槽中设置一个值。你可以使用 .made
访问它:
class OctalActions {
method digits ($/) {
make parse-base( ~$/, 8 ) # must stringify $/
}
}
grammar OctalNumber {
regex TOP { <.prefix>? <digits> }
regex prefix { [ 0o? ] }
regex digits { <[0..7]>+ }
}
my $number = '0o177';
my $result = OctalNumber.parse(
$number, :actions(OctalActions)
);
put $result ??
"Turned 「{$result<digits>}」 into 「{$result<digits>.made}」"
!! 'Failed!';
The make
puts something into the .ast
slot of the [Match
](https://docs.raku.org/type/Match.html) and .made
gets it back out. You can make
any value that you like, including containers, objects, and most other things you can imagine. You still get the original, literal match.
In the previous example, the digits
action method handled the value. A TOP
action method could do it, but it has to reach one level below the [Match
](https://docs.raku.org/type/Match.html) object:
make
将一些内容放入[Match
](https://docs.raku.org/type/Match.html)的 .ast
插槽中,然后 .made
将其恢复原状。你可以`make` 任何你喜欢的值,包括容器,对象和你可以想象的大多数其他内容。你仍然得到原始的,字面上的匹配。
在前面的示例中,digits
action 方法处理了该值。 TOP
action 方法可以做到,但它必须到达 [Match
](https://docs.raku.org/type/Match.html) 对象下面的一个级别:
class OctalActions {
method digits ($/) {
make parse-base( ~$/, 8 ) # must stringify $/
}
}
grammar OctalNumber {
regex TOP { <.prefix>? <digits> }
regex prefix { [ 0o? ] }
regex digits { <[0..7]>+ }
}
my $number = '0o177';
my $result = OctalNumber.parse(
$number, :actions(OctalActions)
);
put $result.so ??
"Turned 「{$number}」 into 「{$result.made}」"
!! 'Failed!';
You don’t have to use $/
in the signature; it’s a convenience. There’s nothing particularly magical about it. You could use some other variable if you are paid by the character:
你不必在签名中使用 $/
; 这是一个方便写法。它没什么特别神奇的。如果你有其它字符,你可以使用其他变量:
class OctalActions {
method TOP ($match) { make parse-base( ~$match<digits>, 8 ) }
}
EXERCISE 17.4Create a grammar to parse a four-part, dotted-decimal IP address, such as 192.168.1.137
. Create an action class that turns the parse results into a 32-bit number. Output that 32-bit number in hexadecimal.
练习17.4 创建一个 grammar 来解析一个由四部分组成的点分十进制 IP 地址,例如 192.168.1.137
。创建一个 action 类,将解析结果转换为32位数。以十六进制输出那个32位数。
5.12. Ratcheting
The rule
and token
declarators have a feature that regex
doesn’t; they both prevent backtracking by implicitly setting the :ratchet
adverb. Once one of those rules matches they don’t backtrack to try again if there’s a failure later in the grammar.
Here’s a nonsense grammar that includes a rule <some-stuff>
that matches one or more of any character. The TOP
token wants to match digits surrounded by unspecified stuff:
rule
和 token
声明符具有 regex
不具有的功能;他们都通过隐式设置 :ratchet
副词来阻止回溯。一旦这些规则中的一个匹配,如果在 grammar 中稍后出现失败,则它们不会回溯以再次尝试。
这是一个无意义的 grammar,其中包含能匹配一个或多个字符的 <some-stuff>
规则。 TOP
token 想要匹配由未指定的东西包围的数字:
grammar Stuff {
token TOP { <some-stuff> <digits> <some-stuff> }
token digits { \d+ }
token some-stuff { .+ }
}
This [Str
](https://docs.raku.org/type/Str.html) could satisfy that pattern. It has stuff, some digits, and more stuff:
my $string = 'abcdef123xyx456';
But, Stuff
fails to parse it:
但是,Stuff
无法解析它:
my $result = Stuff.parse( $string );
put "「$string」 ", $result ?? 'Parsed!' !! 'Failed!'; # Failed!
It’s the :ratchet
that makes it fail. Work out its path to see why. TOP
has to first match <some-stuff>
. That matches any character one or more times, greedily—it matches the entire text. TOP
next needs to match`<digits>`, but there is nothing left to match because of that greediness. Without :ratchet
the pattern might roll back some of the characters it already consumed. With :ratchet
it doesn’t do that. The grammar can’t match the rest of TOP
and it fails.
Without :ratchet
the situation is different. If you use regex
instead of token
, you allow the grammar to give back characters it has already matched:
是 :ratchet
使它失败的。找出原因,看看为什么。 TOP
必须首先匹配 <some-stuff>
。这匹配任何一个字符一次或多次,贪婪地 - 它匹配整个文本。 TOP
接着需要匹配 <digits>
,但由于这种贪婪,没有什么可以匹配的了。如果没有 :ratchet
模式可能会回滚它已经消耗的一些字符。使用 :ratchet
它不会那样做。Grammar 不能匹配 TOP
的其余部分,所以失败了。
没有 :ratchet
的情况是不同的。如果使用 regex
而不是 token
,则允许 grammar 归还已匹配的字符:
grammar Stuff {
# regex does not turn on ratcheting
regex TOP { <some-stuff> <digits> <some-stuff> }
token digits { \d+ }
regex some-stuff { .+ }
}
That could match. The TOP
matches <some-stuff>
but realizes it’s run out of text and starts backtracking. All parts of the grammar that want to allow backtracking have to use regex
. It’s not good enough for TOP
to backtrack but not <some-stuff>
.
那可能会匹配。 TOP
匹配 <some-stuff>
,但意识到它已用完文本并开始回溯。想要允许回溯的 grammar 的所有部分都必须使用 regex
。对于 TOP
来说,回溯并不是足够好,除了 <some-stuff>
。
5.13. Parsing JSON
In Mastering Perl I presented a JSON parser that Randal Schwartz created using some advanced features of Perl 5 regular expressions. In many ways his implementation was a grammar, but he was forced to inseparably combine the parsing and the actions. That made the regular expression almost impenetrable. It’s much cleaner and more accessible to write it as a Raku grammar.
JSON is actually quite simple with only a few weird things to handle, but it gives you the opportunity to see how proto
rules can simplify actions:
在 Mastering Perl 中,我提到了一个 Randal Schwartz 使用 Perl 5 正则表达式的一些高级功能创建的 JSON 解析器。在许多方面,他的实现是一种 grammar,但他被迫不可分割地将解析和 action 组合在一块。这使得正则表达式几乎无法穿透。用 Raku grammar 编写它会更清晰,更容易访问。
JSON 实际上非常简单,只需处理几个奇怪的事情,但它让你有机会了解 proto
规则如何简化 action:
grammar Grammar::JSON {
rule TOP { <.ws> <value> <.ws> }
rule object { '{' ~ '}' <string-value-list> }
rule string-value-list { <string-value> * % ',' }
token string-value { <string> <.ws> ':' <.ws> <value> }
rule array { '[' ~ ']' <list> }
rule list { <value> * % ',' }
token value {
<string> | <number> | <object> | <array> |
<true> | <false> | <null>
}
token true { 'true' }
token false { 'false' }
token null { 'null' }
token string {
(:ignoremark \" ) ~ \"
[
<u_char> |
[ '\\' <[\\/bfnrt"]> ] |
<-[\\\"\n\t]>+
]*
}
token u_char {
'\\u' <code_point>
}
token code_point { <[0..9a..fA..F]>**4 }
token number {
'-' ?
[ 0 | <[1..9]><[0..9]>* ]
[ '.' <[0..9]>+ ]?
[ <[eE]> <[+-]>? <[0..9]>+ ]?
}
}
You may be surprised at how easy and short that grammar is. It’s almost a straight translation of the grammar from [RFC 8259](https://trac.tools.ietf.org/html/rfc8259). Now, create an action class for that:
你可能会对这个 grammar 的简单和简短感到惊讶。它几乎是 [RFC 8259](https://trac.tools.ietf.org/html/rfc8259) grammar 的直接翻译。现在,为此创建一个 action 类:
class JSON::Actions {
method TOP ($/) { make $<value>.made }
method object ($/) {
make $<string-value-list>.made.hash.item;
}
method array ($/) {
make $<list>.made.item;
}
method true ($/) { make True }
method False ($/) { make False }
method null ($/) { make Nil }
method value ($/) { make (
$<true> || $<false> || $<null> || $<object> ||
$<array> || $<string> || $<number> ).made
}
method string-value-list ($/) {
make $<string-value>>>.made.flat;
}
method string-value ($/) {
make $<string> => $<value>
}
method list ($/) { make ~$/ }
method string ($/) { make $<uchar>.made || ~$/ }
method u_char ($/) { make $<code_point>.made }
method code_point ($/) { make chr( (~$/).parse-base(16) ) }
method number ($/) { make +$/ }
}
Look at the clunky handling of value
. Almost anything can be a value, so the action method does some ham-handed work to figure out which thing just matched. It looks into the possible submatches to find one with a defined value. Well, that’s pretty stupid even if it’s a quick way to get started (although there is some value in the immediate stupid versus the far-off smart).
A proto
rule gets around this by making it easy for you to give different subrules the same name but different patterns. Instead of an alternation you have one token for each:
看看笨重的 value
处理。几乎任何东西都可以是一个值,所以 action 方法会做一些简单的工作来弄清楚哪个东西匹配。它查找可能的子匹配以找到具有定义值的子匹配。好吧,即使这是一个快速入门的方式,这也是非常愚蠢的(虽然在愚蠢的直接智能中存在一些价值)。
proto
规则可以让你轻松地为不同的子规则赋予相同的名称但不同的模式。不是备选分支,而是每个都有一个 token
:
proto token value { * }
token value:sym<string> { <string> }
token value:sym<number> { <number> }
token value:sym<object> { <object> }
token value:sym<array> { <array> }
token value:sym<true> { <sym> }
token value:sym<false> { <sym> }
token value:sym<null> { <sym> }
The first proto
rule matches *
, which really means it dispatches to another rule in that group. It can dispatch to all of them and find the one that works.
Some of these use the special <sym>
subrule in their pattern. This means that the name of the rule is the literal text to match. The proto
rule <true>
matches the literal text true
. You don’t have to type that out in the name and the pattern.
It doesn’t matter which of those matches; the grammar calls each of them $<value>
. The superrule only knows that something that is a value matched and that the subrule handled it appropriately. The action class make`s the right value and stores it in the [`Match
](https://docs.raku.org/type/Match.html):
第一个 proto
规则匹配 *
,这实际上意味着它将分派给该组中的另一个规则。它可以发送给所有人并找到有效的。
其中一些在其模式中使用特殊的 <sym>
子规则。这意味着规则的名称是要匹配的文字文本。 proto
规则 <true>
匹配文字文本 true
。你不必在名称和模式中输入该内容。
哪些匹配无关紧要; grammar 调用每个 $<value>
。超级规则只知道值匹配的东西,并且子规则适当地处理它。 action 类生成正确的值并将其存储在 [Match
](https://docs.raku.org/type/Match.html) 中:
class JSON::Actions {
method TOP ($/) { make $<value>.made }
method object ($/) { make $<string-value-list>.made.hash.item }
method string-value-list ($/) { make $<string-value>>>.made.flat }
method string-value ($/) {
make $<string>.made => $<value>.made
}
method array ($/) { make $<list>.made.item }
method list ($/) { make [ $<value>.map: *.made ] }
method string ($/) { make $<uchar>.made || ~$/ }
method value:sym<number> ($/) { make +$/.Str }
method value:sym<string> ($/) { make $<string>.made }
method value:sym<true> ($/) { make Bool::True }
method value:sym<false> ($/) { make Bool::False }
method value:sym<null> ($/) { make Any }
method value:sym<object> ($/) { make $<object>.made }
method value:sym<array> ($/) { make $<array>.made }
method u_char ($/) { make $<code_point>.made }
method code_point ($/) { make chr( (~$/).parse-base(16) ) }
}
EXERCISE 17.5Implement your own JSON parser (steal all the code you like). Test it against some JSON files to see how well it works. You might like to try the JSON files at [https://github.com/briandfoy/json-acceptance-tests
练习17.5实现自己的 JSON 解析器(窃取你喜欢的所有代码)。针对某些 JSON 文件进行测试,看看它的工作情况。你可能想在 [https//github.com/briandfoy/json-acceptance-tests](https//github.com/briandfoy/json-acceptance-tests) 上尝试 JSON文件。
5.14. Parsing CSV
Let’s parse some comma-separated values (CSV) files. These are tricky because there’s no actual standard (despite [RFC 4180](https://tools.ietf.org/html/rfc4180)). Microsoft Excel does it one way but some other producers do it slightly differently.
People often initially go wrong thinking they can merely split the data on a comma character—but that might be part of the literal data in a quoted field. The quote character may also be part of the literal data, but one producer might escape internal quote marks by doubling them, ""
, while another might use the backslash, \"
. People often assume they are line-oriented, but some producers allow unescaped (but quoted!) vertical whitespace. If all of that wasn’t bad enough, what do you do if one line has fewer (or more) fields than the other lines?
让我们解析一些逗号分隔值(CSV)文件。这些都很棘手,因为没有实际的标准(尽管有despite [RFC 4180](https://tools.ietf.org/html/rfc4180) )。 Microsoft Excel 以一种方式实现,但其他一些生产商则略有不同。
最初人们通常认为他们只能按照逗号字符拆分数据 - 但逗号可能是引用字段中字面量数据的一部分。引号字符也可能是字面量数据的一部分,但是有些制作人可能会通过两个双引号 ""
来避免内部引号,而另一个可能会使用反斜杠,\"
。人们通常认为它们是面向行的,但是一些制作人允许未转义的(但引起来!)垂直空白。如果所有这些都不够糟糕,如果一行的字段少于(或多于)其他行,你会怎么做?
Don’t parse CSV files like this. The Text::CSV
module not only parses the format but also tries to correct problems as it goes.
不要像这样解析 CSV 文件。 Text::CSV
模块不仅可以解析格式,还可以尝试纠正问题。
Still willing to give it a try? You should find that grammars make most of these concerns tractable:
仍然愿意尝试一下?你应该发现 grammar 使大多数这样的问题易于处理:
-
The ratcheting behavior keeps things simple.
-
You can easily handle balanced openers and closers (i.e., the quoting stuff).
-
A grammar can inherit other grammars, so you can adjust a grammar based on the data instead of writing one grammar that handles all the data.
-
You’ve seen action classes, but you can also have action instances that remember extra non-[
Match
](https://docs.raku.org/type/Match.html) data. -
There’s a
.subparse
method that lets you parse chunks so you can handle one record at a time. -
棘轮行为使事情变得简单。
-
你可以轻松地处理平衡的开口和闭合(即引用的东西)。
-
grammar 可以继承其他 grammar,因此你可以根据数据调整 grammar,而不是编写一个处理所有数据的 grammar。
-
你已经看过 action 类,但你也可以拥有记住额外非[
匹配
](https://docs.raku.org/type/Match.html)数据的 action 实例。 -
有一个
.subparse
方法,可以让你解析块,这样你就可以一次处理一条记录。
Here’s a simple CSV grammar based off the rules in [RFC 4180](https://tools.ietf.org/html/rfc4180). It allows for quoted fields and uses ""
to escape a literal quote. If a comma, quote, or vertical whitespace appears in the literal data, it must be quoted:
这是一个简单的 CSV grammar,基于 [RFC 4180](https://tools.ietf.org/html/rfc4180) 中的规则。它允许引用的字段并使用 ""
来避免字面量引号。如果字面量数据中出现逗号,引号或垂直空格,则必须引起它:
grammar Grammar::CSV {
token TOP { <record>+ }
token record { <value>+ % <.separator> \R }
token separator { <.ws> ',' <.ws> }
token value {
'"' # quoted
<( [ <-["]> | <.escaped-quote> ]* )>
'"'
|
<-[",\n\f\r]>+ # non-quoted (no vertical ws)
|
'' # empty
}
token escaped-quote { '""' }
}
class CSV::Actions {
method record ($/) { make $<value>».made.flat }
method value ($/) {
# undo the double double quote
make $/.subst( rx/ '""' /, '"', :g )
}
}
Try this on entire files. The entire file either satisfies this grammar or doesn’t:
在整个文件上试试这个。整个文件要么满足这个 grammar,要么不满足:
my $data = $filename.IO.slurp;
my $result = Grammar::CSV.parse( $data );
You typically don’t want to parse entire files, though. Let’s fix the first part of that problem. You want to process records as you run into them. Instead of using .parse
, which anchors to the end of the text, you can use .subparse
, which doesn’t. This means you can parse part of the text then stop.
You can deal with one record at a time. Using .subparse
with the record
rule gets you the first record and only the first record. The .subparse
method always returns a [Match
](https://docs.raku.org/type/Match.html), unlike .parse
, which only returns a [Match
](https://docs.raku.org/type/Match.html) when it succeeds. You can’t rely on the type of the object as an indication of success:
但是,你通常不希望解析整个文件。让我们解决这个问题的第一部分。你希望在遇到记录时处理记录。你可以使用 .subparse
,而不是使用锚定到文本末尾的 .parse
, .subparse
不会锚定到文本末尾。这意味着你可以解析部分文本然后停止。
你可以一次处理一条记录。将 .subparse
与 record
规则一起使用可以获得第一条记录,并且只获得第一条记录。与 .parse
不同,.subparse
方法总是返回一个 [Match
](https://docs.raku.org/type/Match.html),.parse
方法只在成功时返回一个 [Match
](https://docs.raku.org/type/Match.html)。你不能依赖对象的类型作为成功的指示:
my $data = $filename.IO.slurp;
my $first_result = Grammar::CSV.subparse(
$data, :rule('record'), :action(CSV::Actions)
);
if $first-result { ... }
That works for the first line. Use :c(N)
to tell these methods where to start in the [Str
](https://docs.raku.org/type/Str.html). You have to know where you want to start. The [Match
](https://docs.raku.org/type/Match.html) knows how far it got; look in the .from
slot:
这适用于第一行。使用 :c(N)
告诉这些方法在 [字符串
](Match
(https://docs.raku.org/type/Match.html) 知道它进行了多远;看看 .from
插槽:
my $data = $filename.IO.slurp;
loop {
state $from = 0;
my $match = Grammar::CSV.subparse(
$data,
:rule('record'),
:actions(CSV::Actions),
:c($from)
);
last unless $match;
put "Matched from {$match.from} to {$match.to}";
$from = $match.to;
say $match;
}
This is most of the way to a solution—it fails to go through the entire file if .subparse
fails on one record. With some boring monkey work you could fix this to find the start of the next record and restart the parsing, but that’s more than I want to fit in this book.
这是解决方案的大部分方法 - 如果 .subparse
在一条记录上失败,则无法遍历整个文件。使用一些无聊的猴子工作,你可以修复这个问题,找到下一条记录的开始并重新开始解析,但这比我想要适应本书更多。
5.14.1. Adjusting the Grammar
You thought the problem was solved. Then, someone sent you a file with a slightly different format. Instead of escaping a "
by doubling it, the new format uses the backslash.
Now your existing grammar fails to parse. You don’t have a rule that satisfies that type of escape because you didn’t need it for your grammar. As a matter of practice in both patterns and grammars, only match what you should match. Be liberal in what you accept in other ways, such as making a subgrammar to handle the new case:
你以为问题已经解决了。然后,有人给你发送了一个格式略有不同的文件。新格式使用反斜杠,而不是使用两个引号转义 "
。
现在你现有的 grammar 解析失败。你没有满足该类型的转义的规则,因为你的 grammar 不需要它。作为模式和 grammar 的练习,只匹配你应该匹配的内容。在其他方面随心所欲,例如制作一个子 grammar 来处理新案例:
grammar Grammar::CSV::Backslashed is Grammar::CSV {
token escaped-quote { '\\"' }
}
class CSV::Actions::Backslashed is CSV::Actions {
method value ($/) { make $/.subst( rx/ '\\"' /, '"', :g ) }
}
With two grammars, how do you get the one that you need to use? The name interpolation ::($
name)
comes in handy here:
有两个 grammar,你如何得到你需要使用的那个?::($
name)
在这里派上用场:
my %formats;
%formats<doubled> = {
'file' => $*SPEC.catfile( <corpus test.csv> ),
'grammar' => 'Grammar::CSV',
};
%formats<backslashed> = {
'file' => $*SPEC.catfile( <corpus test-backslash.csv> ),
'grammar' => 'Grammar::CSV::Backslashed',
};
for %formats.values -> $hash {
$hash<data> = $hash<file>.IO.slurp;
my $class = (require ::( $hash<grammar> ) );
my $match = $class.parse( $hash<data> );
say "{$hash<file>} with {$hash<grammar>} ",
$match ?? 'parsed' !! 'failed';
}
The %formats
[Hash
](https://docs.raku.org/type/Hash.html) of [Hash
](https://docs.raku.org/type/Hash.html)es stores the filenames and the grammars for them. You can load a grammar and use it to parse the data without the explicit grammar name:
%formats
[散列
](散列
(https://docs.raku.org/type/Hash.html)存储文件名和 grammar。你可以加载 grammar 并使用它来解析数据而不使用显式的 grammar 名称:
corpus/test.csv with Grammar::CSV parsed
corpus/test-backslash.csv with Grammar::CSV::Backslashed parsed
That mostly solves the problem, although there are plenty of special cases that this doesn’t cover.
这主要解决了这个问题,尽管有很多特殊情况并没有涵盖。
5.14.2. Using Roles in Grammars
Roles can supply rules and methods that grammars can use. In the previous section you handled different sorts of double-quote escaping through inheritance, where you overrode the rule. You can do the same thing with roles.
A grammar can have methods and subroutines. The way you declare a name with sub
, method
, or rule
tells the language parser (not your grammar!) how to parse the stuff in the [Block
](https://docs.raku.org/type/Block.html).
First, adjust the main grammar to have a stub method for <escaped-quote>
. This forces something else to define it:
角色可以提供 grammar 可以使用的规则和方法。在上一节中,你通过继承处理了不同类型的双引号转义,其中你重写了规则。你可以用角色做同样的事情。
Grammar 可以有方法和子程序。使用 sub
,method`或 `rule
声明名称的方式告诉语言解析器(而不是 grammar!)如何解析[块
](https://docs.raku.org/type/Block.html)中的东西。
首先,调整主 grammar,使其具有 <escaped-quote>
的存根方法。这迫使别人定义它:
grammar Grammar::CSV {
token TOP { <record>+ }
token record { <value>+ % <.separator> \R }
token separator { <.ws> ',' <.ws> }
token value {
'"' # quoted
<( [ <-["]> | <.escaped-quote> ]* )>
'"'
|
<-[",\n\f\r]>+ # non-quoted (no vertical ws)
|
'' # empty
}
# stub that you must define in a role
method escaped-quote { !!! }
}
A role will fill in that stub method. There’s one role for each way to escape the double quote:
角色将填充该存根方法。每种方式都有一个角色来转义双引号:
role DoubledQuote { token escaped-quote { '""' } }
role BackslashedQuote { token escaped-quote { '\\"' } }
When it’s time to parse a file you can choose which role you want to use. You can create a new object for Grammar::CSV
and apply the appropriate role to it:
在解析文件时,你可以选择要使用的角色。你可以为 Grammar::CSV
创建一个新对象并将适当的角色应用于它:
my $filename = ...;
my $csv-data = $filename.IO.slurp;
my $csv-parser = Grammar::CSV.new but DoubledQuote;
Use that object to parse your data:
使用该对象解析数据:
my $match = $csv-parser.parse: $csv-data;
say $match // 'Failed!';
Doing this doesn’t fix the double quotes in the data—a ""
stays as a ""
—but you can fix that in an action class.
EXERCISE 17.6Adjust the CSV example to use roles instead of inheritance. Create an action class to adjust the escaped double quotes as you run into them. You can start with Grammars/test.csv from the downloads section of [the book’s website](https://www.learningraku.com/) if you like.
这样做不会修复数据中的双引号 - ""
保留为 ""
- 但你可以在 action 类中修复它。
练习17.6 调整 CSV 示例以使用角色而不是继承。创建一个 action 类,以便在遇到它们时调整转义的双引号。如果你愿意,可以从本书网站的下载部分 Grammars/ test.csv 开始。
5.15. Summary
Grammars are one of the killer features of the language. You can define complex relationships between patterns and use action classes to run arbitrarily complex code when something matches. You might find that your entire program ends up being one big grammar.
Grammars 是 Raku 语言的杀手级特性之一。你可以定义模式之间的复杂关系,并在匹配时使用 action 类来运行任意复杂的代码。你可能会发现整个程序最终变成一个大的 grammar。 == 供应、通道和承诺
5.16. Supplier
Supplier 向每一个要求接收其消息的 Supply 发出消息。这是异步发生的。当你的程序在做其它的事情时, 它们也在做它们的工作。你可以在后台处理事情, 并在结果到来时进行处理, 而不是停止整个程序来等待所有的数据。其他语言可能称之为“发布 - 订阅”(或“PubSub”)。
my $supplier = Supplier.new;
$supplier.emit: 3;
my $supplier = Supplier.new;
my $supply = $supplier.Supply;
my $tap = $supply.tap: { put "$^a * $^a = ", $^a**2 };
$supplier.emit: 3;
3 * 3 = 9
my $fifth-second = Supply.interval: 0.2;
$fifth-second.tap: { say "First: $^a" };
sleep 1;
输出显示五行。为什么只有五行?sleep
结束后, 距离程序结束还有五分之一秒的时间:
First: 0
First: 1
First: 2
First: 3
First: 4
一旦你启动了 tap, 它就会持续以异步的方式处理值, 直到程序结束(或你关闭 tap)。当你的程序到达 sleep
语句时, 会发生两件事。首先, 程序会等待你指定的时间量。第二, Supplier 会发出 tap 处理的值。这两件事是同时发生的。当你休眠时, Supplier 仍在工作。所有这些都在几行代码中完成!
并发不是并行。并发允许在重叠时间帧期间处理两个不同的事情。并行意味着两个不同的事情在同一时间发生。不过, 人们往往对其定义模糊不清。 |
如果你拿走 sleep
语句, 你将无法获得任何输出 - 程序会立即结束。Supplier 不会让程序持续运行。如果增加 sleep
时间以使程序运行更长时间, 则可获得更多的输出。
下面是一个永远循环但只产生一行的计数器。回车会回到行的开头但不推进行(虽然终端缓冲可能会干扰):
my $fifth-second = Supply.interval: 0.2;
$fifth-second.tap: { print "\x[D]$^a" };
loop { }
5.16.1. 多个 tap
这不仅仅只限于一个 tap。你可以在同一个 Supply 上拥有任意数量的 tap。下面这个程序将需要两秒钟才能完成。第一个 tap 会运行两秒钟, 第二个 tap 会在最后一秒运行:
my $supply = Supply.interval: 0.5;
$supply.tap: { say "First: $^a" };
sleep 1;
$supply.tap: { say "Second: $^a" };
sleep 1;
每个 tap 标记其输出:
First: 0
First: 1
Second: 0
First: 2
First: 3
Second: 1
注意这里有什么奇怪的东西吗?第二个 tap 再次从 0 开始, 而不是同时获得与第一个 tap 相同的数字。.interval
方法创建一个按需供应(on-demand supply)。当 tap 要求它们时它开始产生值, 并且它为每个新 tap 产生新的间隔。每次 tap 想要一个值时, 它就会获得下一个值, 并且与任何其他 tap 无关。
在该代码再次使用其他值运行之前, tap
中的代码必须完全完成。这可确保你的代码在具有持久变量时不会混淆。如果此 Block 在第一次运行完成之前再次运行, 则 $n
的值将在第一次运行输出其消息之前自增几次:
$supply.tap: {
state $n = 0; $n++;
sleep 1; # misses a couple of emitted values!
say "$n: $^a"
};
5.16.2. 现场供应
现场供应(live supply)与你目前遇到的按需供应(on-demand supply)不同。它会发出所有分流器(taps)共享的单个值流。当有新值时, 即使没有 tap 读取旧值, 也会丢弃旧值。每个新 tap 都以该单个流的当前值开始。使用 .share
将按需供应(on-demand supply)转变为现场供应(live supply):
my $supply = Supply.interval(0.5).share;
$supply.tap: { say "First: $^a" };
sleep 1;
$supply.tap: { say "Second: $^a" };
sleep 1;
输出在两个方面有所不同。首先, 0
值没有了。在第一次 tap 有机会看到它之前, Supply 就发出了它。一秒钟后, 第二个 tap 开始, Supply 发出数字 2
; 两个 tap 都看到数字 2
。之后, 这两个 tap 在程序结束前持续看到相同的值:
First: 1
First: 2
Second: 2
First: 3
Second: 3
First: 4
Second: 4
当你不再需要 tap 时, 你可以关闭它; 它将不再接收值:
my $supply = Supply.interval(0.4).share;
my $tap1 = $supply.tap: { say "1. $^a" };
sleep 1;
my $tap2 = $supply.tap: { say "2. $^a" };
sleep 1;
$tap1.close;
sleep 1;
在开始时, 第一个 tap 处理一切。第一次 sleep
结束后, 第二个 tap 启动。然后两个 tap 都处理了一秒钟, 然后第一个 tap 关闭, 只有第二个 tap 仍在工作:
First: 1
First: 2
First: 3
Second: 3
First: 4
Second: 4
Second: 5
Second: 6
Second: 7
my $supply = $*ARGFILES.lines.Supply; # IO::ArgFiles
$supply.tap: { put $++ ~ ": $^a" };
$supply.tap: {
state %Seen;
END { put "{%Seen.keys.elems} unique lines" }
%Seen{$^a}++;
};
大多数列表(或可以转变成列表的结构)可以做到这一点:
my $list = List.new: 1, 4, 9, 16;
my $supply = $list.Supply;
$supply.tap: { put "Got $^a" }
甚至无限序列也可以:
my $seq := 1, 2, * + 1 ... *;
my $supply2 = $seq.Supply;
$supply2.tap: { put "Got $^a" }
请注意, 这些示例不需要 sleep
来延迟程序结束。它们不像 .interval
那样"耗费剩余的时间"; 他们遍历每一个值。
练习 18.2 创建一个每秒发射一个数字的实时供应(Supply)。三秒钟后, tap 它并输出它射发出的数字。再过三秒钟, 再次 tap 它输出相同的东西。再等三秒钟, 然后关闭第二个 tap。最后, 再过三秒后关闭第一个 tap。
5.17. Channel
Channel 是先到先得的队列。他们确保东西只被准确地处理一次。可以将任何东西放到 Channel 中, 也可以从 Channel 中将拿走任何东西。Channel 两侧的代码无需了解另一方。几个线程可以共享一个 Channel, 但是一旦下一个请求的东西从 Channel 中消失了, 那么就不能被其他代码处理了。
my $channel = Channel.new;
$channel.send: 'Hamadryas';
put 'Received: ', $channel.receive;
$channel.close;
输出显示的是你刚刚添加的值:
Received: Hamadryas
在关闭(.close
) Channel 之后, 你无法向 Channel 发送更多值。你已添加的任何东西仍在 Channel 中, 可供处理。你可以从 Channel 中接收(.receive
) 东西, 直到 Channel 为空:
my $channel = Channel.new;
$channel.send: $_ for <Hamadryas Rhamma Melanis>;
put 'Received: ', $channel.receive;
$channel.close; # no more sending
while $channel.poll -> $thingy {
put "while received $thingy";
}
while
循环使用 .poll
而不是 .receive
。如果 Channel 中还有东西, .poll
会返回它。如果当前没有更多东西可用, 则返回 Nil
(结束循环):
Received: Hamadryas
while received Rhamma
while received Melanis
当 .poll
返回 Nil
时, 你不知道是否还有更多东西。如果 Channel 仍处于打开状态, 可以添加更多东西; 如果 Channel 关闭, 将永远不会有更多的东西可以接收(.receive
)。调用 .fail
会关闭 Channel, 如果再次调用, 则 .receive
会抛出错误。你可以捕获异常以结束循环:
my $channel = Channel.new;
$channel.send: $_ for <Hamadryas Rhamma Melanis>;
put 'Received: ', $channel.receive;
$channel.fail('End of items'); # X::AdHoc
loop {
CATCH {
default { put "Channel is closed"; last }
}
put "loop received: ", $channel.receive;
}
你可以 tap
Channel 代替 loop 循环。它为你调用 .receive
:
my $channel = Channel.new;
$channel.send: $_ for <Hamadryas Rhamma Melanis>;
put 'Received: ', $channel.receive;
$channel.fail('End of items');
$channel.Supply.tap: { put "Received $_" }
CATCH { default { put "Channel is closed" } }
输出相同:
Received: Hamadryas
loop received: Rhamma
loop received: Melanis
Channel is closed
5.18. Promise
Promise 是一小片会在稍后产生结果的代码, 所说的"稍后"可能不会很快。它把工作安排到另一个线程中, 而程序的其余部分继续。这些是 Raku 并发性的基础, 它们为你完成了大部分艰苦的工作。
每个 Promise 都有一个状态。它可能正在等待运行, 当前正在运行或已完成运行。它如何完成取决于它的状态:Promise 在成功时是 Kept
, 在失败时是 Broken
。当它正在运行时是 Planned
。
my $five-seconds-from-now = Promise.in: 5;
loop {
sleep 1;
put "Promise status is: ", $five-seconds-from-now.status;
}
Promise status is: Planned
Promise status is: Planned
Promise status is: Planned
Promise status is: Planned
Promise status is: Kept
Promise status is: Kept
...
你没必要不断检查 Promise。使用 .then
设置代码在 Promise 变为 kept
状态时运行:
my $five-seconds-from-now = Promise.in: 5;
$five-seconds-from-now.then: { put "It's been 5 seconds" };
你可以给程序足够的时间去消耗五秒钟。sleep
延长了程序时间:
my $five-seconds-from-now = Promise.in: 5;
$five-seconds-from-now.then: { put "It's been 5 seconds" };
sleep 7;
现在你看到 .then
代码中的输出:
It's been 5 seconds
5.18.1. 等待承诺
你可以使用 await
而不是 sleep
(并猜测你需要空闲的时间)来阻塞你的程序, 直到 Promise 的状态变为 Kept 或 Broken:
my $five-seconds-from-now = Promise.in: 5;
$five-seconds-from-now.then: { put "It's been 5 seconds" };
await $five-seconds-from-now;
这些示例使用 await
, 因为你需要程序保持运行。更有趣的是, 你的程序很可能会做很多其他的工作, 所以你可能不需要让程序持续作用。
my $later = Promise.at: now + 7;
$later.then: { put "It's now $datetime" };
await $later;
my $pause = start {
put "Promise starting at ", now;
sleep 5;
put "Promise ending at ", now;
};
await $pause;
输出显示 Promise 的开始和结束:
Promise starting at Instant:1507924913.012565
Promise ending at Instant:1507924918.018444
如果 Promise 抛出异常, 承诺就被破坏了。你可以返回所有你喜欢的 False
值, 但是直到你失败或抛出一个带有错误的 Exception, 你的 Promise 将被兑现。即使它返回 False
, 这也会成功:
my $return-false = start {
put "Promise starting at ", now;
sleep 5;
put "Promise ending at ", now;
return False; # still kept
};
await $return-false;
下面这个例子打破了 Promise, 因为你显式地调用了 fail
:
my $five-seconds-from-now = start {
put "Promise starting at ", now;
sleep 5;
fail;
put "Promise ending at ", now;
};
await $five-seconds-from-now;
你得到了一部分输出, 但在你获得其余的输出之前, fail
就停止这个 Block:
Promise starting at Instant:1522698239.054087
An operation first awaited:
in block <unit> at ...
Died with the exception:
Failed
in block at ...
5.18.2. 等待多个承诺
await
可以接受一个 Promise 列表:
put "Starting at {now}";
my @promises =
Promise.in( 5 ).then( { put '5 finished' } ),
Promise.in( 3 ).then( { put '3 finished' } ),
Promise.in( 7 ).then( { put '7 finished' } ),
;
await @promises;
put "Ending at {now}";
在所有的承诺没有兑现(Kept)之前, 这个程序不会结束:
Starting at Instant:1524856233.733533
3 finished
5 finished
7 finished
Ending at Instant:1524856240.745510
put "Starting at {now}";
my @promises =
start { sleep 5; fail "5 failed" },
Promise.in( 3 ).then( { put '3 finished' } ),
Promise.in( 7 ).then( { put '7 finished' } ),
;
await @promises;
put "Ending at {now}";
如果 .in( 3 )
承诺被兑现(Kept)了 , 那么带 start
的那个承诺就会失败:
Starting at Instant:1524856385.367019
3 finished
An operation first awaited:
in block <unit> at await-list.p6 line 9
Died with the exception:
5 failed
in block at await-list.p6 line 4
5.18.3. 管理自己的承诺
my $promise = Promise.new;
通过智能匹配 PromiseStatus 中的常量(你可以免费获得)来检查其状态:
put do given $promise.status {
when Planned { "Still working on it" }
when Kept { "Everything worked out" }
when Broken { "Oh no! Something didn't work" }
}
此时, $promise
已经计划好了, 并将保持这种方式。这将永远循环下去:
loop {
put do given $promise.status {
when Planned { "Still working on it" }
when Kept { "Everything worked out" }
when Broken { "Oh no! Something didn't work" }
}
last unless $promise.status ~~ Planned;
sleep 1;
}
你可以用 now
记下开始的时间, 并检查是否在五秒后制作你自己的 .at
或 .in
。五秒后的一段时间, 你可以调用 .keep
来改变状态:
my $promise = Promise.new;
my $start = now;
loop {
$promise.keep if now > $start + 5;
given $promise.status {
when Planned { put "Still working on it" }
when Kept { put "Everything worked out" }
when Broken { put "Oh no! Something didn't work" }
}
last unless $promise.status ~~ Planned;
sleep 1;
}
现在, 循环在五秒后停止:
Still working on it
Still working on it
Still working on it
Still working on it
Still working on it
Everything worked out
这个 Promise 仍然可以调用带有 .then
的代码:
my $promise = Promise.new;
$promise.then: { put "Huzzah! I'm kept" }
my $start = now;
loop { ... } # same as before
该输出显示了 .then
代码的输出:
Still working on it
Still working on it
Still working on it
Still working on it
Still working on it
Everything worked out
Huzzah! I'm kept
my $promise = Promise.new;
$promise.then: {
put do given .status {
when Kept { 'Huzzah!' }
when Broken { 'Darn!' }
}
}
my $start = now;
loop {
$promise.break if now > $start + 5;
last unless $promise.status ~~ Planned;
sleep 1;
}
5.18.4. Promise Junctions
你可以使用 Junction 来创建一个超级 Promise。.allof
方法创建一个 Promise, 如果它包含的所有 Promise 都被兑现了(Kept), 那么这个 Promise 就会被兑现:
my $all-must-pass = await Promise.allof:
Promise.in(5).then( { put 'Five seconds later' } ),
start { sleep 3; put 'Three seconds later'; },
Promise.at( now + 1 ).then( { put 'One second later' } );
put $all-must-pass;
my $any-can-pass = await Promise.anyof:
Promise.in(5).then( { put 'Five seconds later' } ),
start { sleep 3; put 'Three seconds later'; fail },
Promise.at( now + 1 ).then( { put 'One second later' } );
put $any-can-pass;
这两种情况都成功了。在 .allof
情况下, 你看到的是所有三个 Promise 的输出。然后, 你看到的是 .anyof
中的一个 Promise 的输出。并非所有这些 Promise 都需要完成, 因为整体的 Promise 已经知道它可以成功:
One second later
Three seconds later
Five seconds later
True
One second later
True
5.19. 响应式编程
react
Block 允许你在有新的值时运行一些代码。它一直运行, 直到它用完要处理的值。它类似于事件循环。下面是一个非常简单的例子:
react {
whenever True { put 'Got something that was true' }
}
END put "End of the program";
你使用 whenever
为 Block 块提供值。在这个例子中, 你有一个单一的值 True
。这不是一个条件表达式或测试, 如 if
或 while
。Block 会对这个单一的值作出反应, 并运行 whenever
代码。之后就没有更多的值了, Block 就退出了:
Got something that was true
End of the program
你可能会认为这是一个循环结构, 但这不是一回事。它并不是在 react
Block 中做完所有的事情, 然后再重新启动 Block。True
的 whenever
只运行一次, 而不是像你期望的 loop
那样永远运行:
loop {
if True { put 'Got something that was true' }
}
把 whenever
从 True
改为 Supply.interval
, 就再也看不到程序结束的消息了:
my $supply = Supply.interval: 1;
react {
whenever $supply { put "Got $^a" }
}
END put "End of the program";
Got 0
Got 1
Got 2
...
你可以同时拥有 Supply 和 True
:
my $supply = Supply.interval: 1;
react {
whenever $supply { put "Got $^a" }
whenever True { put 'Got something that was true' }
}
END put "End of the program";
带有 Supply 的 whenever
立即作出反应, 并输出 Supply 中的第一个值。带有 True
的 whenever
则立即作出反应, 并耗尽其值(单个 True
)。之后, Supply 会继续运行, 直到你放弃并中断程序:
Got 0
Got something that was true
Got 1
Got 2
...
如果你把两个 whenever
的顺序对调, 那么 True
可能会先作出反应:
my $supply = Supply.interval: 1;
react {
whenever True { put 'Got something that was true' }
whenever $supply { put "Got $^a" }
}
END put "End of the program";
输出略有不同, 但并没有说一定要这样。也许未来的实现会有不同的选择。这就是并发, 你不能依赖严格的发生顺序:
Got something that was true
Got 0
Got 1
Got 2
...
my $supply = Supply.interval: 1;
react {
whenever $supply { put "Got $^a" }
whenever True { put 'Got something that was true' }
whenever Promise.in(5) { put 'Timeout!'; done }
}
END put "End of the program";
5 秒后, Promise 被兑现, 然后 whenever
被启动。它输出超时消息, 并使用 done
结束 react
:
Got 0
Got something that was true
Got 1
Got 2
Got 3
Got 4
Got 5
Timeout!
End of the program
添加另一个 react
, 然后用新的 Supply 重新开始这个过程:
my $supply = Supply.interval: 1;
react {
whenever $supply { put "Got $^a" }
whenever True { put 'Got something that was true' }
whenever Promise.in(5) { put 'Timeout!'; done }
}
put "React again";
react {
whenever $supply { put "Got $^a" }
}
END put "End of the program";
Supply 的输出又开始了, 但在间隔的开始:
Got 0
Got something that was true
Got 1
Got 2
Got 3
Got 4
Timeout!
React again
Got 0
Got 1
练习 18.4: 修改双 react
示例, 使用现场供应(live Supply)而不是按需供应。输出是如何变化的?
5.19.1. 在后台响应
react
是一种当有值时, 你可以对其进行响应的方式。到目前为止, 你已经看到 react
是一个顶层的 Block。它一直在运行 - 支撑着程序的其余部分 - 直到它完成。
相反, 你很有可能希望你的 react
在你的程序做其他事情的时候在后台做它的工作。你可以用一个带有 start
的 Promise 包裹 react
。这样就可以让 react
在一个线程中工作, 而程序的其他部分则继续工作:
my $supply = Supply.interval: 1;
my $promise = start {
react {
whenever $supply { put "Got $^a" }
whenever True { put 'Got something that was true' }
whenever Promise.in(5) { put 'Timeout!'; done }
}
}
put 'After the react loop';
await $promise;
put 'After the await';
END put "End of the program";
输出的第一行是来自 start
块之后的 put
。react
开始工作, 但没有阻塞程序的其他部分:
After the react loop
Got 0
Got something that was true
Got 1
Got 2
Got 3
Got 4
Timeout!
After the await
End of the program
把它提升一个档次。加一个 Channel 进去。把 Supply 移到 whenever
里面。当这个 Supply 有一个值的时侯, 它就会执行 Block 以输出和之前一样的东西。如果该值是 2 的倍数, 它还会将其发送到 Channel 中。
添加第二个 whenever
来读取 Channel 上可用值。你需要将 Channel 转换为 Supply。这很容易, 因为有一个 .Supply
方法。whenever
分接那个 Supply:
my $channel = Channel.new;
my $promise = start {
react {
whenever Supply.interval: 1
{ put "Got $^a"; $channel.send: $^a if $^a %% 2 }
whenever $channel.Supply
{ put "Channel got $^a" }
whenever True
{ put 'Got something that was true' }
whenever Promise.in(5)
{ put 'Timeout!'; done }
}
}
put 'After the react loop';
await $promise;
put 'After the await';
END put "End of the program";
在插入 Channel 输出的情况下, 输出的效果与之前大致相同:
After the react loop
Got 0
Got something that was true
Channel got 0
Got 1
Got 2
Channel got 2
Got 3
Got 4
Channel got 4
Timeout!
After the await
End of the program
练习 18.5: 使用 IO::Notification
在命令行指定的文件发生变化时输出一条消息。
5.20. 总结
Promise 是并发的基础, 有多种方法可以创建它们来获取你所需要的东西。将你的问题分解成独立的部分, 并以 Promise 的形式运行, 它可以在不同的线程中运行(甚至可能在不同的核心上运行)。有了这些, Supply 和 Channel 提供了一种在你的程序的不相连的部分之间传递数据的方法。要想从所有这些东西中获得最大的好处, 你需要用不同于你目前所看到的程序性的东西来思考。通过实践, 你会得到这些。 == 控制其它程序
有时你需要让其他程序为你做一些工作。 Perl 系列语言被称为“互联网的胶水”。开始一个著名的,稳定的,现有的程序比自己重新实现它更容易,更快。本章介绍了许多启动和控制外部程序的方法,以便根据你的意愿对其进行控制。
5.21. 快速和容易
shell
例程是运行外部命令或程序的快捷方式。它接受参数并在 shell 中运行它,就像你自己输入它一样。此示例使用类 Unix 的 shell 命令列出所有文件:
shell( 'ls -l' );
If you were on Windows you’d use a different command. There’s an implicit cmd /c
in front of your command:
如果你在 Windows 上,你会使用不同的命令。命令前面有一个隐式的 cmd /c
:
shell( 'dir' ); # actually cmd /c dir
此命令的输出将转到程序输出所在的位置(只要你没有将标准输出或错误重定向到其他内容)。
你可以通过检查 $*DISTRO
变量来选择命令。 Distro
对象有一个 .is-win
方法,如果它认为你的程序在该平台上运行,则返回 True
:
my $command = $*DISTRO.is-win ?? 'dir' !! 'ls -l';
shell( $command );
注意变量作为 shell 的参数!一定要知道它们里面有什么。如果 shell 中的字符是特殊的,那么它在该值中也是特殊的。稍后详细介绍。
|
shell
返回一个 [Proc
](https://docs.raku.org/type/Proc.html) 对象。当你在 sink 上下文中使用它(对结果不执行任何操作)并且命令失败时,[Proc
](https://docs.raku.org/type/Proc.html) 对象会抛出异常:
shell( '/usr/bin/false' ); # throws X::Proc::Unsuccessful
当命令以 0
以外的值退出时,命令“失败”。这是一种 Unix 惯例,其中非零数字表示各种错误条件。并非所有程序都遵循该惯例,如果不遵循,你将不得不做更多的工作。
你可以保存结果以避免异常。你可以检查 [Proc
](https://docs.raku.org/type/Proc.html) 对象以查看发生的情况:
my $proc = shell( '/usr/bin/false' );
unless $proc.so {
put "{$proc.command} failed with exit code: {$proc.exitcode}";
}
这仍然可能不是你想要的。如果你希望它返回非零值,你可能必须自己处理部分过程:
my $proc = shell( '/usr/bin/true' );
given $proc {
unless .exitcode == 1 {
put "{.command} returned: {.exitcode}";
X::Proc::Unsuccessful.new.throw;
}
}
如果你不关心命令是否失败,则可以在返回的对象上调用 .so
。这“处理”对象并阻止 [Proc
](https://docs.raku.org/type/Proc.html) 抛出异常:
shell( '/usr/bin/false' ).so
5.21.1. 引起来的命令
有时你想捕获命令的输出或将其保存在变量中。你可以使用带 :x
副词的引用从命令的输出创建一个[字符串
](https://docs.raku.org/type/Str.html):
my $output = Q:x{ls -1};
my $output = q:x{ls -1};
my $output = qq:x{$command};
这些是它们的稍短版本,可以做同样的事情:
my $output = Qx{dir};
my $output = qx{dir};
my $output = qqx{$command};
这些仅捕获标准输出。如果要合并标准错误,则需要在 shell 中处理。这适用于 Unix 和 Windows,使用 2>&1
。这会在句柄到达你的程序之前合并它们:
my $output = qq:x{$command 2>&1};
5.21.2. 更安全的命令
run
例程允许你将命令表示为列表。列表中的第一项是命令名,Raku 直接执行而没有 shell 交互。这个命令并不像它看起来那样令人讨厌,因为没有一个字符对 shell 来说是特殊的。那些分号不会结束命令并启动另一个命令:
# don't do this, just in case
run( '/bin/echo', '-n', ';;;; rm -rf /' );
如果你在 shell 中将其作为单个[字符串
](https://docs.raku.org/type/Str.html)输入,则可以启动递归操作以删除所有文件。即使在开玩笑中也不要尝试这个(或者使用带有保存快照的虚拟机!)。
run
返回一个 [Proc
](https://docs.raku.org/type/Proc.html) 对象; 以与 shell
相同的方式处理它:
unless run( ... ) {
put "Command failed";
}
你可能想要使用没有路径信息的裸命令名:
run( 'echo', '-n', 'Hello' );
这也不是特别安全。 run
将在 PATH
环境变量中查找匹配的文件。这是人们可以在你的程序之外设置的东西。有人可能会欺骗你的程序运行一些叫做 echo 的东西。
你可以清除 PATH
,强制程序始终指定命令的完整路径:
%*ENV{PATH} = ''; # won't find anything
run( '/bin/echo', '-n', 'Hello' );
将 PATH
设置为你信任且允许的目录可能更容易:
%*ENV{PATH} = '/bin:/sbin:/usr/bin:/usr/sbin'
run( 'echo', '-n', 'Hello' );
这并不意味着你找到的命令是正确的;有人可能已经篡改过。没有办法提供完美的安全性 - 但你不必太担心。每当你与程序之外的事物进行交互时,请考虑这一点。
像 shell
一样,run
返回一个 [Proc
](https://docs.raku.org/type/Proc.html) 对象。 :out
参数捕获标准输出并通过 [Proc
](https://docs.raku.org/type/Proc.html) 对象使其可用。使用 .slurp
来提取它:
my $proc = run(
'/bin/some_command', '-n', '-t', $filename
:out,
);
put "Output is 「{ $proc.out.slurp }」";
:err
参数对错误输出执行相同的操作:
my $proc = run(
'/bin/some_command', '-n', '-t', $filename
:out, :err,
);
put "Output is 「{ $proc.out.slurp }」";
put "Error is 「{ $proc.err.slurp }」";
如果你不希望它们作为单独的流,你可以合并它们:
my $proc = run(
'/bin/some_command', '-n', '-t', $filename
:out, :err, :merge
);
put "Output is 「{ $proc.out.slurp }」";
你还可以给它其命名参数以控制编码,环境和当前工作目录(以及其他内容)。
练习19.1 使用 run
来获取当前目录的按文件大小排序的文件列表。输出那个长文件列表。 Unix 命令是 ls -lrS
,Windows 命令是 cmd /c dir /OS
。一旦你开始工作,过滤行只输出那些带有 7
的行。最后,你能让一个程序在两个平台上都能运行吗?
5.21.3. 写入到 Proc
进程可以从你的程序中接收数据。包括 :in
允许你写入到进程:
my $string = 'Hamadryas perlicus';
my $hex = run 'hexdump', '-C', :in, :out;
$hex.in.print: $string;
$hex.in.close;
$hex.out.slurp.put;
在此示例中,你将调用一次 .print
,然后关闭输出。这对于 hexdump 来说很好,但其他程序可能表现不同。有些人可能会期待一些输入,给你一些输出,然后在你读取之后期望更多的输入。这如何工作取决于具体的程序,有时可能令人发狂:
my $string = 'Hamadryas perlicus';
my $hex = run 'fictional-program', :in, :out;
$hex.in.print: $string;
$hex.out.slurp;
$hex.in.print: $string;
...;
你可以将一个外部程序的输出重定向到另一个外部程序的输入。此示例获取 raku -v
的输出并使其成为下一个[Proc
](https://docs.raku.org/type/Proc.html) 的输入:
my $proc1 = run( 'raku', '-v', :out );
my $proc2 = run(
'tr', '-s', Q/[:lower:]/, Q/[:upper:]/,
:in($proc1.out)
);
第二个 run
使用外部的 tr
命令将所有小写字母转换为大写字母:
THIS IS RAKUDO STAR VERSION 2018.04 BUILT ON MOARVM VERSION 2018.04
IMPLEMENTING PERL 6.C.
5.22. Procs
[Proc
](https://docs.raku.org/type/Proc.html) 对象处理 shell
和 run
。自己构造对象以获得更多控制。这分两步进行; [Proc
](https://docs.raku.org/type/Proc.html) 设置了稍后运行命令的东西:
my $proc = Proc.new: ...;
设置捕获并合并标准输出和错误流的通用 [Proc
](https://docs.raku.org/type/Proc.html):
my $proc = Proc.new: :err, :out, :merge;
当你准备好运行命令时,.spawn
它。你生成的进程使用你已经建立的设置。结果是基于程序退出状态的布尔值:
unless $proc.spawn: 'echo', '-n', 'Hello' {
... # handle the error
}
如果需要不同的设置,请在调用 .spawn
时指定当前工作目录和环境:
my $worked = $proc.spawn: :cwd($some-dir), :env(%hash);
unless $worked {
... # handle the error
}
练习19.2 创建捕获标准输出和错误的 [Proc
](https://docs.raku.org/type/Proc.html)。生成命令以获取目录列表。
5.23. 异步控制
通过 [Proc
](https://docs.raku.org/type/Proc.html)(以及 shell
和 run
)执行命令会使程序等待,直到外部程序完成其工作。使用 [Proc::Async
](https://docs.raku.org/type/Proc::Async.html) 允许这些程序在自己的 [Promise
](https://docs.raku.org/type/Promise.html) 中运行,而程序的其余部分继续运行。
运行外部 find
并等待它遍历所有文件系统可能几乎永远等不到(至少感觉像):
my $proc = Proc.new: :out;
$proc.spawn: 'find', '/', '-name', '*.txt';
for $proc.out.lines -> $line {
put $++, ': ', $line;
}
put 'Finished';
运行此程序时,你会看到 find 的所有输出行。完成后,可能需要很长时间,然后你将看到 Finished
消息。你可以异步地执行此操作。
你可以在这些示例中看到 Unix find,但你还在第8章中创建了一个类似的目录列表程序,你可以将其用作外部程序来练习使用 [Proc
](https://docs.raku.org/type/Proc.html):
my $proc = Proc.new: :out;
$proc.spawn: 'raku', 'dir-listing.p6';
for $proc.out.lines -> $line {
put $++, ': ', $line;
}
put 'Finished';
[Proc::Async
](https://docs.raku.org/type/Proc::Async.html) 的接口与 [Proc
](https://docs.raku.org/type/Proc.html) 的有点不同。一旦有了对象,就可以使用第18章中看到的 [Supply
](https://docs.raku.org/type/Supply.html) 和 [Promise
](https://docs.raku.org/type/Promise.html) 功能。这个例子使用 .lines
将输出分解为行(而不是缓冲区的块),然后轻敲该 [Supply
](https://docs.raku.org/type/Supply.html) 以处理进来的行:
my $proc = Proc::Async.new: 'find', '/', '-name', '*.txt';
$proc.stdout.lines.Supply.tap: { put $++, ': <', $^line, '>' };
my $promise = $proc.start;
put 'Moving on';
await $promise;
这是 [Proc::Async
](https://docs.raku.org/type/Proc::Async.html) 的简单使用,但你可以将它与你已经看到的并发功能结合使用。调用 .stdout
可以获得输出行,但只能在调用 .start
之后。在[块
](https://docs.raku.org/type/Block.html)中执行这两个操作:
my $proc = Proc::Async.new: 'find', '/', '-name', '*.txt';
react {
whenever $proc.stdout.lines { put $_; }
whenever $proc.start { put "Finished"; done }
};
.start
返回一个在外部程序完成之前不会保留的 [Promise
](https://docs.raku.org/type/Promise.html)。即使 whenever
在程序开始时运行,[Promise
](https://docs.raku.org/type/Promise.html) 都不会保留到最后,然后 [Block
](https://docs.raku.org/type/Block.html) 就会完成它的工作。
练习19.3 实现异步 find 程序。修改它,使其在找到你在命令行上指定的文件数后停止。报告找到的文件数。
5.24. 总结
你可以运行程序并等待它们的输出或在后台触发它们并在它进入时处理它们的输出。扩展它以处理多个程序,你的程序成为外部资源的精细处理程序。你已经看到了它如何工作的机制,但你可以用它来设计更大更好的东西。 == 高级话题 In such a short book I don’t have enough pages to show you everything that you can do. This chapter is a brief survey of some of the features I would have liked to explain in more detail. You now know these exist and you can investigate them further on your own.
在这么短的书中,我没有足够的页面向你展示你可以做的一切。本章简要介绍了一些我希望更详细解释的功能。你现在知道这些存在,你可以自己进一步研究它们。
5.25. 单行
You can run raku one-liners. These are programs that you compose completely on the command line. The `-e`switch takes an argument that is the program:
你可以运行 raku 单行。这些完全是你在命令行上编写的程序。 -e
开关接受一个参数,即程序:
% raku -e 'put "Hello Raku"'
Hello Raku
The -n
switch runs the program once for each line of input. The current line is in $_
. This one uppercases and outputs the line:
-n
开关为每行输入运行一次程序。当前行是 $_
。这个单行大写一行并输出该行:
% raku -n -e '.uc.put' *.pod
You can load a module with -M
:
你可以使用 -M
加载模块:
% raku -MMath::Constants -e 'put α'
0.0072973525664
5.26. Declarator Block Comments
The parser doesn’t discard all comments. It remembers special comments and attaches them to the subroutine. |
comments attach themselves to the subroutine after them and =
comments attach themselves to the subroutine before them. These comments are available through the .WHY
meta-method:
解析器不会丢弃所有的注释。它会记住特殊注释并将它们附加到子例程中。 |
注释将它们附加到它们之后的子例程中,并且 =
注释将它们自身附加到它们之前的子例程中。这些注释可通过 .WHY
元方法获得:
#| Hamadryas is a sort of butterfly
class Hamadryas {
#| Flap makes the butterfly go
method flap () {
}
}
Hamadryas.WHY.put;
Hamadryas.^find_method('flap').WHY.put;
The output is the combination of all the comments attached to that subroutine:
输出是附加到该子例程的所有注释的组合:
Hamadryas is a sort of butterfly
Flap makes the butterfly go
This is the sort of thing that’s handy in an integrated development environment to grab a description of the thing you are trying to use. It’s also useful when you are debugging something—that is, it’s useful if the developer documented their code.
在集成开发环境中,这种方法很方便,可以获取你尝试使用的内容的描述。当你调试某些内容时,它也很有用 - 也就是说,如果开发人员记录了他们的代码,那么它很有用。
5.27. Feed Operators
The feed operators decide which way information flows. Here’s a list-processing pipeline that has a .grep
, a .sort
, and finally a .map
. What they do doesn’t matter as much as their order:
feed 操作符决定信息的流向。这是一个列表处理管道,它有一个 .grep
,一个 .sort
,最后一个 .map
。他们所做的事与顺序无关紧要:
my @array = @some-array
.grep( *.chars > 5 )
.sort( *.fc )
.map( *.uc )
;
The final step is farthest away from the assignment. You might not like that. The leftward feed operator allows you to write this in a way where the data flows in one direction. This flows bottom to top into the new variable:
最后一步是离赋值最远的。你可能不喜欢那样。向左的 feed 操作符允许你以数据在一个方向上流动的方式编写代码。下面这个从底部到顶部流入新到变量:
my @array <==
map( *.uc ) <==
sort( *.fc ) <==
grep( *.chars > 5 ) <==
@some-array
;
Notice that the assignment operator disappeared because the feed operator took care of that.
请注意,赋值运算符已消失,因为 feed 运算符负责处理了。
The rightward feed operator goes the other way. The new variable is at the end this time. This is the same thing in the other direction:
向右的 feed 操作符走另一条路。这次这个新变量在最后。在另一方向上也是如此:
@some-array
==> grep( *.chars > 5 )
==> sort( *.fc )
==> map( *.uc )
==> my @array;
5.28. Destructuring Signatures
You can group parameters with square brackets to create a subsignature. Inside the []
you can break down the aggregate into a smaller signature:
你可以使用方括号对参数进行分组以创建子签名。在 `[]`内部,你可以将总体分解为较小的签名:
sub show-the-arguments ( $i, [$j, *@args] ) { # slurpy
put "The arguments are i: $i j: $j and @args[]";
}
my @a = ( 3, 7, 5 );
show-the-arguments( 1, @a );
With that, $i
gets the first parameter and the []
gets the rest. The []
destructures the remaining arguments into $j
and @args
.
有了它,$i
获得第一个参数,[]
获得其余参数。 []
将剩余的参数解构为 $j
和 @args
。
5.29. Defining Your Own Operators
You can create new operators. Almost all of the things that we call “operators” are methods.
The ↑
and ↑↑
represent Knuth arrows. These are higher levels of exponentiation:
你可以创建新的运算符。几乎所有我们称之为“运算符”的东西都是方法。
↑
和 ↑↑
代表高德纳箭头。这些是更高的取幂水平:
multi infix:<↑> ( Int:D \n, Int:D \m --> Int:D )
is equiv(&infix:<**>)
is assoc<right>
{ n ** m }
proto infix:<↑↑> ( Int:D \n, Int:D \m --> Int:D )
is tighter(&infix:<↑>)
is assoc<right>
{ * }
multi infix:<↑↑> ( \n, 0 ) { 1 }
multi infix:<↑↑> ( \n, 1 ) { n }
multi infix:<↑↑> ( \n, \m ) { [↑] n xx m }
put 2↑3; # 2 ** 3 = 8
put 2↑↑3; # 2 ** 2 ** 2 = 2 ** 4 = 16
Notice that the definitions allow you to set traits for precedence and associativity. As with other subroutines these are lexically scoped, so they won’t affect other parts of your program.
请注意,这些定义允许你设置优先级和关联性的特征。与其他子例程一样,它们是词法作用域的,因此它们不会影响程序的其他部分。
5.30. Perl 5 Patterns
If you like Perl 5 patterns better, or already have some good ones that you’d like to reuse, you can do that. The :Perl5
adverb tells the match operator to interpret the pattern as a Perl 5 regular expression:
如果你更喜欢 Perl 5 模式,或者已经有一些你想要重用的好模式,你可以这样做。 :Perl5
副词告诉匹配操作符将模式解释为 Perl 5 正则表达式:
my $file = ...;
for $file.IO.lines {
next unless m:Perl5/\A\s+#/; # no quoting the # in Perl 5
.put;
}
5.31. Shaped Arrays
Want a multidimensional matrix? You can create a shaped array that knows how wide it is in any dimension. Use the ;
to separate the dimensions:
想要一个多维矩阵?你可以创建一个定形数组,知道它在任何维度上的宽度。使用 ;
分开维度:
my @array[2;2];
say @array; # [[(Any) (Any)] [(Any) (Any)]]
@array[1;0] = 'Hamadryas';
say @array; # [[(Any) (Any)] [Hamadryas (Any)]]
my $n = 0;
my $m = 1;
@array[$n;$m] = 'Borbo';
say @array; # [[(Any) Borbo] [Hamadryas (Any)]]
You can extend this to higher dimensions:
你可以将此扩展到更高的维度:
my @array[2;2;3];
The :shape
adverb can describe the size in each dimension:
:shape
副词可以描述每个维度的大小:
my @array = Array.new: :shape(3,3);
Once you set the limits in each dimension the size is fixed. This means that you can create fixed-size one-dimensional arrays. You won’t be able to use operaters that increase or decrease the number of elements:
在每个维度中设置限制后,大小就固定了。这意味着你可以创建固定大小的一维数组。你将无法使用增加或减少元素数量的操作符:
my @array[5];
5.32. Typed Containers
The container types ([List
](https://docs.raku.org/type/List.html), [Array
](https://docs.raku.org/type/Array.html), [Hash
](https://docs.raku.org/type/Hash.html), and so on) can limit their elements to a particular type. There are a few ways that you can constrain these. Consider this example:
容器类型([List
](https://docs.raku.org/type/List.html), [Array
](https://docs.raku.org/type/Array.html), [Hash
](https://docs.raku.org/type/Hash.html)等)可以将其元素限制为特定类型。有几种方法可以约束这些。考虑这个例子:
my Int @array = 1, 2, 3;
@array.push: 'Hamadryas';
Since a [Str
](https://docs.raku.org/type/Str.html) is not an [Int
](https://docs.raku.org/type/Int.html) the .push
fails:
由于[字符串
](Int
(https://docs.raku.org/type/Int.html),因此 .push
失败:
Type check failed in assignment to @array
That form types the @array
variable. The type is actually Array[Int]
. You can also bind to the object you construct directly:
该形式键入 @array
变量。该类型实际上是 Array [Int]
。你还可以绑定到直接构造的对象:
my @array := Array[Int].new: 1, 3, 7;
You can create [Hash
](https://docs.raku.org/type/Hash.html)es with objects for keys and many other interesting constraints.
你可以使用对象创建[散列
](https://docs.raku.org/type/Hash.html)以及许多其他有趣的约束。
5.33. NativeCall
There’s a builtin foreign function interface named NativeCall
. You use the is native
trait to specify the external library. This one connects your program to the argumentless flap
routine in libbutterfly:
有一个名为 NativeCall
的内置外部函数接口。你使用 is native
trait 指定外部库。这个程序将你的程序连接到 libbutterfly
中的无参数 flap
例程:
use NativeCall;
sub flap() is native('butterfly') { * }
There are ways to tell NativeCall
how to translate data structures to “native” types and the other way around.
有办法告诉 NativeCall
如何将数据结构转换为“原生”类型,反之亦然。
5.34. The with Topicalizer
The with
keyword sets the topic. In the postfix form you can use it so you don’t have to repeat a long variable name:
with
关键字设置主题。你可以在后缀形式中使用它,以使你不必重复长变量名称:
put "$_ has {.chars}" with $some-very-long-name;
There’s a [Block
](https://docs.raku.org/type/Block.html) form that’s similar to if-elsif-else
but sets the topic to the result of the condition. Instead of looking for True
or False
it tests for definedness. In each of these the topic inside the [Block
](https://docs.raku.org/type/Block.html) is the result of the respective .index
:
有一个与 if-elsif-else
类似的 [Block
](https://docs.raku.org/type/Block.html) 形式,但将主题设置为条件的结果。它不是寻找 True
或 False
,而是测试定义。 [Block
](https://docs.raku.org/type/Block.html) 里的每个这样的主题是相应的 .index
的结果:
my $s = 'Hamadryas';
with $s.index: 'a' { $s.substr( $_, 2 ).put }
orwith $s.index: 'm' { put 'Found m' }
orwith $s.index: 'H' { fail "Why is there an H at $_?" }
6. 结论
Congratulations. You’ve made it to the end of the book. Some people estimate that only one-third of the readers of a technical book accomplish that feat. This book was only supposed to be 300 pages long, but I couldn’t decide how to leave out anything still included. Sorry about that. The 80 pages of exercise answers really sent me over the limit. If you’re reading this, send me an email noting your rarified status as a completist reader!
恭喜。你已经读到了这本书的最后。有些人估计,只有三分之一的技术书籍的读者才能完成这一壮举。这本书应该只有 300 页,但我无法决定如何遗漏任何仍然包含的内容。对于那个很抱歉。 80 页的练习题答案真的让我超过极限。如果你正在读这篇文章,请给我发一封电子邮件,注明你作为一个完整的读者的稀有地位!
I wasn’t able to teach you how to be a programmer. I only had the one book. I’ve been at it for several decades and I’m still learning. This book specifically avoided that goal, and I think I’ve succeeded there. Remember that what you’ve seen here to demonstrate and isolate concepts and syntax is not a prescription for good programming practices.
我无法教你如何成为一名程序员。我只有一本书。几十年来我一直在这里,我还在学习。这本书专门避免了这个目标,我想我已经成功了。请记住,你在此处所展示的用于演示和隔离概念和语法的内容并不是良好编程实践的处方。
I hope you learned the basics of the language and that you can get simple programs to run. If you’re at the start of your programming career, don’t feel bad if you think you are taking longer than you should to get programs working. Writing programs is always the easy part. It’s the debugging work that’s hard. That takes practice. Every time you encounter a new problem you’re adding to the list of things you’ve encountered. Eventually you encounter a problem often enough that you start to subconsciously avoid it. That merely makes space for new sorts of mistakes.
我希望你学习了语言的基础知识,并且可以运行简单的程序。如果你正处于编程生涯的开始阶段,如果你认为自己花费的时间超过应该使程序运行的时间,请不要感到难过。编写程序总是很容易的。这是调试工作很难。这需要练习。每次遇到新问题时,你都会添加到你遇到的事物列表中。最终你经常遇到一个问题,你开始下意识地避免它。这只会为新的错误提供空间。
You aren’t done learning the language. There’s much more in the documentation. I noted some favorite excluded topics in [Chapter 20](https://www.safaribooksonline.com/library/view/learning-perl-6/9781491977675/ch20.html#camelia-advanced), but even that was limited. I really wanted to talk more about those, but I couldn’t go past that 500-page barrier. That list isn’t nearly complete. There are so many other things I don’t even mention. Explore those new topics as you become comfortable with what you’ve seen here.
你还没有学过这门语言。文档中还有更多内容。我在第20章中注意到了一些最受欢迎的主题,但即使这样也是有限的。我真的想更多地谈论这些,但我无法超越这500页的障碍。该清单并不完整。还有很多其他事我甚至都没提到。当你对这里看到的内容感到满意时,请探索这些新主题。
Consider going back to the beginning and reading through the book again. Some of the things will make more sense now that you have a better overview of the major topics. You have more context for the design decisions you dealt with in the first few chapters.
考虑回到开头并再次阅读本书。由于你对主要主题有了更好的概述,因此有些事情会更有意义。在前几章中,你有更多关于设计决策的背景信息。
Finally, read other books. Don’t limit yourself to one author. I have a particular opinion about some things, and other people have their opinions. Sometimes those are at odds. You don’t have to choose sides. As I write more extensively in Mastering Perl, your role is to take the best and most useful ideas from as many people as you can. Synthesize those into something that works in your world and for your tasks. Tell the world what decisions you made and what influenced them. Feed your ideas back into the milieu.
最后,阅读其他书籍。不要仅限于一位作者。我对某些事情有一个特别的看法,而其他人有他们的意见。有时这些是不一致的。你不必选择边。正如我在 Mastering Perl 中更广泛地撰写的那样,你的角色是尽可能多地采用最好和最有用的想法。将这些合成为适合你的世界和任务的东西。告诉全世界你做出的决定以及影响他们的因素。把你的想法反馈到环境中。