Perl 6 支持"泛型, roles 和 多重分發(fā)", 它們都是很好的特點(diǎn), 并且已經(jīng)在其它 advent calendar 中發(fā)布過(guò)了。
但是今天我們要看的是 MOP。 "MOP"代表著元對(duì)象協(xié)議("Meta-Object Protocol")。那意味著, 它們實(shí)際上是你能從用戶(hù)那邊改變的一部分, 而不是對(duì)象、類(lèi)等定義語(yǔ)言的東西。
實(shí)際上, 在 Perl 6中, 你可以為類(lèi)型添加方法, 移除某個(gè)方法, 包裹方法, 使用更多能力增強(qiáng)類(lèi)(OO::Actors 和 OO::Monitors 就是兩個(gè)這樣的例子), 或者你可以完全重定義它(并且, 例如, 使用 Ruby-like 的對(duì)象系統(tǒng)。這兒有個(gè)例子)。
但是今天, 我們首先看一下第一部分: 自省。在類(lèi)型創(chuàng)建完之后查看它的類(lèi)型, 了解它, 并使用這些信息。
我們將要?jiǎng)?chuàng)建的模塊是基于 Sixcheck 模塊(一個(gè) QuickCheck-like 模塊)的需求: 為某個(gè)類(lèi)型生成一些隨機(jī)數(shù)據(jù), 然后把數(shù)據(jù)喂給我們正測(cè)試的函數(shù), 并檢查某些后置條件(post-condition)。
所以, 我們先寫(xiě)出第一個(gè)版本:
my %special-cases{Mu} =
(Int) => -> { (1..50).pick },
(Str) => -> { ('a'..'z').pick(50).join('') },
;
sub generate-data(Mu:U \t) {
%special-cases{t} ?? %special-cases{t}() !! t.new;
}
generate-data(Int);
注意以下幾點(diǎn):
- 我們給 %special-cases 指定了鍵的類(lèi)型。那是因?yàn)槟J(rèn)地, 鍵的類(lèi)型為 Str。顯然地, 我們不想讓我們的類(lèi)型字符串化。我們實(shí)際上做的是指定它們?yōu)?Mu"的子類(lèi)(這在類(lèi)型"食物鏈"的頂端)。
- 我們?cè)?Int 和 Str 周?chē)派蠄A括號(hào), 以避免字符串化。
- 我們?cè)诤瘮?shù)參數(shù)類(lèi)型中使用了
:U
。那意味著那個(gè)值必須是未定義的(undefined)。類(lèi)型對(duì)象(就像 Int、Str 等等)是未定義的, 所以它能滿(mǎn)足我們(你可能見(jiàn)過(guò)一個(gè)叫 Nil 的不同的未知值)。 - 類(lèi)型對(duì)象實(shí)際上是對(duì)象, 就像其它任何對(duì)象一樣。這就是為什么我們?cè)陬?lèi)型對(duì)象上調(diào)用
.new
方法, 例如, 它和直接調(diào)用Int.new
相同(那對(duì)一致性和 autovivification 很有用)。 - 我們?yōu)?Int 和 Str 提供了fallback, 因?yàn)檎{(diào)用 Int.new 和 Str.new ( 0 和 "" )不會(huì)在我們創(chuàng)建的數(shù)據(jù)中給我們?nèi)魏坞S機(jī)化。
- Perl 6 在函數(shù)中自動(dòng)返回最后一個(gè)表達(dá)式。所以不需要在那兒放上一個(gè) return。
我們用代碼生成數(shù)據(jù), 公平且公正。但是我們需要生成更多那樣簡(jiǎn)單的數(shù)據(jù)。
我們至少需要支持帶有屬性的類(lèi): 我們想查看屬性列表, 為它們的類(lèi)型生成數(shù)據(jù), 并把它們喂給構(gòu)造器。
我們要能夠看到類(lèi)的內(nèi)部。用 Perl 6 的術(shù)語(yǔ)來(lái)說(shuō), 我們將要到達(dá)的是元對(duì)象協(xié)議(Meta-Object Protocol)。首先我們定義一個(gè)類(lèi):
class Article {
has Str $.title;
has Str $.content;
has Int $.view-count;
}
# 我們可以這樣手動(dòng)創(chuàng)建一個(gè)實(shí)例
Article.new(title => "Perl 6 Advent, 第 19 天",
content => "Magic!",
view-count => 0
);
但是我們不想親手創(chuàng)建那個(gè)文章 (article)。我們想把那個(gè) class Article 傳遞給我們的 generate-data 函數(shù), 并返回一個(gè) Article(里面帶有隨機(jī)數(shù)據(jù))。讓我們回到我們的 REPL...
say Article.^attributes; # (Str $!title Str $!content Int $!view-count)
say Article.^attributes[0].WHAT; # (Attribute)
如果你點(diǎn)擊了 MOP 鏈接, 你不會(huì)對(duì)我們得到一個(gè)含有 3 個(gè)元素的數(shù)組感到驚訝。如果你仍舊對(duì)該語(yǔ)法感到驚訝, 那么 .^
是元方法調(diào)用。意思是 a.^b
會(huì)被轉(zhuǎn)換為 a.HOW.b(a)
。
如果我們想知道我們可以訪問(wèn)到什么, 我們問(wèn)它就是了(移除了匿名的那些):
Attribute.^methods.grep(*.name ne '<anon>');
# (compose apply_handles get_value set_value
# container readonly package inlined WHY set_why Str gist)
Attribute.^attributes # Method 'gist' not found for invocant of class 'BOOTSTRAPATTR'
哎吆… 看起來(lái)這有點(diǎn)太 meta 了。幸好, 我們能使用 Rakudo 的一個(gè)非常好的屬性: 它的大部分都是用 Perl 6寫(xiě)的! 要查看我們可以得到什么, 我們查看源代碼就好了:
# has Str $!name;
...
# has Mu $!type;
我們得到了鍵的名字, 還有去生成值的類(lèi)型。讓我們看看...
> say Article.^attributes.map(*.name)
($!title $!content $!view-count)
> say Article.^attributes.map(*.type)
((Str) (Str) (Int))
天才! 看起來(lái)是正確的。(如果你想知道為什么我們得到 $!
(私有的) twigils, 那是因?yàn)?$.
只意味著將會(huì)生成的一個(gè) getter 方法)。屬性本身仍然是私有的, 并且在類(lèi)中是可訪問(wèn)的。
現(xiàn)在, 我們唯一要做的事情就是創(chuàng)建一個(gè)循環(huán)...
my %args;
for Article.^attributes -> $attr {
%args{$attr.name.substr(2)} = generate-data($attr.type);
}
say %args.perl;
這是一個(gè)將會(huì)打印什么的例子:
{:content("muenglhaxrvykfdjzopqbtwisc"), :title("rfpjndgohmasuwkyzebixqtvcl"), :view-count(45)}
每次你運(yùn)行你的代碼你都會(huì)得到不同的結(jié)果(然而我不認(rèn)為它會(huì)創(chuàng)建一篇值得閱讀的文章…)。剩下唯一要做的就是把它們傳遞給 Article 的構(gòu)造函數(shù):
say Article.new(|%args);
(前綴 |
允許我們把 %args 作為具名參數(shù)傳遞, 而不是單個(gè)位置參數(shù))。再次, 你應(yīng)該會(huì)打印這些東西:
Article.new(title => "kyvphxqmejtuicrbsnfoldgzaw", content => "jqbtcyovxlngpwikdszfmeuahr", view-count => 26)
呀! 我們?cè)O(shè)法在不了解 Article 的情況下胡亂地(blindly)創(chuàng)建了一個(gè) Article 實(shí)例。 我們的代碼能夠用于為任何期望傳遞它的類(lèi)屬性的構(gòu)造函數(shù)生成數(shù)據(jù)。好了!
PS: 留個(gè)作業(yè)! 移動(dòng)到 generate-data 函數(shù), 以至于我們能給 Article 添加一個(gè) User $.author 屬性, 并且構(gòu)建好這個(gè)函數(shù)。祝你好運(yùn)!