第二天-Perl 6: 符號, 變量和容器
對容器的基本理解對于在 Perl 6 中進(jìn)行愉快的編程是至關(guān)重要的蹂季。它們無處不在默刚,不僅影響你獲得的變量類型,還決定了 List
和 Map
在迭代時的行為方式轨奄。
今天裳仆,我們將學(xué)習(xí)什么是容器,以及如何使用它們晦墙,但是首先悦昵,我希望你暫時忘記你對 Perl 6 的符號和變量的所有知識或懷疑,特別是如果你來自 Perl 5 的背景晌畅。 忘記一切但指。
把錢拿出來
在 Perl 6 中,變量以 $
符號為前綴,用綁定運(yùn)算符(:=
)賦值棋凳。 像這樣:
my $foo := 42;
say "The value is $foo"; # OUTPUT: ?The value is 42
?
如果你已經(jīng)按照我的建議來忘記你所知道的一切拦坠,那么學(xué)習(xí) List
和 Hash
類型也是一樣:
my $ordered-things := <foo bar ber>;
my $named-things := %(:42foo, :bar<ber>);
say "$named-things<foo> bottles of $ordered-things[2] on the wall";
# OUTPUT: ?42 bottles of ber on the wall
?
.say for $ordered-things; # OUTPUT: ?foo
bar
ber
?
.say for $named-things; # OUTPUT: ?bar => ber
foo => 42
?
了解這一點(diǎn),你可以寫出各種各樣的程序剩岳,所以如果你開始覺得有太多的東西需要學(xué)習(xí)贞滨,記住你不需要一次學(xué)習(xí)所有東西。
我們祝你有一個愉快的列表圣誕
讓我們試著用我們的變量做更多的事情拍棕。 想要更改列表中的值并不罕見晓铆。 到目前為止我們的表現(xiàn)如何呢?
my $list := (1, 2, 3);
$list[0] := 100;
# OUTPUT: ?Cannot use bind operator with this left-hand side […] ?
盡管我們可以綁定到變量莫湘,但是如果我們試圖綁定到某個值尤蒿,我們會得到一個錯誤,無論這個值是來自 List
還是只是一個字面值:
1 := 100;
# OUTPUT: ?Cannot use bind operator with this left-hand side […] ?
這就是為什么列表是不可變的幅垮。 然而腰池,這是一個實(shí)現(xiàn)愿望的季節(jié),所以我們希望有一個可變的List
忙芒!
我們需要掌握的是一個 Scalar
對象示弓,因?yàn)榻壎ú僮鞣梢允褂盟?顧名思義,一個 Scalar
存儲一個東西呵萨。 你不能通過 .new
方法實(shí)例化一個 Scalar
奏属,但是我們可以通過聲明一些詞法變量來得到它們。 不需要費(fèi)心給他們的名字:
my $list := (my $, my $, my $);
$list[0] := 100;
say $list; # OUTPUT: ?(100 (Any) (Any))
?
輸出中的 (Any)
是容器的默認(rèn)值(稍后一點(diǎn))潮峦。 上面囱皿,似乎我們設(shè)法在 List
創(chuàng)建后將一個值綁定到列表的元素上,我們不是嗎忱嘹? 確實(shí)我們做了嘱腥,但是...
my $list := (my $, my $, my $);
$list[0] := 100;
$list[0] := 200;
# OUTPUT: ?Cannot use bind operator with this left-hand side […] ?
綁定操作用一個新的值(100
)代替 Scalar
容器,所以如果我們試圖再次綁定拘悦,我們又回到了原來的方括號那個齿兔,試圖綁定到一個值,而不是一個容器础米。
我們需要一個更好的工具分苇。
That's Your Assignment
綁定運(yùn)算符有一個表親:賦值運(yùn)算符(=
)。 我們不用一個綁定操作符替換我們的 Scalar
容器屁桑,而是使用賦值操作符來賦值或者“存儲”我們在容器中的值:
my $list := (my $ = 1, my $ = 2, my $ = 3);
$list[0] = 100;
$list[0] = 200;
say $list;
# OUTPUT: ?(200 2 3)
?
現(xiàn)在医寿,我們可以從一開始就指定我們的原始值,并且可以隨時用其他值替換它們蘑斧。 我們甚至可以變得時髦靖秩,并在每個容器上放置不同的類型約束:
my $list := (my Int $ = 1, my Str $ = '2', my Rat $ = 3.0);
$list[0] = 100; # OK!
$list[1] = 42; # Typecheck failure!
# OUTPUT: ?Type check failed in assignment;
# expected Str but got Int (42) […] ?
這有些放縱艾帐,但有一件事可以使用類型約束:$list
變量。 我們將其限制為 Positional
角色盆偿,以確保它只能保持 Positional
類型柒爸,就像 List
和 Array
:
my Positional $list := (my $ = 1, my $ = '2', my $ = 3.0);
不知你咋想的,但是這對我來說看起來非常冗長事扭。 幸運(yùn)的是捎稚,Perl 6 有語法來簡化它!
Position@lly
首先求橄,讓我們擺脫變量的顯式類型約束今野。 在 Perl 6 中,您可以使用 @
而不是 $
作為符號來表示您希望變量受到角色 Positional
的類型約束:
my @list := 42;
# OUTPUT: ?Type check failed in binding;
# expected Positional but got Int (42) […] ?
其次罐农,我們將使用方括號來代替圓括號來存儲我們的 List
条霜。 這告訴編譯器創(chuàng)建一個 Array
而不是一個 List
。 Array
s 是可變的涵亏,它們將每個元素自動粘貼到 Scalar
容器中宰睡,就像我們在前一節(jié)中手動操作一樣:
my @list := [1, '2', 3.0];
@list[0] = 100;
@list[0] = 200;
say @list;
# OUTPUT: ?[200 2 3]
?
我們的代碼變得更短了,但我們可以折騰更多的字符气筋。 就像賦值給$
-sigiled 變量而不是綁定一樣拆内,你可以賦值給@
-sigiled 變量來獲得一個自由的 Array
。 如果我們切換到賦值宠默,我們可以完全擺脫方括號:
my @list = 1, '2', 3.0;
好麸恍,簡潔。
類似的想法背后是 %
- 和 &
符號化的變量搀矫。 %
sigil 意味著 Associative
角色的類型約束抹沪,并為賦值提供相同的快捷方式(給你一個 Hash
),并為這些值創(chuàng)建 Scalar
容器瓤球。 對于角色 Callable
和賦值的 &
-sigiled變量類型 - 行為類似于 $
sigils融欧,給出一個可以修改其值的自由 Scalar
容器:
my %hash = :42foo, :bar<ber>;
say %hash; # OUTPUT: ?{bar => ber, foo => 42}
?
my &reversay = sub { $^text.flip.say }
reversay '6 lreP ? I'; # OUTPUT: ?I ? Perl 6
?
# store a different Callable in the same variable
&reversay = *.uc.say; # a WhateverCode object
reversay 'I ? Perl 6'; # OUTPUT: ?I ? PERL 6
?
The One and Only
之前我們知道賦值給 $
-sigiled 變量會給你一個免費(fèi)的 Scalar
容器。 由于標(biāo)量冰垄,顧名思義蹬癌,只包含一個東西......如果你把一個 List
放到 Scalar
中會發(fā)生什么权她? 畢竟虹茶,當(dāng)你試圖這樣做的時候,宇宙仍然沒有被扼殺:
my $listish = (1, 2, 3);
say $listish; # OUTPUT: ?(1 2 3)
?
這樣的行為可能使 Scalar
看起來似乎是一個用詞不當(dāng)隅要,但它確實(shí)把整個列表視為一個東西蝴罪。 我們可以通過幾種方式觀察其差異。 我們來比較綁定到 $
-sigiled 變量的 List
(所以不包含 Scalar
)和賦值給 $
-sigiled 變量(自動 Scalar
容器)的 List
:
# Binding:
my $list := (1, 2, 3);
say $list.perl;
say "Item: $_" for $list;
# OUTPUT:
# (1, 2, 3)
# Item: 1
# Item: 2
# Item: 3
# Assignment:
my $listish = (1, 2, 3);
say $listish.perl;
say "Item: $_" for $listish;
# OUTPUT:
# $(1, 2, 3)
# Item: 1 2 3
.perl
方法給了我們一個額外的見解步清,并在第二個 List
之前顯示了一個 $
要门,以表明它在 Scalar
中是集裝箱化的虏肾。 更重要的是,當(dāng)我們用 for
循環(huán)迭代我們的 List
s 時欢搜,第二個 List
結(jié)果只有一個迭代:整個 List
作為一個項(xiàng)目封豪! Scalar
沒有辜負(fù)它的名字。
這種行為不僅僅是學(xué)術(shù)上的興趣炒瘟。 回想一下吹埠,Array
s(和Hash
es)為它們的值創(chuàng)建Scalar
容器。 這意味著如果我們嵌套東西疮装,即使我們選擇一個單獨(dú)的列表或散列在里面存儲著 Array
(或 Hash
)缘琅,并試圖迭代它,它將只被視為一個單一的項(xiàng)目:
my @stuff = (1, 2, 3), %(:42foo, :70bar);
say "List Item: $_" for @stuff[0];
say "Hash Item: $_" for @stuff[1];
# OUTPUT:
# List Item: 1 2 3
# Hash Item: bar 70
# foo 42
同樣的推理(即 Scalar
容器中的列表和散列是單個項(xiàng)目)適用于當(dāng)您試圖壓扁 Array
的元素或?qū)⑺鼈冏鳛閰?shù)傳遞給 slurpy 參數(shù)時:
my @stuff = (1, 2, 3), %(:42foo, :70bar);
say flat @stuff;
# OUTPUT: ?((1 2 3) {bar => 70, foo => 42})
?
-> *@args { @args.say }(@stuff)
# OUTPUT: ?[(1 2 3) {bar => 70, foo => 42}]
?
正是這種行為可以將 Perl 6 初學(xué)者推上墻廓推,特別是那些來自 Perl 5 自動展平語言的人刷袍。然而,現(xiàn)在我們知道為什么會出現(xiàn)這種行為樊展,我們可以改變它呻纹!
Decont
如果 Scalar
容器是罪魁禍?zhǔn)祝覀兯龅木褪莿h除它专缠。 我們需要將我們的列表和哈希值去容器化居暖,或者簡稱為 “decont”。 在你的 Perl 6 之旅中藤肢,你可以找到幾種方法來完成這個工作太闺,但是為此設(shè)計(jì)的一個方法就是 decont methodop(<>
):
my @stuff = (1, 2, 3), %(:42foo, :70bar);
say "Item: $_" for @stuff[0]<>;
say "Item: $_" for @stuff[1]<>;
# OUTPUT:
# Item: 1
# Item: 2
# Item: 3
# Item: bar 70
# Item: foo 42
它很容易記住:它看起來像一個被擠壓的盒子(一個被踩踏的容器)嘁圈。 在通過索引到 Array
中檢索我們的容器化項(xiàng)目之后省骂,我們附加了 decont 并從 Scalar
容器中移除了內(nèi)容,導(dǎo)致我們的循環(huán)遍歷它們中的每個項(xiàng)目最住。
如果您希望一次去除 Array
中的每個元素钞澳,只需使用超運(yùn)算符(?
,或 >>
涨缚,如果您更喜歡使用 ASCII)就可以使用 decont:
my @stuff = (1, 2, 3), %(:42foo, :70bar);
say flat @stuff?<>;
# OUTPUT: ?(1 2 3 bar => 70 foo => 42)
?
-> *@args { @args.say }(@stuff?<>)
# OUTPUT: ?[1 2 3 bar => 70 foo => 42]
?
隨著容器被刪除轧粟,我們的列表和散列就像我們想要的那樣變平。 當(dāng)然脓魏,我們可以避免使用 Array
兰吟,而將原始 List
綁定到變量上。 由于 List
沒有把它們的元素放入容器茂翔,所以沒有任何東西可以去除:
my @stuff := (1, 2, 3), %(:42foo, :70bar);
say flat @stuff;
# OUTPUT: ?(1 2 3 bar => 70 foo => 42)
?
-> *@args { @args.say }(@stuff)
# OUTPUT: ?[1 2 3 bar => 70 foo => 42]
?
不要讓它溜走
當(dāng)我們在這里的時候混蔼,值得注意的是,當(dāng)他們想要執(zhí)行decont(我們不是在傳遞參數(shù)給 Callable
的時候使用它)時珊燎,許多人使用 slip運(yùn)算符(|
):
my @stuff = (1, 2, 3), (4, 5);
say "Item: $_" for |@stuff[0];
# OUTPUT:
# Item: 1
# Item: 2
# Item: 3
雖然它可以完成工作惭嚣,但可能會引入微妙的 bugs遵湖,這些 bug 可能很難追查到。 嘗試在這里找到一個晚吞,在一個程序中迭代了一個無限的非負(fù)整數(shù)列表延旧,并打印那些素?cái)?shù):
my $primes = ^∞ .grep: *.is-prime;
say "$_ is a prime number" for |$primes;
放棄? 這個程序會導(dǎo)致內(nèi)存泄漏... 非常緩慢槽地。 盡管我們遍歷了無限的項(xiàng)目列表垄潮,但這不是問題,因?yàn)?.grep
方法返回的 Seq
對象不會保留已經(jīng)迭代的項(xiàng)目闷盔,因此內(nèi)存使用永遠(yuǎn)不會增長弯洗。
有問題的部分是我們的 |
slip 操作符。 它將我們的 Seq
轉(zhuǎn)換成一個 Slip
逢勾,這是一個 List
類型牡整,并且保存我們已經(jīng)消耗的所有的值。 如果您希望在 htop
中看到增長溺拱,那么這個程序的修改版本會更快地增長:
# CAREFUL! Don't consume all of your resources!
my $primes = ^∞ .map: *.self;
Nil for |$primes;
讓我們再試一次逃贝,但是這次使用 decont 方法 op:
my $primes = ^∞ .map: *.self;
Nil for $primes<>;
內(nèi)存使用現(xiàn)在是穩(wěn)定的,程序可以坐在那里迭代直到時間結(jié)束迫摔。 當(dāng)然沐扳,因?yàn)槲覀冎肋@是 Scalar
容器導(dǎo)致的容器化,我們希望在這里避免它句占,所以我們可以簡單地將 Seq
綁定到變量上:
my $primes := ^∞ .map: *.self;
Nil for $primes;
I Want Less
如果你討厭符號沪摄,Perl 6 會得到一些你可以微笑的東西:無符號的變量。 只要在聲明中加一個反斜杠的前綴纱烘,表示你不想要討厭的符號:
my \Δ = 42;
say Δ2; # OUTPUT: ?1764
?
你不會得到任何這樣的變量的自由 Scalar
杨拐,因此,在聲明期間擂啥,綁定或賦值給他們沒有任何區(qū)別哄陶。 它們的行為類似于將值綁定到 $
-sigiled 變量的行為,包括綁定 Scalar
s 并使變量可變:
my \Δ = my $ = 42;
Δ = 11;
say Δ2; # OUTPUT: ?121
?
一個更常見的地方哺壶,你可能會看到這樣的變量是作為例程的參數(shù)屋吨,在這里,這意味著你想把 is raw
trait 應(yīng)用到參數(shù)上山宾。 這在 +
positional slurpy 參數(shù)的含義也是存在的(不需要反斜杠)至扰,如果它是 is raw
的,意味著你將不會得到不需要的 Scalar
容器塌碌,因?yàn)樗且粋€ Array
渊胸,因?yàn)樗哂?@
sigil:
sub sigiled ($x is raw, +@y) {
$x = 100;
say flat @y
}
sub sigil-less (\x, +y) {
x = 200;
say flat y
}
my $x = 42;
sigiled $x, (1, 2), (3, 4); # OUTPUT: ?((1 2) (3 4))
?
say $x; # OUTPUT: ?100
?
sigil-less $x, (1, 2), (3, 4); # OUTPUT: ?(1 2 3 4)
?
say $x; # OUTPUT: ?200
?
Defaulting on Default Defaults
容器提供的一個很棒的功能是默認(rèn)值旬盯。 你可能聽說過在 Perl 6 中台妆,Nil
表示缺少一個值翎猛,而不是一個值。 容器默認(rèn)值就是它的作用:
my $x is default(42);
say $x; # OUTPUT: ?42
?
$x = 10;
say $x; # OUTPUT: ?10
?
$x = Nil;
say $x; # OUTPUT: ?42
?
一個容器的默認(rèn)值是使用 is default
trait 給它的接剩。 它的參數(shù)是在編譯時計(jì)算的切厘,每當(dāng)容器缺少一個值時,就使用結(jié)果值懊缺。 由于 Nil
的工作是表明這一點(diǎn)疫稿,因此將 Nil
分配到容器中將導(dǎo)致容器包含其默認(rèn)值,而不是 Nil
鹃两。
可以給 Array
和 Hash
容器賦予默認(rèn)值遗座,如果你希望你的容器在字面上包含 Nil
,當(dāng)沒有值時俊扳,只需要指定 Nil
作為默認(rèn)值:
my @a is default<meow> = 1, 2, 3;
say @a[0, 2, 42]; # OUTPUT: ?(1 3 meow)
?
@a[0]:delete;
say @a[0]; # OUTPUT: ?meow
?
my %h is default(Nil) = :bar<ber>;
say %h<bar foos>; # OUTPUT: ?(ber Nil)
?
%h<bar>:delete;
say %h<bar> # OUTPUT: ?Nil
?
容器的默認(rèn)值有一個默認(rèn)的默認(rèn)值:容器上的顯式類型約束:
say my Int $y; # OUTPUT: ?(Int)
?
say my Mu $z; # OUTPUT: ?(Mu)
?
say my Int $i where *.is-prime; # OUTPUT: ?(<anon>)
?
$i.new; # OUTPUT: (exception) ?You cannot create […]?
如果沒有明確的類型約束途蒋,默認(rèn)的默認(rèn)值是一個 Any
類型的對象:
say my $x; # OUTPUT: ?(Any)
?
say $x = Nil; # OUTPUT: ?(Any)
?
請注意,您可能在可選參數(shù)的例程簽名中使用的默認(rèn)值不是容器默認(rèn)值馋记,將 Nil
分配給子例程參數(shù)或分配給參數(shù)不會使用簽名中的默認(rèn)值号坡。
自定義
如果容器的標(biāo)準(zhǔn)行為不適合您的需求,您可以使用 Proxy
類型創(chuàng)建自己的容器:
my $collector := do {
my @stuff;
Proxy.new: :STORE{ @stuff.push: @_[1] },
:FETCH{ @stuff.join: "|" }
}
$collector = 42;
$collector = 'meows';
say $collector; # OUTPUT: ?42|meows
?
$collector = 'foos';
say $collector; # OUTPUT: ?42|meows|foos
?
接口有點(diǎn)笨重梯醒,但它完成了工作宽堆。我們使用 .new
方法創(chuàng)建 Proxy
對象,該方法需要兩個必需的命名參數(shù):STORE
和 FETCH
茸习,每個都帶一個 Callable
畜隶。
每當(dāng)從容器中讀取一個值時,FETCH
Callable
被調(diào)用号胚,這可能比直接看到的次數(shù)多出現(xiàn)一次:在上面的代碼中代箭,當(dāng)容器通過調(diào)度和例程這兩個調(diào)用滲透時,FETCH
Callable
被調(diào)用10次涕刚。 Callable
被調(diào)用一個單一的位置參數(shù):Proxy
對象本身嗡综。
無論何時將值存儲到我們的容器中(例如,使用賦值運(yùn)算符(=
))杜漠,STORE
Callable
都會被調(diào)用极景。 Callable
的第一個位置參數(shù)是 Proxy
對象本身,第二個參數(shù)是存儲的值驾茴。
我們希望 STORE
和 FETCH
Callable
共享 @stuff
變量盼樟,所以我們使用 do
statement prefix 和一個代碼塊來很好地包含它。
我們將我們的 Proxy
綁定到一個變量锈至,其余的只是正常的變量用法晨缴。輸出顯示我們的自定義容器提供的改變過的行為。
Proxies 也可以方便地作為返回值來提供具有可變屬性的額外行為峡捡。例如击碗,這里有一個屬性筑悴,從外部看來只是一個正常的可變屬性,但實(shí)際上強(qiáng)制它的值從 Any
任何類型變?yōu)?Int
類型:
class Foo {
has $!foo;
method foo {
Proxy.new: :STORE(-> $, Int() $!foo { $!foo }),
:FETCH{ $!foo }
}
}
my $o = Foo.new;
$o.foo = ' 42.1e0 ';
say $o.foo; # OUTPUT: ?42
?
很甜蜜稍途! 如果你想要一個更好的接口的 Proxy
與一些更多的功能阁吝,請檢查 Proxee 模塊。
這就是全部械拍,伙計(jì)
那關(guān)于這一切突勇。 在 Perl 6 中你將會看到的剩下的動物是 “twigils”:名稱前帶有兩個符號的變量,但是就容器而言坷虑,它們的行為與我們所介紹的變量相同甲馋。 第二個符號只是表示附加信息,如變量是隱含的位置參數(shù)還是命名參數(shù)...
sub test { say "$^implied @:parameters[]" }
test 'meow', :parameters<says the cat>;
# OUTPUT: ?meow says the cat
?
...或者該變量是私有屬性還是公共屬性:
with class Foo {
has $!foo = 42;
has @.bar = 100;
method what's-foo { $!foo }
}.new {
say .bar; # OUTPUT: ?[100]
?
say .what's-foo # OUTPUT: ?42
?
}
然而迄损,這是另一天的旅程摔刁。
結(jié)論
Perl 6 有一個豐富的變量和容器系統(tǒng),與 Perl 5 有很大的不同海蔽。理解它的工作方式是非常重要的共屈,因?yàn)樗鼤绊懥斜砗凸P袨榈牡驼归_方式。
賦值給變量提供了有價(jià)值的快捷方式党窜,例如提供Scalar
拗引,Array
或Hash
容器,具體取決于符號幌衣。 如果您需要矾削,綁定到變量允許您繞過這樣的快捷方式。
在 Perl 6 中存在無符號變量豁护,它們與具有綁定功能的 $
-sigiled 變量具有相似的行為哼凯。 當(dāng)用作參數(shù)時,這些變量的行為就像應(yīng)用了 is raw
trait一樣楚里。
最后断部,容器可以有默認(rèn)值,可以創(chuàng)建自己的自定義容器班缎,可以綁定到變量或從例程返回蝴光。
節(jié)日快樂!