Perl 6 documentnode
翻譯c++
本翻譯意在幫助理解原文,因我的水平有限,不免會有翻譯不當,若是疑惑請在評論中指出。程序員
原文編程
Perl 6 從本質上講是一門面向對象語言,雖然它容許你編寫代碼時使用其餘編程風格。
Perl 6 提供了豐富的預約義類型,它們能夠分爲兩大類:普通類型以及原生類型。
原生類型被用做低級類型(好比uint16
),它們並無普通變量擁有的功能,可是若是你在他們身上調用方法,它們會包裝成普通對象。
全部 你能存儲在變量裏面的東西除了原生類型就是對象,原生類型包括字面量,類型(類型對象),代碼,以及容器。安全
你能夠經過調用方法來使用對象,在一個表達式中調用一個方法,在對象後添加一個點,緊跟着是方法名字:less
say "abc".uc; ### 輸出: ABC
上面的代碼在"abc"
上調用了方法uc
,"abc"
是類型Str
的一個對象。若是你想給方法提供參數,在方法以後添加一對括號
,參數放在括號裏面便可:函數
my $formatted-text = "Fourscore and seven years ago...".indent(8);
$formatted-text
如今包含了上面的文本,可是縮進了8個空格。
若是想傳遞多個參數,只須要逗號
隔開多個參數:工具
my @words = "Abe", "Lincoln"; @words.push("said", $formatted-text.comb(/\w+/));
另一種調用方法的語法就是在方法名和參數列表之間用冒號
隔開:post
say @words.join: '--'; ### 輸出: Abe--Lincoln--said--Fourscore--and--seven--years--ago
所以若是你想在傳遞參數的時候省略括號,你就須要在方法後面添加:
,一個方法調用若是沒有冒號或者括號被看成沒有參數列表的方法調用:ui
say 4.log: ; ### 1.38629436111989 ( 4的天然對數 ) say 4.log: +2; ### 2 ( 4的以2爲底的對數 ) say 4.log +2; ### 3.38629436111989 ( 4的天然對數,而後加上2 )
許多看起來不像是方法調用的操做(好比智能匹配,或者在字符串裏面內插變量)會致使方法調用。
方法可能會返回可修改的容器,在這種狀況下你能夠對方法調用的返回值進行賦值操做,這也是對象的可讀寫屬性的使用方法:
$*IN.nl-in = "\r\n";
在上面代碼中,咱們調用了對象$*IN
的nl-in
無參方法,而後咱們使用=
運算符給返回的容器賦值。
全部的對象支持來自類Mu的方法,它是類型層次的根,換句話說,全部的類都繼承自Mu。
類型自己也是對象,你可使用它們的名字獲取類型對象:
my $int-type-obj = Int;
你能夠經過使用WHAT
方法查詢其餘對象的類型(這其實是一個宏方法形式):
my $int-type-obj = 1.WHAT;
類型對象(不一樣於Mu)可使用===
運算符比較是否相等:
sub f(Int $x) { if $x.WHAT === Int { say "you passed an Int"; } else { say "you passed a subtype of Int"; } }
不過,在大多數狀況下,.isa
方法就足夠了:
sub f($x) { if $x.isa(Int) { ... } ... }
子類型檢測能夠經過使用智能匹配:
if $type ~~ Real { say '$type contains Real or subtype thereof'; }
類使用class
關鍵字聲明,一般跟着一個名字:
class Journey { }
(???)聲明的結果就是一個類型對象被建立,名字Journey
在當前包、lexical scope可用。你還能夠聲明一個詞法做用域的類:
my class Journey { }
這強制類只在當前詞法做用域可見,這在當某個類只是另外一個類或者模塊的實現細節的時候頗有用。
屬性是存在於每個類的實例中的變量,對象的狀態存儲即存儲在它們那裏。在 Perl 6 裏面,全部的屬性都是私有的,他們的聲明一般使用has
說明符和 twigil !
:
class Journey { has $!origin; has $!destination; has @!travellers; has $!notes; }
同時並無用來聲明公共的(甚至是保護的)屬性的那種東西,不過有一種方式能夠自動的生成屬性的 accessor 方法:使用 twigil .
替代!
(.
能夠提醒你生成了方法調用)。
class Journey { has $.origin; has $.destination; has @!travellers; has $.notes; }
默認狀態下是提供只讀 accessor,爲了容許對屬性改變,加上is rw
:
class Journey { has $.origin; has $.destination; has @!travellers; has $.notes is rw; }
如今,當一個Journey
對象被建立時,它的.origin
,.destination
,以及.notes
屬性均可以從類的外部訪問,可是隻有.notes
是能夠修改的。
上面的聲明容許不指定.origin
以及.destination
的值建立Journey
對象,對此你能夠提供一個默認值或者把它標記爲is required確保它被提供。
class Journey { has $.origin is required; # 當origin沒有提供值時會產生編譯錯誤 has $.destination = self.origin eq 'Orlando' ?? 'Kampala' !! 'Orlando'; # 把Orlando設置爲默認值(除非出發點(origin)是Orlando) has @!travellers; has $.notes is rw; }
由於類會從Mu繼承到一個默認的構造方法,加上咱們請求生成的 accessor 方法,咱們的類已經能夠用了。
# 建立一個類實例 my $vacation = Journey.new( origin => 'Sweden', destination => 'Switzerland', notes => 'Pack hiking gear!' ); # 使用存取器;這將會輸出「Sweden」。 say $vacation.origin; # 使用讀寫存取器改變屬性的值 $vacation.notes = 'Pack hiking gear and sunglasses!';
注意默認的 constructor 只會設置擁有 accessor 方法的屬性(私有的屬性沒有 accessor,設置值也沒有用,可是這不會報錯),可是能夠初始化只讀屬性的值。
使用method
關鍵字在類的內部聲明一個方法。
class Journey { has $.origin; has $.destination; has @!travellers; has $.notes is rw; method add_traveller($name) { if $name ne any(@!travellers) { push @!travellers, $name; } else { warn "$name is already going on the journey!"; } } method describe() { "From $!origin to $!destination"; } }
一個方法能夠擁有 signature,就像子例程同樣。屬性能夠在方法中使用,而且老是能夠用 twigil !
來訪問,即便它們是使用的 twigil .
。這實際上是由於,twigil .
是 twigil !
和額外生成的 accessor 方法的組合。
查看上面的代碼,在方法describe
使用$!origin
和$.origin
之間有一些細微的差異,前者老是簡單的查找屬性,代價低廉,而且你能清楚它是這個類中聲明的屬性;後者實際上是一個方法調用,並且有可能會被子類重寫,因此,只有你想顯式的容許重寫時使用$.origin
。
在一個方法內,self
是可用的,它將會綁定到 invocant,也就是說方法調用做用的對象。使用self
能夠進一步的調用 invocant 的方法,對於方法,$.origin
和self.origin
有着相同的效果。
徹底地在類內部使用,不會在其餘任何地方調用的方法聲明的時候在方法名前面加上一個感嘆號標記!
便可,調用它們時候使用感嘆號替代點:
method !do-something-private($x) { ... } method public($x) { if self.precondition { self!do-something-private(2 * $x) } }
私有方法將不會被子類繼承。
一個子方法是不會被子類繼承的公共方法,這個名字源於它們的語義相似與子例程的事實。
子方法對於對象 construction 以及 destruction 頗有用,由於這些任務老是特定於特定類型,子類型必定要重寫它們。
好比default method new在繼承層次上調用了子方法BUILD
:
class Point2D { has $.x; has $.y; submethod BUILD(:$!x, :$!y) { say "Initializing Point2D"; } } class InvertiblePoint2D is Point2D { submethod BUILD() { sub "Initializing InvertiblePoint2D"; } method invert { self.new(x => - $.x, y => - $.y); } } say InvertiblePoint2D.new(x => 1, y => 2);
這將會產生如下輸出:
Initializing Point2D Initializing InvertiblePoint2D InvertiblePoint2D.new(x => 1, y => 2);
一個類能夠有父類:
class Child is Parent1 is Parent2 { }
若是在子類(的對象)上調用一個方法,可是子類卻沒有提供那個方法,若是其中一個父類中存在叫那個名字的方法,就會調用父類的方法。在這裏,父類會被考慮的順序被叫作method resolution order(MRO),Perl 6使用了C3 method resolution order,你能夠經過調用一個類型的元類型的方法獲取它的MRO:
say List<^mro>; # 將會輸出 List() Cool() Any() Mu()
若是一個類沒有指定父類,那麼默認就是Any, 全部的類直接或者間接的繼承自類型層次的根類型Mu。
全部的公共函數的調用都看起來像是c++中「虛函數」調用,這意味着對象的實際類型決定須要調用的方法,而不是聲明類型。
class Parent { method frob { say "the parent class frobs" } } class Child is Parent { method frob { say "the child's somewhat more fancy frob is called" } } my Parent $test; $test = Child.new; $test.frob; # 這將會調用Child的frob方法而不是Parent
這將會產生下列輸出:
the child's somewhat more fancy frob is called
對象的建立一般都是經過方法調用,建立一個類型對象或者相同類型的另外一個對象。
Mu提供了一個 constructor 方法叫作new,它接受命名參數用來初始化公共屬性。
class Point { has $.x; has $.y = 2 * $!x; } my $p = Point.new(x = > 5, y => 2); # ^^^ 繼承自Mu say "x: ", $p.x; say "y: ", $p.y;
這將會輸出:
x:5 y:2
my $p2 = Point.new(x => 5); # y的值將會根據給定的x計算出來 say "x: ", $p.x; say "y: ", $p.y;
這將會輸出:
x:5 y:10
Mu.new
調用 invocant 的bless方法,傳遞給它全部的命名參數,bless
建立一個新的對象,而後調用BUILDALL
函數。BUILDALL
以MRO相反的順序遍歷全部的子類(也就是說從Mu到全部繼承的類),並在每個類中檢查有沒有一個叫作BUILD的方法,若是該方法存在則調用,再傳遞給它來自new方法的全部命名參數;若是該方法不存在,全部公共屬性都會根據對應的(名字相同的命名參數)命名參數的值初始化。在這兩種狀況下,若是BUILD
以及默認機制都沒有初始化的屬性,將會使用默認值(好比上面例子中的2 * $!x
)。
得益於BUILDALL
的默認行爲和自定義的BUILD
子方法,傳遞給從Mu
繼承而來的new
方法的命名參數能夠直接對應MRO
上的任何類的公共屬性,或者BUILD
方法的任何命名參數。
這個對象構造方案對於自定義構造有這麼幾個影響。首先,自定義的BUILD
方法必須老是子方法,不然它會打破子類屬性初始化的順序;其次,BUILD
子方法能夠用來在對象構造期間運行本身的代碼,它們也能夠用來建立用來初始化屬性的別名:
class EncodedBuffer { has $.enc; has $.data; submethod BUILD(:encoding(:$enc), :$data) { $!enc := $enc; $!data := $data; } } my $eb1 = EncodedBuffer.new(encoding => 'utf8', data => [64, 65]); my $eb2 = EncodedBuffer.new(enc => 'utf8', data => [64, 65]); # ''' 如今enc、encoding你均可以使用
由於向例程傳遞參數就是把實參綁定在形參上面,用屬性做爲形參能夠省略沒必要要的步驟,所以上面的例子能夠被改寫成:
submethod BUILD(:$encoding(:$!enc), :$!data) { # 這裏不須要作多餘的事情了 # 函數簽名綁定爲咱們作了全部的事情 }
第三,若是你想要 constructor 接受位置參數,你必須重寫new方法:
class Point { has $.x; has $.y; method new($x, $y) { self.bless(:$x, :$y); } }
然而,這是一個不恰當的作法,由於它使得初始化來自子類的對象變的更加困難。
你須要注意的另外一件事情是new
這個名字在 Perl 6 裏面並不特別(好比關鍵字之類的東西就算特別),它僅僅是一個共同的約定,你能夠在任何的方法中調用bless
函數,或者玩弄低級的方法CREATE
。
另外一種偷換對象建立(hooking into object creation)的方法是編寫你本身的BUILDALL
方法,爲了確保父類的初始化正常工做,你還必須使用callsame
調用父類的BUILDALL
方法。
class MyClass { method BUILDALL(|) { # 在這裏添加你的初始化 callsame; # 調用父類的BUILDALL # 作進一步的檢查 } }
全部類的父類Mu
提供了一個奇妙的名爲clone的方法,它能夠經過拷貝實例的私有屬性的值建立一個新的實例。拷貝是淺拷貝,由於它只是把源實例屬性的值綁定到新的屬性上,而沒有進行值的拷貝。
正如new
提供公共屬性的初始值,clone使用源實例的值覆蓋初始值。(參見文檔中關於Mu的clone的例子)
須要注意的是clone
並非一個submethod
,一個類的提供了自身了clone
就會替換掉Mu
的方法。這裏也沒有像BUILDALL
同樣的自動機制,好比,若是你想爲一個特定的類作深拷貝,你可能須要調用callwith
或者nextwith
對父類進行深拷貝。
class A { has $.a; # ... method clone { nextwith(:a($a.clone)); } }
這在簡單的類上能很好的工做,可是某些狀況下可能須要效仿BUILDALL
,在MRO上進行拷貝工做。
class B is A { has $.b; # ... method clone { my $obj = callsame; $obj.b = $!b.clone(:seed($obj.a.generate_seed)); $obj; } }
Roles 在某些方面上相似與類,它們都是屬性和方法的集合,不一樣的是 roles 也能夠用來描述部分對象的行爲,還有 roles 如何應用到類上,換句話說,類是用來管理實例的,roles 是用來管理行爲以及代碼重用的。
role Serializable { method serialize() { self.perl; # 很是原始的序列化 } method deserialize($buf) { EVAL $buf; # 相對與.perl的反向操做 } } class Point is Serializable { has $.x; has $.y; } my $p = Point.new(:x(1), :y(2)); my $serialized = $p.serialize; # role 提供的方法 my $clone-of-p = Point.deserialize($serialized); say $clone-of-p.x; # 將會輸出 1
一旦編譯器解析到了 role 聲明的關閉括號,role 就再也不是可變的了。
role 的運用徹底不一樣與類的繼承,當一個 role 應用於一個類的時候, role 的方法就會拷貝到類裏面,若是有多個 role 應用到同一個類,衝突(好比 attribute 或者非重載方法重名)將會產生一個編譯錯誤,解決方案是在類中提供一個相同名字的方法。
這比多繼承安全的多,多繼承編譯器永遠不會檢測到衝突,只是簡單的使用MRO出現最先的父類的方法,從而可能違背了程序員的本意。
好比,你發現了一種有效的方法騎牛,你嘗試市場化它做爲一種新的流行的交通工具,你可能有一個類Bull
,表明你家裏的牛,還有一個類Automobile
,表明你能夠駕駛的車。
class Bull { has Bool $.castrated = False; method steer { # 閹割你的牛,讓它變的更加容易操控 $!castrated = True; return self; } } class Automobile { has $.direction; method steer($!direction) { } } class Taurus is Bull is Automobile { } my $t = Taurus.new(); $t.steer; # 閹割 $t
使用這種設置,你可憐的客戶可能發現他們沒法控制他們的金牛座,而你就不能出售更多的產品,這種狀況下, 使用 role 是較好的:
role Bull-Like { has Bool $.castrated = False; method steer { # 閹割你的牛 $!castrated = True; return self; } } role Steerable { has Real $.direction; method steer (Real $d = 0) { $!direction += $d; } } class Taurus does Bull-Like does Steerable { }
編譯器可能會產生如下的錯誤:
===SORRY!=== Method 'steer' must be resolved by class Taurus because it exists in multiple roles (Steerable, Bull-Like)
這個檢測會解決許多你和你的客戶頭疼的事情,你只須要簡單的將你的類定義改爲:
class Taurus does Bull-Like does Steerable { method steer ($direction?) { self.Steerable::steer($direction?); } }
當一個 role 被應用到第二個 role ,實際的運用會推遲直到第二個類應用到類上面,此時 role 會應用到了類上面,所以:
role R1 { # ... } role R2 does R1 { # ... } class C does R2 { }
這其實至關於
role R1 { # ... } role R2 { # ... } class C does R1 does R2 { }
當一個 role 包含一個方法存根的時候, role 應用的類必須提供一個名字相同的非存根版本的方法,這容許你建立行爲像抽象接口的 role 。
role AbstractSerializable { method serialize() { ... } # 字面量 ... 標記這個方法爲存根 } # 下面的代碼產生一個編譯錯誤,好比 # Method 'serialize' must be implemented by Point because # it is required by a role class APoint does AbstractSerializable { has $.x; has $.y; } # 下面的代碼是正常的 class SPoint does AbstractSerializable { has $.x; has $.y; method serialize() { "p($.x, $.y)" } }
存根方法的實現可能由另外一個 role 提供。
任何對 role 實例化(或者其餘相似的用法)的嘗試都會自動的建立一個和 role 有着相同名字的類,使其能夠透明的像類同樣使用一個 role 。
role Point { has $.x; has $.y; method abs { sqrt($.x * $.x + $.y * $.y) } } say Point.new(x => 6, y => 8).abs;
咱們管這個自動建立類過程叫作punning, 生成的類叫作pun。
Roles 可使用參數,在方括號裏面給出簽名:
role BinaryTree[::Type] { has BinaryTree[Type] $.left; has BinaryTree[Type] $.right; has Type $.node; method visit-preorder(&cb) { cb $.node; for $.left, $.right -> $branch { $branch.visit-preorder(&cb) if defined $branch; } } method visit-postorder(&cb) { for $.left, $.right -> $branch { $branch.visit-postorder(&cb) if defined $branch; } cb $.node; } method new-from-list(::?CLASS:U: *@el) { my $middle-index = @el.elems div 2; my @left = @el[0 .. $middle-index - 1]; my $middle = @el[$middle-index]; my @right = @el[$middle-index + 1 .. * - 1]; self.new( node => $middle, left => @left ?? self.new-from-list(@left) !! self, right => @right ?? self.new-from-list(@right) !! self, ); } } my $t = BinaryTree[Int].new-from-list(4, 5, 6); $t.visit-preorder(&say); # 輸出 5 \n 4 \n 6 $t.visit-postorder(&say); # 輸出 4 \n 5 \n 6
上面的簽名只是由類型捕獲組成的,其實能夠是任何參數:
use v6; enum Severity < debug info warn error critical >; role Logging[$filehandle = $*ERR] { method log(Severity $sev, $message) { $filehandle.print("[{uc $sev}] $message\n"); } } Logging[$*OUT].log(debug, "here we go!"); # 輸出 [DEBUG] here we go!
你能夠定義多個相同名字的 roles ,他們擁有不一樣的簽名,這也是重載的分派時選擇重載對象的正常規則。
Roles 能夠混入到對象中, roles 將自身的屬性和方法添加爲對象的屬性和方法,多混入和匿名 roles 也是支持的。
role R { method Str() { 'hidden'} }; my $i = 2 but R; sub f(\bound) { put bound }; f($i); # hidden!
注意,是對象被 role 混入,而不是對象的類或者容器,(???)所以 @-sigil 容器須要綁定到 role ,有些操做會返回一個新的值,致使混入被剝奪。
混入能夠發生在對象生命期的任什麼時候候:
# 一個目錄計數器 role TOC-Counter { has Int @!counters is default(0); method Str() { @!counters.join('.') } method inc($level) { @!counters[$level - 1]++; @!counters.splice($level); self } } my Num $toc-counter = NaN; # 不要作數學運算 say $toc-counter; # 輸出 NaN $toc-counter does TOC-Counter; # 如今咱們將 role 混入 $toc-counter.inc(1).inc(2).inc(2).inc(1).inc(2).inc(2).inc(3).inc(3); put $toc-counter / 1; # 輸出 NaN (由於這裏是數值上下文) put $toc-counter; # 輸出 2.2.2 (put 將會調用TOC-Counter::Str)
Roles 能夠是匿名的:
my %seen of Int is default(0 but role :: { method Str() { 'NULL' } }); say %seen<not-there>; # 輸出 NULL say %seen<not-there>.defined; # 輸出 True (0有多是False可是卻定義了) say Int.new(%seen<not-there>); # 輸出 0
Perl 6 擁有一個元對象系統,它的意思是對象、類、 role 、grammars、枚舉等的行爲均是經過其它對象控制的,這些對象稱爲元對象。元對象,就像普通的對象,也是類的實例,不過在這種狀況下類叫作元類。
對於每個對象或者類,你均可以經過調用.HOW
方法獲取它們的元對象,注意雖然這看起來像是一個方法調用,但其實對編譯器來講是特殊狀況,因此它其實更像 macro。
因此,你該怎麼樣使用元對象呢?一個用法就是你能夠經過比較元對象是否相等檢測它們是否有用相同的元類。
say 1.HOW === 2.HOW; # 輸出 True say 1.HOW === Int.HOW; # 輸出 True say 1.HOW === Num.HOW; # 輸出 False
Perl 6 使用這個單詞HOW, Higher Order Workings,指代元對象系統。所以,沒必要驚訝於Rakudo裏面,控制類行爲的元類的類名被叫作Perl6::Metamodel::ClassHow
,對於每個類都有一個Perl6::Metamodel::ClassHOW
的實例。
固然元模型能夠爲你作更多,好比它支持對象以及類的內省,元對象的調用約定是,調用元對象的方法,將在乎的對象做爲第一個參數傳入。因此要獲取實例的類的名字,你能夠這麼寫:
my $object = 1; my $metaobject = 1.HOW; say $metaobject.name($object); # 輸出 Int # 簡短的方式 say 1.HOW.name(1); # 輸出 Int
(Perl 6 這麼作的動機是想擁有一個基於原型的對象系統(prototype-based object system),這樣沒有必要爲每個類建立一個新的元對象)。
可使用下面的方式擺脫使用兩次相同對象的麻煩,如今代碼更簡短了:
say 1.^name; # 輸出 Int # 至關與 say 1.HOW.name(1); # 輸出 Int