Raku Weekly Notes
— 焉知非鱼Raku Weekly Notes
:my $foo
的作用域和用途 #
在 regex、token 或 rule 中, 定义像下面这样的变量是可能的:
token directive {
:my $foo = "in command";
<command> <subject> <value>?
}
在中提到了一点有关该变量的东西, 我引用过来:
任何 grammar regex 实际上是一种
方法
, 并且你可以在这样一个子例程中使用一个冒号跟着任何作用域声明符来声明一个变量, 这些声明符包括my
,our
,state
和constant
(作为类似的声明符, temp 和 let 也能被识别). 单个语句(直到结尾的分号或行末尾的闭括号为止) 被解析为普通的 Raku 代码:
token prove-nondeterministic-parsing {
:my $threshold = rand;
'maybe' \s+ <it($threshold)>
}
有谁能解释下这段代码的应用场景吗?
what scope does :my $foo;
have? #
:my $foo
在它所出现的 rule/token/regex 中拥有词法作用域(lexical scope)。你所得到的作用域要么很大要么很小:
grammar g {
regex r1 {
{ my $foo; ...} # `$foo` 在该 block 的结尾超出作用域。
...
{ say $foo; } # `$foo` 不在作用域中。
}
}
grammar i {
my $foo;
regex r1 { ... } # 在 `r1` 内部, `$foo` 被识别出。
...
regex r999 { ... } # 但是在 r999 中也是。
}
它的用途? #
使用 :my $foo;
形式的变量声明以在 rule/token/regex 中声明本地作用域的变量, 如果没有进一步的声明, 那么这些变量能在 rule/token/regex 中的任何地方通过所声明的名字来引用。举个例子, 你可以看看 Rakudo 的 Grammar.nqp 源代码中的 token babble
中声明的 @extra_tweaks
变量的用法。
使用 :my $*foo;
形式的变量声明来声明动态的词法变量。动态变量能够, 在没有进一步声明的情况下, 在闭合词法作用域和闭合动态作用域中通过它们声明的名字来引用。作为说明, 请查看 the declaration of @*nibbles
in Rakudo’s Grammar module 和 its use in Rakudo’s Actions module 。
一般的使用场景 #
在 regular expressions 中一般不使用 :…
风格的声明。:...;
结构通常用在特别复杂和庞大的 grammars 中。对于这些使用场景, 依靠 Raku 的正则表达式和闭包的一致性是合适的。正是这使得 rule/token/regex 级别的 :...;
变量声明变得正当。
Regexes 和 closures 的一致性 #
很多 grammars 都是上下文有关的.
Raku 使 regexes 和 closures 统一了:
say Regex.^mro; (Regex) (Method) (Routine) (Block) (Code) ...
mro 是方法解析顺序, 这足以说明 regex 实际上是一种特殊类型的方法(就像方法是一种特殊类型的子例程一样)。
Raku: is there a phaser that runs only when you fall out of a loop? #
#!/usr/bin/env raku
ROLL:
for 1..10 -> $r {
given (1..6).roll {
when 6 {
say "Roll $r: you win!";
last ROLL;
}
default {
say "Roll $r: sorry...";
}
}
LAST {
say "You either won or lost - this runs either way";
}
}
更优雅的写法:
constant N = 5;
for flat (1..6).roll xx * Z 1..N -> $_, $n {
print "roll $n: $_ ";
when 6 {
put "(won)";
last;
}
default {
put "(lost)";
}
LAST {
print "result: ";
when 6 { put "winner :)" }
default { put "loser :(" }
}
}
怎么从命令行传递一个复数给 sub MAIN? #
#!/usr/bin/env raku
sub MAIN($x)
{
say "$x squared is { $x*$x }";
}
我要在命令行中传递一个复数给 MAIN:
% ./square i
Cannot convert string to number: base-10 number must begin with valid digits or '.' in '⏏i' (indicated by ⏏)
in sub MAIN at ./square line 7
in block <unit> at ./square line 5
Actually thrown at:
in sub MAIN at ./square line 7
in block <unit> at ./square line 5
当我把脚本变为:
#!/usr/bin/env raku
sub MAIN(Complex $x)
{
say "$x squared is { $x*$x }";
}
它竟然彻底罢工了:
% ./square i
Usage:
square <x>
% ./square 1
Usage:
square <x>
一种方法是使用 Coercive type declaration (强制类型声明), 从 Str 到 Complex:
sub MAIN(Complex(Str) $x) {
say "$x 的平方为 { $x * $x }";
}
那么:
% ./squared.pl 1
1+0i 的平方为 1+0i
% ./squared.pl 1+2i
1+2i 的平方为 -3+4i
但是:
$ ./test.pl6 2
Usage:
./test.p6 <x>
所以你真正需要的是把其它 Numeric 类型强转为 Complex 类型:
#!/usr/bin/env raku
sub MAIN ( Complex(Real) $x ) {
say "$x squared is { $x*$x }";
}
我使用 Real 而非 Numeric, 因为 Complex 已经涵盖了其它的了。
Blessing a Hash into an object #
为什么我写的这段代码不对呢?
class WordCount {
has %!words; # Tried with both . and !
method new($string) {
my %words;
my @sentence = split(/\s+/, $string);
for @sentence -> $word {
%words{$word}++;
}
return self.bless(:%words);
}
method sayCounts() {
my @keys = keys(%!words);
for @keys -> $key {
say $key ~ " " ~ %!words{$key};
}
}
}
sub MAIN {
my $sentence = "the boy jumped over the dog";
my $wordCount = WordCount.new($sentence);
$wordCount.sayCounts();
}
Raku-ify:
class WordCount {
has Int %.words is default(0);
method new($string) {
my Int %words;
for $string.split(/\s+/) -> $word {
%words{$word}++;
}
self.bless(:%words)
}
method gist {
%.words.map({.value ~ " " ~ .key}).join("\n")
}
}
my $word-count = WordCount.new('the boy jumped over the dog');
say $word-count;
散列中的每一项都是一个 Pair:
my %w = a => 1;
%w.map({ say $_.^name }) # OUTPUT«Pair»
所以:
%.words.map({.value ~ " " ~ .key}).join("\n")
等价于:
%.words.kv.map( -> $word, $count { "$word $count" } ).join("\n")
你还可以使用 sub-signature(子签名)来解构 .map
提供的 Pair
:
%.words.map( -> (:key($word), :value($count)) { "$word $count" } ).join("\n")
字符串处理 #
将每行从第二列到最后一列数值为0的且数目多于6个的行删除
数据:
OG004240: 1 3 1 1 9 0 4 5 1 1 6 1 2
OG004241: 1 2 1 4 7 2 1 3 1 2 9 1 1
OG004242: 1 2 1 2 4 1 3 9 2 2 4 2 2
OG004243: 0 4 1 2 9 2 4 5 1 2 3 1 1
OG004244: 0 2 1 3 8 3 3 2 2 3 4 2 2
OG004245: 0 3 1 2 7 3 3 0 3 2 7 2 2
OG004246: 0 0 2 0 1 15 0 15 0 0 1 0 1
my @lines = "a.txt".IO.lines;
for @lines -> $line {
my @words = $line.split(/\s+/);
say $line unless @words[1..*].grep(* eq 0).elems > 6;
}
使用 rakudoc -f Str.split
查看 split 的帮助文档。
合并相同行:
文件一:
1###ENSMMUP00000017866###-###27582-27683
1###ENSMMUP00000017866###-###27508-27576
1###ENSMMUP00000017866###-###27290-27503
1###ENSMMUP00000040736###-###199515-200498
1###ENSMMUP00000040736###-###198582-198818
1###ENSMMUP00000030409###+###395728-395934
1###ENSMMUP00000030409###+###403004-403148
想合并相同的,生成文件格式如下:
1###ENSMMUP00000017866###-###27582-27683 27508-27576 27290-27503
1###ENSMMUP00000040736###-###199515-200498 198582-198818
1###ENSMMUP00000030409###+###395728-395934 403004-403148
一种方法如下:
my @lines = "a.txt".IO.lines;
my %hash;
for @lines -> $line {
$line.match(/^(.*?)(\d+'-'\d+)/);
%hash{$0} ~= $1 ~ " ";
}
for %hash.kv -> $key, $value {
say $key, $value;
}
如下数据,想去掉第3列重复的行且保留的行要使第四列最小, 原始数据:
326 0.00 0.00 ( 0 )
63 0.00 2.43 ( 0.0082 )
64 0.00 2.43 ( 0.0082 )
120 0.00 2.43 ( 0 )
340 0.00 4.03 ( 0 )
99 0.00 9.14 ( 0.0229 )
441 0.00 9.14 ( 0.0232 )
142 0.00 10.77 ( 0.0569 )
292 0.00 10.77 ( 0.0393 )
266 0.00 10.77 ( 0.0233 )
想要的结果:
326 0.00 0.00 ( 0 )
120 0.00 2.43 ( 0 )
340 0.00 4.03 ( 0 )
99 0.00 9.14 ( 0.0229 )
266 0.00 10.77 ( 0.0233 )
一种方法如下:
my @lines = "a.txt".IO.lines;
my %hash;
for @lines -> $line {
$line.match(/(\d+\.\d+)\s+\(\s+(\S+)/);
%hash{$0} ~= $1 ~ " ";
}
for @lines -> $line {
$line.match(/(\d+\.\d+)\s+\(\s+(\S+)/);
for %hash.kv -> $key, $value {
say $line if $0 ~~ $key && $1 ~~ $value.words.min;
}
}
有 gene.txt 和 in.txt 两个文件, 文件内容如下:
gene.txt:(2000多行)
chr1 ABCA4 94458582 94586799
chr1 ACADM 76190031 76229363
chr16 BBS2 56518258 56554008
chr17 G6PC 41052813 41066450
chr17 GAA 78078244 78093271
in.txt:(5万多行)
1 94505603 rs368951547 C T NA NA
1 94505604 rs61750126 A C 0.02066 NA
1 94505611 rs137853898 G A NA not-provided
1 94505620 rs370967816 T A NA NA
1 94505621 rs149503495 T C NA NA
1 94505627 rs374610040 A G NA NA
22 18901263 rs377148163 C A NA NA
22 18901290 rs381848 G A 0.07989 NA
22 18901322 rs62232347 C A NA NA
22 18901326 rs201353896 TCC T 0.05005 NA
22 18901327 rs10537001 CCT C 0.0528 NA
16 18901326 rs201353896 TCC T 0.05005 NA
17 18901327 rs10537001 CCT C 0.0528 NA
gene.txt 和 in.txt 的第一列的数字部分相同,并且 In 的第二列在gene 的三四列范围之间,就输出 in.txt 中的那一行。
解决方法:
my @lines = "a.txt".IO.lines;
my @inlines = "in.txt".IO.lines;
my %hash;
for @lines -> $line {
$line.match(/^chr(\d+)\s+(\w+)\s+(\d+)\s+(\d+)/);
%hash{$0~$1~$2~$3} = $0 ~ " " ~ $2 ~ " " ~ $3;
}
for @inlines -> $line {
$line.match(/^(\d+)\s+(\d+)/);
for %hash.values -> $value {
say $line
if $0 ~~ $value.words[0]
&& $1 <= $value.words[1].Num
&& $1 <= $value.words[2].Num;
}
}
例如我现在数组中的值是 @project = ('NX11','NX12','NX13’)
。
另外一个数组是 @get = ('ss','ssfd','NX12','sed','NX11’)
。
现在把第一个数组中出现过的值,如果第二个数组中也有的话删除掉,然后保留第二个数组剩下的值。
使用差集:
@get (-) @project
有如下数据:
PL -0.00 5.50
PL -0.25 3.50
PL -0.50 0.00
PL -0.75 4.50
-0.25 -0.00 1.00
-0.25 -0.25 4.50
-0.25 -0.50 1.00
-0.75 -0.75 1.00
-0.75 -1.00 0.00
-1.00 -0.25 3.50
-1.00 -0.50 0.00
-1.00 -1.25 3.40
-1.00 -1.75 4.00
将第一列值相同的行合并, 分使合并第二列和第三列:
结果如下:
PL -0.00 -0.25 -0.50 -0.75
PL 5.50 3.50 0.00 4.50
...
面向对象 #
Fluent interface (流接口)
在软件工程中,一个流接口(fluent Interface)是指实现一种实现面向对象的能提高代码可读性的API的方法。
在 Raku 中有很多种方法, 但是最简单的一种是声明属性为可读写并使用 given
关键字。类型注释是可选的。
class Employee {
subset Salary of Real where * > 0;
subset NonEmptyString of Str where * ~~ /\S/; # 至少一个非空白符号
has NonEmptyString $.name is rw;
has NonEmptyString $.surname is rw;
has Salary $.salary is rw;
method gist {
return qq:to[END];
Name: $.name
Surname: $.surname
Salary: $.salary
END
}
}
my $employee = Employee.new();
given $employee {
.name = 'Sally';
.surname = 'Ride';
.salary = 200;
}
say $employee;
# Output:
# Name: Sally
# Surname: Ride
# Salary: 200
在 Raku 中怎样检查文件的时间戳属性? 在 Perl 5 中是使用文件测试操作符 file test operators , 在 Raku 中是使用来自于 IO::FileTestable
role 的方法 (e.g. .modified
, .accessed
and .changed
) 。
例如:
my $filename = "sample.txt";
my $seconds_since_epoch = $filename.IO.accessed;
my $readable_timestamp = DateTime.new($filename.IO.accessed);
say "File '$filename' was last accessed at '$readable_timestamp', which is {$seconds_since_epoch.Num} seconds since the epoch";
2、我正尝试生成包含 10 个随机随机序列的 FASTQ 文件,序列由随机品质分数构成。我原来是使用下面的代码,它工作良好:
my @seq = (rand_fa_seq() for ^10);
my @qual = (rand_qual() for ^10);
@seq.raku.say;
@qual.raku.say;
sub rand_fa_seq
{
return join("", roll(20,"ACGT".comb));
}
sub rand_qual
{
return join("", roll(20,"EFGHIJ".comb))
}
等价于:
sub rand-fa-seq($n = 20) { <A C G T>.roll($n).join }
sub rand-qual($n = 20) { <E F G H I J>.roll($n).join }
my @seq = rand-fa-seq() xx 10;
my @qual = rand-qual() xx 10;
在 Raku 中我怎样使用 “列表解析” 创建一组非平方数? 我在 Rosetta Code 那儿看到了如何打印一组非平方数的代码:
sub nth_term (Int $n) { $n + round sqrt $n }
say nth_term $_ for 1 .. 22;
目前为止我看到的最接近的东西是使用 for
关键字。 但是因为这实际上仅仅是一个内联(inline)循环,我认为这从技术上来讲并不是列表解析,尽管它看起来相似:
my @y = ($_**2 + 1 for 1 .. 10);
但是,我真正想知道是否有一种 “列表解析 “ 的方法来创建可在数学上描述的诸如非平方数的列表。这儿有一个我用来创建一组非平方数的方法(直到 30):
my @non_squares = grep {sqrt($_) != floor(sqrt($_))}, 1 .. 30;
我怎样用列表解析来实现它呢?
实际上, 你的例子 my @y = ($_**2 + 1 for 1 .. 10);
是 Raku 方式写成的列表解析。你还可以添加一个条件测试, 就像 Raku design document S04 中建议的那样:
为了轻松地书写列表解析, 循环语句修饰符允许包含单个条件语句修饰符:
sub odd(Int $n) {return $n % 2}
@evens = ($_ * 2 if .&odd for 0..100);
这个就是怎样写一个 Raku 列表解析的非平方数(直到 30):
my @non_squares = ($_ if .sqrt != .sqrt.Int for 1 .. 30);
一丢丢解释:在每次 for 循环迭代中, 从 1 到 30 这个范围中的当前数字会被赋值给默认变量 $_
(等价于 it)。没有调用者的方法调用会默认在 “it" 身上调用(例如 .sqrt
等价于 $_.sqrt
)。 所以,对于 1到30中的每一个数字,它的平方根被检查以查看它是否有非整数平方根。 如果是真, 那它就被包含在列表中。
我想知道在 Raku 中冒号与方法和函数调用有什么关系。
我在 Raku spec test (S32-io) 中看到了这个(我添加了注释):
$fh.print: "0123456789A"; # prints '0123456789A' to the file
据我所知,这等价于:
$fh.print("0123456789A"); # prints '0123456789A' to the file
这两种方式看起来都接收多个参数而且展平列表也没问题:
$fh.print: "012", "345", "6789A"; # prints '0123456789A' to the file
$fh.print("012", "345", "6789A"); # prints '0123456789A' to the file
my @a = <012 345 6789A>;
$fh.print(@a); # prints '0123456789A' to the file
$fh.print: @a; # prints '0123456789A' to the file
存在这两种语法一定有某种原因。 使用这种或另一种语法有某种理由吗?
我还注意到,当作为方法使用时, 我们不得不使用带有 :
或 ()
的 print:
$fh.print(@a); # Works
$fh.print: @a; # Works!
$fh.print @a; # ERROR!
当使用带冒号的 print 函数时,还有一些有意思的行为。 在这种情况下, : 和 () 不等价:
print @a; # Prints '0123456789A' (no newline, just like Perl 5)
print(@a); # Ditto
print: @a; # Prints '012 345 6789A' followed by a newline (at least in REPL)
print @a, @a; # Error (Two terms in a row)
print: @a, @a; # Prints '012 345 6789A 012 345 6789A' followed by a newline (in REPL)
然后我尝试在脚本文件中使用print。这对于打印到标准输出有效:
print @a;
然而, 这不会打印到标准输出:
print: @a, @a;
但是方法版本的工作良好:
$fh.print: @a, @a; # Prints '0123456789A0123456789A' to the file
我感觉我已经理解了这个, 但是不能用语言表达出来。有人可以解释下使用 print 的这些变化吗。 还有, 这些行为会因为 Great List Refactor 而改变吗?
Answer:
使用冒号代替圆括号的一个主要原因是通过移除一组圆括号,它能使代码更清晰。在其它方面它们真的一样。
当你使用 print: @a
, 那你真正在做的就是在行上放置一个标签, 并让 @a 落进去(fall-through)。这在 REPL 中会调用带有值的 say 方法。
如果你没有在方法调用中使用括号或冒号,, 那么方法会以无参数方式调用。
你可以交换方法的顺序,还有调用者,如果你使用冒号的话。
say $*ERR: 'hello world'; # $*ERR.say('hello world')
我刚刚确认了, 就像你说的, print: @a 就是 label: @a , label 可以是任何东西. – Christopher Bottoms Jun 26 at 14:12 |
|
---|---|
换句话说,冒号能代替方法调用的圆括号,但不能代替子例程调用。 – Christopher Bottoms Jun 26 at 14:12 |
5、排序散列键值对儿
my %hash =
two => 2,
three => 3,
one => 1,
;
for %hash.sort(*.key)».kv -> ($key, $value) {
say "'$key' => '$value'";
}
%hash.sort({.key})».kv
和上面的 sort
等价吗?
为什么这个 sort 没有 hyper »
提示就不会工作?
因为在列表身上调用 .kv
会返回一个索引, Pair 列表, 这不是你想要的; 你不能单单在列表身上调用 .kv
。所以你必须通过在每个 Pair 身上调用 .kv 方法分别从列表中的 Pair 中取出键和值, 这正是 ».kv 所做的。
你还可以使用 .map(*.kv)
代替。
».kv
语法允许把工作展开到多个线程中执行, 如果那样做有意义的话。
(当前的 Rakudo仅以半随机的顺序工, 以防止人们错误地使用该特性 )
通过在签名中使用副词以提取属性, 这是另一种 loop 写法:
for %hash.sort -> (:$key, :$value) {
say "'$key' => '$value'";
}
for %hash.sort -> $pair (:$key, :$value) {
say $pair;
say $key === $pair.key and $value === $pair.value; # True
}
# :$key is short for :key($key)
for %hash.sort -> (:key($k), :value($v)) {
say "'$k' => '$v'";
}
这对其它没有方法创建一组它们公用属性的对象有用:
class C { has $.a; has $.b; has $.c; has $!private-value }
my $c = 5;
my $obj = C.new(:a<A>,:b(1),:$c);
given $obj -> ( :$a, :b($b), :$c) ) {
say "$a $b $c"; # A 1 5
}
# ignore $.a by using an unnamed scalar
given $obj -> ( :a($), :$b, :$c ) { ... }
# places any unspecified public attributes in %others
given $obj -> ( :$a, :$b, *%others ) {
.say for keys %others; # c
}
# 忽略任何未指定的属性
# useful to allow subclasses to add more attributes
# 或仅仅丢弃掉任何你不关心的值
given $obj -> ( :$a, :$b, *% ) { ... }
# 失败,因为它没有处理公用的 c 属性
# in the sub-signature
given $obj -> ( :$a, :$b ) { ... }
关于签名能做什么,那只是开始。
所有下面的,在子例程和方法签名中都是被允许的,非强制性的, 对于这个例子杀伤力过大。这在 multi subs 和 multi methods 中对于限制可能的候选者真的很有用。
for 'one' => 1, 1/3
->
# Type is an alias to the object type
::Type Any $_ # Any is the default type requirement
# the public attributes of the object
(
::A-Type Any :key( :numerator( $a ) ),
::B-Type Any :value( :denominator( $b ) ) where $b >= 1,
)
{
my Type $obj = $_; # new variable declared as having the same type
my A-Type $new-a = $a;
my B-Type $new-b = $b;
# could have used $_.^name or .^name instead of Type.^name
# so you don't actually have to add the alias to the signature
# to get the name of the arguments type
say Type.^name, ' ', $_;
say ' ', A-Type.^name, ' ', $a;
say ' ', B-Type.^name, ' ', $b;
}
Pair one => 1
Str one
Int 1
Rat 0.333333
Int 1
Int 3
至于使用 .sort({.key})
, 恩, 那从根本上来说是同一个东西, 因为 sort
在那儿接受任何 Callable
我要指出, 你甚至不需要为 sort
提供参数, 因为它默认比你给它的东西智能。
Raku 有很多创建和访问 Callable 东西的方式。所以任何下面一种都可以工作:
*.key
{ .key } # { $_.key }
-> $_ { .key } # basically what the previous line turns into
{ $^placeholder-var.key }
sub ($_) { .key }
&a-subroutine-reference # you would have to create the subroutine though
还有, 因为所有普通的操作符实际上都是子例程,你可以在需要 Callable 的其它地方使用它们:
&infix:<+> # the subroutines responsible for the numeric addition operator
&[+] # ditto
&prefix:<++>
&postfix:<++>
# etc
- 和 ( ) 的区别
# 无法正常排序
my @s = [2443,5,33, 90, -9, 2, 764];
say @s.sort; # 2443 5 33 90 -9 2 764
say @s.WHAT; # (Array)
say @s.raku; # [[2443, 5, 33, 90, -9, 2, 764]]<>
# 正常排序
my $array = [2443,5,33, 90, -9, 2, 764];
say $array.sort; # -9 2 5 33 90 764 2443
say $array.WHAT; # (Array)
say $array.raku; # [2443, 5, 33, 90, -9, 2, 764]
my @s = (2443,5,33,90,-9,2,764);
say @s.sort; # -9 2 5 33 90 764 2443
say $array.WHAT; # (Array)
say @s.raku; # [2443, 5, 33, 90, -9, 2, 764]<>
可见, 使用 [ ]
和 ( )
创建数组是不一样的.
my @s = [2443, 5, 33, 90, -9, 2, 764];
这创建了一个数组, 并把该数组赋值给 @s[0]
, 所以 @s
只有一个元素, 所以对 @s
进行排序是没有意义的. 然而你可以使用:
@s[0].sort.say
来实现你要求的排序。
我想修改一个数组(我在这个例子中使用了 splice
, 但是它也可能是修改数组的任何操作)并返回修改后的数组 - 和 slice
不一样, slice 返回的是从数组中抠出的项。我可以很容易地通过在数组中存储一个 block 来做到, 就像下面这样:
my $1 = -> $a { splice($a,1,3,[1,2,3]); $a };
say (^6).map( { $_ < 4 ?? 0 !! $_ } ).Array;
# [0 0 0 0 4 5]
say (^6).map( { $_ < 4 ?? 0 !! $_ } ).Array.$1;
# [0 1 2 3 4 5]
我怎么把由 $1
代表的 block 内联到单个表达式中呢? 下面的解决方法不正确:
say (^6).map( { $_ < 4 ?? 0 !! $_ } ).Array.(-> $a { splice($a,1,3,[1,2,3]); $a })
Invocant requires a type object of type Array, but an object instance was passed. Did you forget a 'multi'?
解决方法是添加一个 &
符号:
say (^6).map( { $_ < 4 ?? 0 !! $_ } ).Array.&(-> $a { splice($a,1,3,[1,2,3]); $a })
# 输出 [0 1 2 3 4 5]
my @numbers = <4 8 16 16 23 42>;
.say for @numbers[0..2]; # this works
# 4
# 8
# 15
# but this doesn't
my $range = 0..2;
.say for @numbers[$range];
# 16
最后的那个下标看起来好像把 $range
解释为 range 中元素的个数(3)。怎么回事?
解决方法
使用 @numbers[|$range]
把 range 对象展平到列表中。或者在 Range 对象上使用绑定来传递它们。
# On Fri Jul 2016, gfldex wrote:
my @numbers = <4 8 15 16 23 42>; my $range = 0..2; .say for @numbers[$range];
# OUTPUT«16»
# expected:
# OUTPUT«4\n 8\n 15»
# 这是对的, 并且还跟 "Scalar container implies item" 规则有关.
# Changing it would break things like the second evaluation here:
my @x = 1..10; my @y := 1..3; @x[@y]
# (2 3 4)
@x[item @y]
# 4
# 注意在签名中 range 可以被绑定给 @y, 而特殊的 Range 可以生成一个像 @x[$(@arr-param)] 的表达式
# 这在它的语义中是不可预期的。
# 同样, 绑定给 $range 也能提供预期的结果
my @numbers = <4 8 15 16 23 42>; my $range := 0..2; .say for @numbers[$range];
# OUTPUT«4815»
# 这也是预期的结果, 因为使用绑定就没有标量容器来强制被当成一个 item 了。
# So, all here is working as designed.
或者:
.say for @numbers[@($range)]
# 4
# 8
# 15
绑定到标量容器的符号输出一个东西, 可以达到你想要的选择包含:
前置一个 @
符号来得到单个东西的复数形式:numbers[@$range];
或者以不同的形式来声明 ragne 变量, 以使它直接工作。
对于后者, 考虑下面的形式:
# Bind the symbol `numbers` to the value 1..10:
my \numbers = [0,1,2,3,4,5,6,7,8,9,10];
# Bind the symbol `rangeA` to the value 1..10:
my \rangeA := 1..10;
# Bind the symbol `rangeB` to the value 1..10:
my \rangeB = 1..10;
# Bind the symbol `$rangeC` to the value 1..10:
my $rangeC := 1..10;
# Bind the symbol `$rangeD` to a Scalar container
# and then store the value 1..10 in it:`
my $rangeD = 1..10;
# Bind the symbol `@rangeE` to the value 1..10:
my @rangeE := 1..10;
# Bind the symbol `@rangeF` to an Array container and then
# store 1 thru 10 in the Scalar containers 1 thru 10 inside the Array
my @rangeF = 1..10;
say numbers[rangeA]; # (1 2 3 4 5 6 7 8 9 10)
say numbers[rangeB]; # (1 2 3 4 5 6 7 8 9 10)
say numbers[$rangeC]; # (1 2 3 4 5 6 7 8 9 10)
say numbers[$rangeD]; # 10
say numbers[@rangeE]; # (1 2 3 4 5 6 7 8 9 10)
say numbers[@rangeF]; # (1 2 3 4 5 6 7 8 9 10)
绑定到标量容器($rangeD
)上的符号总是产生单个值。在 [...]
下标中单个值必须是数字。
对于 range, 被当作单个数字时, 产生的是 range 的长度。