范圍和范圍迭代器語法
..
范圍操作符有各種在兩端帶有 ^
符號的變體以表明把那個端點排除在范圍之外。 它總會產生一個 Range 對象。 Range 對象是不可變的, 主要用于匹配間隔。
1..2 是從1到2包含端點的間隔, 而 1..2 不包含端點但是匹配任何在它倆之間的實數。
對于不同類型的數字參數, 范圍會被強制為更寬的類型,所以:
1 .. 1.5
被看作為:
1.0 .. 1.5
這些強制由 multi 簽名定義。(其它類型可能有不同的強制策略。)特別要說明的是, 使用 Range 作為末端是非法的:
0 ..^ 10 # 0 .. 9
0 .. ^10 # ERROR
如果范圍右側是非數字類型, 那么右側的參數被強轉為數字, 然后按上面那樣使用。
因此,第二個參數中的 Array 類型會被假定用作數字, 如果左側的參數是數字的話:
0 ..^ @x # okay
0 ..^ +@x # same thing
對于字符串也類似:
0 .. '1.5' # okay
0 .. +'1.5' # same thing
Whatever 類型也支持代表 -Inf/+Inf。 如果端點之一是一個 WhateverCode, 那么范圍會被引導為另一個 WhateverCode。
Range 對象支持代表它們的左側和右側參數的 .min 和 .max 方法。 .bounds 方法返回一個含有那兩個值的列表以代表間隔。 Ranges 不會自動反轉:
2..1 總是一個 null 范圍。(然而, 序列操作符 .. 能夠自動反轉,看下面。)
在 Range 的每個端點處, Range 對象支持代表排除(有)或包含(沒有)的 .excludes_min
and .excludes_max
方法。
Range | .min | .max | .excludes_min | .excludes_max
-----------+------+------+---------------+------------
1..10 | 1 | 10 | Bool::False | Bool::False
2.7..^9.3 | 2.7 | 9.3 | Bool::False | Bool::True
'a'^..'z' | 'a' | 'z' | Bool::True | Bool::False
1^..^10 | 1 | 10 | Bool::True | Bool::True
如果用在列表上下文中, Range 對象返回一個迭代器, 它產生一個以最小值開頭,以最大值結尾的序列。
任一端點可以使用 ^ 排除掉。因此 1..2 產生 (1,2) 但是 1^..^2
等價于 2..1 并不產生值, 就像 () 做的那樣。要指定一個倒數的序列, 使用反轉:
reverse 1..2
reverse 'a'..'z'
作為選擇, 對于數字序列, 你能使用序列操作符代替范圍操作符:
100,99,98 ... 0
100, *-1 ... 0 # same thing
換句話說,任何用作列表的 Range 會假定 .succ 語義, 絕對不會是 .pred
語義。 沒有其它的增量被允許;如果你想通過某個不是 1 的增量數字來增加一個數字序列,
你必須使用 ... 序列操作符。(Range 操作符的 :by
副詞因此被廢棄了。)
0, *+0.1 ... 100 # 0, 0.1, 0.2, 0.3 ... 100
只有正念叨的類型支持 .succ 方法的 Range 才能被迭代。如果它不支持, 任何迭代嘗試都會失敗。
一元區間
一元操作符 ^ 生成一個從 0 直到 其參數(不包括該參數)的區間。所以 ^4 等價于 0..^4.
for ^4 { say $_ } # 0, 1, 2, 3
范圍的自動填充
[這一節是推斷的,并且可能會在 6.0 中被忽略.]
因為在 item 上下文中 Range 對象通常是無意義的,用作標量操作符的 Range 對象一般會嘗試把操作分配給終點并返回另外一個適當的修改過的 Range 代替。
很像兩個項的連結(junction), 但是只使用合適的間隔語義。 (值得注意的這種自動線程化的例外包括 infix:<~~>
, 它是做智能匹配的, 還有 prefix:<+>
, 它返回范圍的長度。) 因此如果你想使用長度而不是終點來做切片, 你可以這樣說:
@foo[ start() + ^$len ]
它是下面這種形式的簡寫:
@foo[ start() + (0..^$len) ]
它有點等價于:
@foo[ list do { my $tmp = start(); $tmp ..^ $tmp+$len } ]
換句話說,數值化的操作符和其它有順序的類型通常被重載以在 Range 身上完成某些有意義的事情。
鏈式比較
S03-operators/relational.t lines 102–238
Perl 6 支持比較操作符的自然擴展, 它允許多個操作數:
if 1 < $a < 100 { say "Good, you picked a number *between* 1 and 100." }
if 3 < $roll <= 6 { print "High roll" }
if 1 <= $roll1 == $roll2 <= 6 { print "Doubles!" }
如果第一個比較失敗了則產生比較短路鏈:
S03-operators/short-circuit.t lines 236–297
1 > 2 > die("this is never reached");
鏈子中得每個參數至多會被求值一次:
S03-operators/short-circuit.t lines 226–235
1 > $x++ > 2 # $x 只精確地增長一次
注意: 任何以 < 開頭的操作符必須在前面擁有空格,否則它會被解釋為散列的下標。
調用者標記
當使用 Perl 6 方法調用的「間接對象」語法時追加的 :
標記了調用者(invocant)。下面的兩個語句是等價的:
$hacker.feed('Pizza and cola');
feed $hacker: 'Pizza and cola';
冒號也可以用在普通方法調用中以標示它應該被解析為列表操作符:
$hacker.feed: 'Pizza and cola';
這個冒號是一個單獨的令牌(token)。副詞前面的冒號不是單獨的令牌(token)。因此,在最長令牌(longest-token)規則下,
$hacker.feed:xxx('Pizza and cola');
被標記為應用到方法上作為它的「最高層級的在前面的操作符」("toplevel preceding operator")的副詞:
$hacker.feed :xxx('Pizza and cola');
不是作為 .feed
參數列表中的 xxx sub:
$hacker.feed: xxx('Pizza and cola'); # wrong
如果你兩種意義的冒號你都想要,為了既提供副詞又提供某種位置參數, 你必須放置兩次冒號:
$hacker.feed: :xxx('Pizza and cola'), 1,2,3;
(因為類型的原因它需要把空格放在標簽的冒號之后。)
要特別注意因為副詞的優先級:
1 + $hacker.feed :xxx('Pizza and cola');
會把 :xxx
副詞應用到 +
操作符上, 而不是應用到方法調用上。這是不可能成功的。
S03-operators/adverbial-modifiers.t lines 7–201
流操作符
S03-feeds/basic.t lines 6–163
新的操作符 ==>
和 <==
就像Unix里的管道一樣,但是它作用于函數或語句,接受并返回列表.因為這些列表由不相關聯的對象組成并不流動, 我們把它們叫做喂食(feed)操作符而非管道。例如:
@result = map { floor($^x / 2) },
grep { /^ \d+ $/ },
@data;
也能寫成向右偏的流操作符:
@data ==> grep { /^ \d+ $/ }
==> map { floor($^x / 2) }
==> @result;
或者使用左方向的流操作符:
@result <== map { floor($^x / 2) }
<== grep { /^ \d+ $/ }
<== @data;
每一種形式更清晰地表明了數據的流動。查看 S06 了解更多關于這兩個操作符的信息。
元操作符
Perl 6 的操作符被極大地規范化了,例如,通過分別在數字、字符串和布爾操作符前前置 +
、~
、?
來表明按位操作是作用在數字、字符串還是單個位身上。但是那只是一種命名約定,并且如果你想添加一個新的按位 ?
操作符, 你必須自己添加 +?
, ~?
, 和 ??
操作符。 類似地,范圍中排除末端的脫字符(^
)在那里只是約定而已。
和它相比, Perl 6 有 8 個標準的元操作符用于把已存在的給定操作符轉換為相關的更強大的操作符(或者至少不是一般的強大)。換句話說,這些元操作符正是高階函數(以其它函數作為參數的函數)的便捷形式。
包含元操作符的結構被認為是 "metatokens", 這意味著它們不受普通匹配規則的制約, 盡管它們的部件受制約。 然而,像普通的 tokens 那樣, metatokens 不允許在它們的子部件之間有空格。
賦值操作符
S03-operators/autovivification.t lines 4–111
C 和 Perl 程序員對于賦值操作符已經司空見慣了。(盡管 .= 操作符現在意味著在左邊對象的身上調用一個可變方法, ~= 是字符串連結。)
大部分非關系中綴操作符能通過后綴 = 被轉換為對應的賦值操作符。
A op= B;
A = A op B;
否定關系操作符
任何能返回 Bool 值的中綴關系操作符都可以通過前置一個 !
將它轉換為否定的關系操作符。有幾個關系操作符還有傳統的便捷寫法:
Full form Shortcut
--------- --------
!== !=
!eq ne
但是大部分關系操作符沒有傳統的便捷寫法:
!~~
!<
!>=
!ge
!===
!eqv
!=:=
為了避免 !!
操作符迷惑視線, 你不可以修改任何已經以!
開頭的操作符。
否定操作符的優先級和基(base)操作符的優先級相同。
你只可以否定那些返回 Bool 值的操作符。 注意諸如 ||
和 ^^
的邏輯操作符不返回 Bool 值, 而是返回其中之一的操作數。
翻轉操作符
在任意中綴操作符上前置一個 R,會翻轉它的兩個參數。例如,反向比較:
- Rcmp
- Rleg
- R<=>
任何翻轉操作符的優先級和根操作符的優先級是一樣的。結合性沒有被翻轉。
[R-] 1,2,3 # produces 2 from 3 - (2 - 1)
要得到另外一種效果,可以先翻轉列表:
[-] reverse 1,2,3 # produces 0
超運算符
Unicode 字符 ?
(\x[BB]) 和 ?
(\x[AB]) 和它們的 ASCII連字 >>
和 <<
用于表示列表操作
, 它作用于列表中的每個元素, 然后返回單個列表(或數組)作為結果. 換句話說, 超運算符在 item 上下文中計算它的參數, 但是隨后將操作符分派到每個參數身上,結果作為列表返回。
當書寫超運算符時, 里面不允許出現空格, 即, 兩個 "hyper" 標記之間不能有空格, 并且該操作符是能修改參數的。 在外面空格策略和它的 base 操作符相同。 同樣地, 超運算符的優先級和它的 base 操作符相同。 這意味著對于大部分操作符,你必須使用圓括號括起你使用逗號分割的列表。。
例如:
-? (1,2,3); # (-1, -2, -3)
(1,1,2,3,5) ?+? (1,2,3,5,8); # (2,3,5,8,13),尖括號都朝內時,兩邊列表元素的個數必須相同
(如果你發現你自己這樣做了, 問問你自己是否真的在使用對象或列表; 在后一種情況中, 可能其它的諸如 Z 或 X 的元操作符更合適, 并且不需要括號)
一元超運算符(要么前綴,要么后綴)只有一個 hyper 標記, 位于它的參數那邊, 而中綴操作符通常在參數的每一邊以表明有兩個參數。
一元超運算符
一元超運算符的意思取決于操作符是否是結構非關聯的操作符。 大部分操作符不是結構的。
對于中綴操作符,如果兩者其中之一的一個參數的長度不夠,那么 Perl 會「提高」它,但是只有你把 hyper 標記「尖」的那一端指向它時,Perl 才會提升長度不夠的那一端。
(3,8,2,9,3,8) >>->> 1; # (2,7,1,8,2,7)
@array ?+=? 42; # add 42 to each element
實際上,對于一個無序的諸如 Bag 的類型來說,一個提升過的標量是唯一能工作于該類型中的東西:
Bag(3,8,2,9,3,8) >>->> 1; # Bag(2,7,1,8,2,7) === Bag(1,2,2,7,7,8)
# Cannot modify an immutable Bag
> Bag(3,8,2,9,3,8) # Bag 的用法以改變
bag(9, 8(2), 3(2), 2)
換句話說,把小于號那端指向一個參數告訴超運算符在那邊做我想要做的(dwim, do what i means)。如果你不知道一邊或是另一邊會是 underdimensioned,那么你可以在兩邊都做我想做的:
$left ?*? $right
注意:如果你擔心 Perl 會被像這樣的東西所迷惑:Note: if you are worried about Perl getting confused by something like this:
func ?*?
那么你無需擔心,因為不想之前的版本, Perl 6 從來不會猜測下一個東西是一個項(term)還是一個操作符。在這種情況下,它總是期望一個項除非 func 被預先定義為一個類型或值的名字。
升級絕對不會發生在 hyper 的「鈍」的那一端上。如果你這樣寫:
$bigger ?*? $smaller
$smaller ?*? $bigger
那么會拋出異常,而如果你這樣寫:
$foo ?*? $bar
那么你要求形狀的兩邊是一樣長的,否則會拋出異常。
對于所有 hyper dwimminess,如果運算符的另一邊期望列表的地方出現的是一個標量,那么那個標量會被當做一個重復了 *
次的那個元素的列表。
一旦我們有兩個列表要處理,那么我們不得不決定使兩邊的元素長度相一致。如果兩邊都是 dwimmy,那么較短的那個列表會重復盡可能多的次數以使元素的個數合適。
如果只有一邊是 dwimmy,那么那一端的列表只會被增長或截斷以適應另一邊的 non-dwimmy 的那個列表。
不管從數組的形狀的 dwim 是強制的還是自然發生的,一旦選擇了 dwim 的那一端,在 dwimmy 端的 dwim 語義總是:
下面是一些例子:
(1,2,3,4) ?+? (1,2) # always error,尖括號都朝內時,兩邊元素必須個數相同
(1,2,3,4) ?+? (1,2) # 2,4,4,6 rhs dwims to 1,2,1,2
(1,2,3) ?+? (1,2) # 2,4,4 rhs dwims to 1,2,1
(1,2,3,4) ?+? (1,2) # 2,4 lhs dwims to 1,2
(1,2,3,4) ?+? (1,2) # 2,4,4,6 rhs dwims to 1,2,1,2
(1,2,3) ?+? (1,2) # 2,4,4 rhs dwims to 1,2,1
(1,2,3) ?+? 1 # 2,3,4 rhs dwims to 1,1,1
當使用一元
操作符時, 你總是把鈍的那端對準單個運算對象, 因為沒有出現重復的東西:
@negatives = -? @positives;
@positions?++; # Increment all positions
@positions.?++; # Same thing, dot form
@positions?.++; # Same thing, dot form 報錯
@positions.?.++; # Same thing, dot form
@positions\ .?\ .++; # Same thing, unspace form
@objects.?.run();
("f","oo","bar").?.chars; # (1,2,3)
注意方法調用實際上是后綴操作符, 而非中綴操作符, 所以, 你不能在點號后面放上一個 ?
超運算符在嵌套數組中是被遞歸地定義的, 所以:
-? [[1, 2], 3] # [-?[1, 2], -?3] 得到 -1 -2 -3
# == [[-1, -2], -3]
[[1, 2], 3] ?+? [4, [5, 6]] # [[1,2] ?+? 4, 3 ?+? [5, 6]],得到 5 6 8 9
# == [[5, 6], [8, 9]]
超運算符也能作用于散列,就像作用于數組一樣。
%foo ?+? %bar;
得到兩個鍵的交集(對應的鍵值相加)
> my %foo = "Tom" => 98, "Larry" => 100, "Bob" => "49";
("Tom" => 98, "Larry" => 100, "Bob" => "49").hash
> my %bar = "Tom" => 98, "Larry" => 100, "Vivo" => 86
("Tom" => 98, "Larry" => 100, "Vivo" => 86).hash
> %foo ?+? %bar
("Tom" => 196, "Larry" => 200).hash
而:
> %foo ?+? %bar;
("Tom" => 196, "Larry" => 200, "Bob" => 49, "Vivo" => 86).hash
得到兩個鍵的并集(鍵值相加)
不對稱的 hypers 也有用; 例如, 如果你說:
%outer ?+? %inner;
只有在 %outer 中已經存在的 %inner 鍵才會出現在結果中.
> my %inner = "a" => 11;
> my %outer = "a" => 9, "b" => 12;
> %outer ?+? %inner # a => 20, b => 12
然而,
%outer ?+=? %inner;
假設你想讓 %outer 擁有鍵的并集,累加鍵值
> my %inner = "a" => 11;
> my %outer = "a" => 9, "b" => 12;
> %outer ?+=? %inner; # a => 20, b => 12
> say %outer # a => 20, b => 12
> say %inner # a => 11
注意, hypers 允諾你不必關心處理以怎樣的順序發生,只保證結果的結構和輸入的形式保持一致。從系統角度也不能保證操作是并行化的。
高效的并行化要求某種程度的不帶更多額外工作的工作分割,系統被允許平衡并行處理的惰性需求。
例如, 一個算法想把一個列表分成2個等長的子列表是不會起作用的, 如果你不得不提前計算好列表長度, 因為你不是總能計算出長度。可以采取各種方法:
按需切換要并行處理的群組, 或交錯循環地使用一組 N 個核心的處理器,或任何東西。在該限制下, 一個簡單、非并行、逐項的惰性實現就在 sepc 之中了,但是不太可能高效的使用多核。‘
不考慮性能要求,如果算法依賴于這些采用的方法, 那也是錯誤的。
Reduction 操作符
任何中綴操作符(除了 non-associating 操作符)都可以在 term 位置處被方括號圍住, 以創建使用使用該操作符進行換算的列表操作符:
[+] 1, 2, 3; # 1 + 2 + 3 = 6
my @a = (5,6);
[*] @a; # 5 * 6 = 30
對于所有的元操作符來說, 在 metatoken
里面是不允許有空格的.
換算操作符和列表前綴的優先級相同。 實際上, 換算操作符就是一個列表前綴,被當作一個操作符來調用。因此, 你可以以兩種方式的任何一種來實現換算操作符。要么你也能寫一個顯式的列表操作符:
multi prefix:<[+]> (*@args) is default {
my $accum = 0;
while (@args) {
$accum += @args.shift();
}
return $accum;
}
或者你能讓系統根據對應的中綴操作符為你自動生成一個:
&prefix:<[*]> ::= &reduce.assuming(&infix:<*>, 1);
&prefix:<[**]> ::= &reducerev.assuming(&infix:<**>);
如果換算操作符和中綴操作符的定義是獨立的, 那換算操作符和該操作符的結合性要相同:
[-] 4, 3, 2; # 4-3-2 = (4-3)-2 = -1
[**] 4, 3, 2; # 4**3**2 = 4**(3**2) = 262144
對于 list-associative 操作符(優先級表中的 X),實現必須把參數的 listiness 考慮在內; 即,如果重復地應用一個二元版本的操作符會產生錯誤的結果,那么它就不會被那樣實現。 例如:
[^^] $a, $b, $c; # means ($a ^^ $b ^^ $c), NOT (($a ^^ $b) ^^ $c)
對于 chain-associative 操作符(像 <), 所有的參數被一塊兒接收, 就像你顯式地寫出:
[<] 1, 3, 5; # 1 < 3 < 5
對于列表中綴操作符, 輸入列表不會被展平, 以至于多個 parcels 可以以逗號分割形式的參數傳遞進來:
[X~] (1,2), <a b>; # 1,2 X~ <a b>
如果給定的參數少于 2 個, 仍然會用給定的參數嘗試分派, 并根據那個分派的接受者來處理少于 2 個參數的情況。 注意,默認的列表操作符簽名是最通用的, 所以, 你被允許根據類型去定義不同的方式處理單個參數的情況:
multi prefix:<[foo]> (Int $x) { 42 }
multi prefix:<[foo]> (Str $x) { fail "Can't foo a single Str" }
然而, 0 參數的情況不能使用這種方式定義, 因為沒有類型信息用于分派。操作符要想指定一個同一值應該通過指定一個接收 0 個參數的 multi 變體來實現這:
multi prefix:<[foo]> () { 0 }
在內建操作符中,舉個例子, [+]()
返回 0 , [*]()
返回 1 。
默認地, 如果有一個參數, 內建的換算操作符就返回那個參數。 然而, 這種默認對于像 <
那樣返回類型和接收參數不同的操作符沒有效果,所以這種類型的操作符重載了單個參數的情況來返回更有意義的東西。為了和鏈式語義保持一致, 所有的比較操作符都對于 1 個或 0 個參數返回 Bool::True。
你也可以搞一個逗號操作符的換算操作符。 這正是 circumfix:<[ ]>
匿名數組構建器的列表操作符形式:
[1,2,3] # make new Array: 1,2,3
[,] 1,2,3 # 與上相同
內置換算操作符返回下面的同一值:
[**]() # 1 (arguably nonsensical)
[*]() # 1
[/]() # fail (換算沒有意義)
[%]() # fail (換算沒有意義)
[x]() # fail (換算沒有意義)
[xx]() # fail (換算沒有意義)
[+&]() # -1 (from +^0, the 2's complement in arbitrary precision)
[+<]() # fail (換算沒有意義)
[+>]() # fail (換算沒有意義)
[~&]() # fail (sensical but 1's length indeterminate)
[~<]() # fail (換算沒有意義)
[~>]() # fail (換算沒有意義)
[+]() # 0
[-]() # 0
[~]() # ''
[+|]() # 0
[+^]() # 0
[~|]() # '' (length indeterminate but 0's default)
[~^]() # '' (length indeterminate but 0's default)
[&]() # all()
[|]() # any()
[^]() # one()
[!==]() # Bool::True (also for 1 arg)
[==]() # Bool::True (also for 1 arg)
[before]() # Bool::True (also for 1 arg)
[after]() # Bool::True (also for 1 arg)
[<]() # Bool::True (also for 1 arg)
[<=]() # Bool::True (also for 1 arg)
[>]() # Bool::True (also for 1 arg)
[>=]() # Bool::True (also for 1 arg)
[~~]() # Bool::True (also for 1 arg)
[!~~]() # Bool::True (also for 1 arg)
[eq]() # Bool::True (also for 1 arg)
[!eq]() # Bool::True (also for 1 arg)
[lt]() # Bool::True (also for 1 arg)
[le]() # Bool::True (also for 1 arg)
[gt]() # Bool::True (also for 1 arg)
[ge]() # Bool::True (also for 1 arg)
[=:=]() # Bool::True (also for 1 arg)
[!=:=]() # Bool::True (also for 1 arg)
[===]() # Bool::True (also for 1 arg)
[!===]() # Bool::True (also for 1 arg)
[eqv]() # Bool::True (also for 1 arg)
[!eqv]() # Bool::True (also for 1 arg)
[&&]() # Bool::True
[||]() # Bool::False
[^^]() # Bool::False
[//]() # Any
[min]() # +Inf
[max]() # -Inf
[=]() # Nil (same for all assignment operators)
[,]() # []
[Z]() # []
[=] $x, @y, $z, 0
[+=] $x, @y, $z, 1
等價于:
$x = @y[0] = @y[1] = @y[2] ... @y[*-1] = $z = 0
$x += @y[0] += @y[1] += @y[2] ... @y[*-1] += $z += 1
而不是:
$x = @y = $z = 0;
$x += @y += $z += 1;
my :($b, $c); # okay
sub foo :($a,$b) {...} # okay
->
"pointy block" token 也引入簽名, 但是這種情況你必須省略冒號和括號. 例如, 如果你在定義 loop block 的 "循環變量":
for @dogpound -> Dog $fido { ... }
$foo.bar.baz.bletch.whatever.attr[] = 1,2,3;
空的 [] 和 .[] 后綴操作符被解釋為 0 維下標, 這返回整個數組, 不是作為一個一維的空切片, 返回空元素. 這同樣適用于散列上的 {} 和 .{} , 還有 <>
, .<>
,??
, 和 .??
。