The Awesome Errors of Perl 6

The Awesome Errors of Perl 6

如果你一直在讀技術(shù)相關(guān)的東西,你現(xiàn)在可能知道 Rust 里面的令人驚喜的錯誤報告能力净赴。 既然 Perl 6 也因它的絕妙的錯誤處理而聞名, mst 查詢了一些例子來炫耀 rust 的錯誤處理能力, 但是不幸的是我沒有發(fā)現(xiàn)玖翅。

我盡力避免錯誤并且我很少完整地讀完它烧栋。所以我會搜尋一些很酷的關(guān)于令人驚嘆的錯誤方面的例子并寫出來审姓。雖然我能夠用頭撞擊鍵盤并把輸出粘貼出來, 但是那將會慘不忍讀, 所以我會談?wù)撘恍Τ鯇W(xué)者來說沒那么明顯的棘手的錯誤, 還有怎樣修復(fù)那些錯誤魔吐。

讓我們開始用頭部猛擊吧!

基礎(chǔ)

下面是一段有錯誤的代碼;

say "Hello world!;
say "Local time is {DateTime.now}";

# ===SORRY!=== Error while compiling /home/zoffix/test.p6
# Two terms in a row (runaway multi-line "" quote starting at line 1 maybe?)
# at /home/zoffix/test.p6:2
# ------> say "Local time is {DateTime.now}";
#     expecting any of:
#         infix
#         infix stopper
#         postfix
#         statement end
#         statement modifier
#         statement modifier loop

第一行丟失了字符串上的閉合引號, 所以直到第二行的開括號之間的所有東西都會被認為是字符串的一部分嗜桌。一旦推測的閉合引號被找到, Perl 6 就看到單詞 "Local", 這個單詞被定義為一個項(item)骨宠。因為在 Perl 6 中一行中同時存在兩個項(item)是不允許的, 所以編譯器拋出了錯誤, 并對它所期望的提供了一些建議, 并且它探測到了我們正處在一個字符串中, 并且建議我們檢測, 我們忘記在行 1 中閉合引號了层亿。

===SORRY!=== 部分并不是意味著你運行的是加拿大版本的編譯器, 而是意味著該錯誤是一個編譯時錯誤(和運行時相比)立美。

Nom-nom-nom-nom

下面有一個有趣的錯誤建蹄。我們有一個返回東西的子例程, 所以我們調(diào)用了它并使用了 for 循環(huán)來迭代值:

sub things {1 ... ∞}

for things {
    say "Current stuff is $_";
}

# ===SORRY!===
# Function 'things' needs parens to avoid gobbling block
# at /home/zoffix/test.p6:5
# ------> }<EOL>
# Missing block (apparently claimed by 'things')
# at /home/zoffix/test.p6:5
# ------> }<EOL>

Perl 6 允許你在調(diào)用子例程的時候省略圓括號洞慎。上面的錯誤提到了全局塊兒(globbing blocks)。實際發(fā)生的是我們希望傳給 for 循環(huán)的塊兒被作為參數(shù)傳遞給了子例程桦他。輸出中的第二個錯誤證實 for 循環(huán)丟失了它的塊兒(并且給出了一個建議, 它被我們的 things 子例程接收了)快压。

第一個錯誤告訴我們怎樣修復(fù)那個問題: Function 'things' needs parens, 所以我們的循環(huán)需要是:

for things() {
    say "Current stuff is $_";
}

然而, 如果我們的子例程真的期望傳遞一個塊兒, 那么圓括號就不是必須的蔫劣。兩個代碼塊肩并肩地在一塊兒會導(dǎo)致 "two terms in a row" 錯誤, 所以 Perl 6 知道把第一個 block 傳遞給子例程并使用第二個 block 作為 for 循環(huán)的主體:

sub things (&code) { code }

for things { 1 ... ∞ } {
    say "Current stuff is $_";
}

Did You Mean Levestein?

下面有一個很酷的特性, 它不僅告訴你出錯了, 還能指出你可能想要的:

sub levenshtein {}
levestein;

# ===SORRY!=== Error while compiling /home/zoffix/test.p6
# Undeclared routine:
#     levestein used at line 2. Did you mean 'levenshtein'?

當(dāng) Perl 6 遇到它不認識的名字時它會為它知道的東西計算Levenshtein distance以嘗試提供一個有用的建議脉幢。在上面的距離中它遇到了一個它不知道的子例程調(diào)用嫌松。它注意到我們確實有一個相似的子例程, 所以它把它作為備選提供了出來。不要再盯著屏幕了, 嘗試找到你在哪里敲擊的鍵盤液走!

然而, 這個特性不可能在觸發(fā)時面面俱到缘眶。假設(shè)我們把子例程的名字變?yōu)榇髮懙?Levenshtein, 我們就不會得到那個建議, 因為對于以大寫字母開頭的東西, 編譯器認為它看起來像一個類型名而非子例程名, 所以它檢測這些東西來代替:

class Levenshtein {}
Lvnshtein.new;

# ===SORRY!=== Error while compiling /home/zoffix/test.p6
# Undeclared name:
#    Lvnshtein used at line 2. Did you mean 'Levenshtein'?

一旦你成了 Seq, 你再也變不回來

我們假設(shè)你生成了一個短的斐波納契數(shù)字序列巷懈。你打印了它然后你想再打印它一次, 但是這一次打印每個成員的平方顶燕。發(fā)生了什么?

my $seq = (1, 1, * + * ... * > 100);
$seq.join(', ').say;
$seq.map({ $_2 }).join(', ').say;

# 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144
# This Seq has already been iterated, and its values consumed
# (you might solve this by adding .cache on usages of the Seq, or
# by assigning the Seq into an array)
#   in block <unit> at test.p6 line 3

嗷, 運行時錯誤涌攻。我們從序列操作符得到的 Seq 類型不能保留東西。當(dāng)你迭代序列的時候, 每次它給你一個值之后就丟棄這個值, 所以一旦你迭代完整個 Seq 序列, 就結(jié)束了维咸。

上面的例子中, 我們嘗試再次迭代那個序列, 所以 Rakudo 運行時奔潰并抱怨了, 因為它做不了虽画。錯誤消息的確提供了兩種可能的解決方案。我們要么使用 .cache 方法來獲得一個我們將要迭代的 List:

my $seq = (1, 1, * + * ... * > 100).cache;
$seq             .join(', ').say;
$seq.map({ $_2 }).join(', ').say;

# 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144
# 1, 1, 4, 9, 25, 64, 169, 441, 1156, 3025, 7921, 20736

或者我們可以從一開始就使用數(shù)組 Array:

my @seq = 1, 1, * + * … * > 100;
@seq             .join(', ').say;
@seq.map({ $_2 }).join(', ').say;

# 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144
# 1, 1, 4, 9, 25, 64, 169, 441, 1156, 3025, 7921, 20736

并且即使我們把序列 Seq 存儲進了 Array 中, 它不會被具體化直到真正被需要時:

my @a = 1 … ∞;
say @a[^10];

# OUTPUT:
# (1 2 3 4 5 6 7 8 9 10)

These Aren't The Attributes You're Looking For

假設(shè)你有一個類。在類里面, 你有一些私有屬性并且你有一個使用屬性的值作為它的一部分的正則匹配方法:

class {
    has $!prefix = 'foo';
    method has-prefix ($text) {
        so $text ~~ /^ $!prefix/;
    }
}.new.has-prefix('foobar').say;

# ===SORRY!=== Error while compiling /home/zoffix/test.p6
# Attribute $!prefix not available inside of a regex, since regexes are methods on Cursor.
# Consider storing the attribute in a lexical, and using that in the regex.
# at /home/zoffix/test.p6:4
# ------>         so $text ~~ /^ $!prefix?/;
#     expecting any of:
#         infix stopper

糟糕! 發(fā)生什么了?

就像編譯器所指出的那樣, Perl 6 實際上是由幾種語言編織而成: Perl 6, Quote, 和 Regex 語言是這個編織物的一部分。這就是為什么像下面這樣的東西就能起效:

say "foo { "bar" ~ "meow" } ber ";

# OUTPUT:
# foo barmeow ber

盡管內(nèi)插的代碼塊中使用了同樣的引號", 但是沒有發(fā)生沖突糟港。然而, 同樣的機制在正則表達式中有限制, 因為在正則表達式中, 所查詢的屬性屬于 Cursor 對象, 它負責(zé)這個正則表達式秸抚。

為了避免這個錯誤, 就像錯誤信息暗示的那樣, 僅僅使用一個臨時的變量來存儲 $!prefix 好了, 或者使用 given 塊兒:

class {
    has $!prefix = 'foo';
    method has-prefix ($text) {
        given $!prefix { so $text ~~ /^ $_/ }
    }
}.new.has-prefix('foobar').say;

De-Ranged

嘗試過訪問列表中超出范圍的元素嗎?

my @a = <foo bar ber>;
say @a[*-42];

# Effective index out of range. Is: -39, should be in 0..Inf
#  in block <unit> at test.p6 line 2

在 Perl 6 中, 如果從列表末端索引一個條目, 要使用時髦的語法: [*-42]。它實際上是一個接收一個參數(shù)(它是列表中元素的個數(shù))的閉包, 然后減去 42, 然后返回的值作為實際的索引颠放。如果你特別無聊, 你可以使用 @a[sub ($total) { $total - 42 }] 代替碰凶。

在上面的錯誤中, 那個索引以 3 - 42 結(jié)束, 或者說是 -39, 這是我們在錯誤信息中看到的那個值省有。因為索引不能是負的, 所以我們收到了錯誤, 這也告訴我們索引必須從 0 到 正無窮大(任何超過列表所包含的索引會在被查詢時返回 Any)伸头。

A Rose By Any Other Name, Would Code As Sweet

如果你是 Perl 6 的姐妹語言, Perl 5 的活躍使用者, 你可能會發(fā)現(xiàn)有時候你在 Perl 6 代碼中寫出 Perl 5 風(fēng)格的代碼:

say "foo" . "bar";

# ===SORRY!=== Error while compiling /home/zoffix/test.p6
# Unsupported use of . to concatenate strings; in Perl 6 please use ~
# at /home/zoffix/test.p6:1
# ------> say "foo" .? "bar";

在上面, 我們嘗試使用 Perl 5 的字符串連接操作符來連接兩個字符串舷蟀。這個錯誤機制足夠聰明地檢測到這樣的用法并推薦了正確的 ~ 操作符來代替。

這不是這種探測的唯一使用場景扫步。有很多場景河胎。這兒有另外一個例子, 用于探測 Perl 5 的鉆石操作符的意外使用, 伴隨著幾個程序員可能想要的建議:

while <> {}

# ===SORRY!=== Error while compiling /home/zoffix/test.p6
# Unsupported use of <>; in Perl 6 please use lines() to read input, ('') to
# represent a null string or () to represent an empty list
# at /home/zoffix/test.p6:1
# ------> while <?> {}

Heredoc, Theredoc, Everywheredoc

為了拋出問題, 請先閱讀底部的錯誤, 假裝就是你自己寫的程序代碼:

my $stuff = qq:to/END/;
Blah blah blah
END;

for ^10 {
    say 'things';
}

for ^20 {
    say 'moar things';
}

sub foo ($wtf) {
    say 'oh my!';
}

# ===SORRY!=== Error while compiling /home/zoffix/test.p6
# Variable '$wtf' is not declared
# at /home/zoffix/test.p6:13
# ------> sub foo (?$wtf) {

哈? 編譯器哭著說有一個未聲明的變量, 但是它指向的卻是子例程中的簽名游岳。當(dāng)然它不會被聲明胚迫。

那些沒有發(fā)現(xiàn)問題的人: 問題就出在 heredoc 中的閉合 END 后面的分號上访锻。 heredoc 在閉合分隔符單獨出現(xiàn)在一行的地方結(jié)束期犬。在編譯器看來, 我們還沒有在 END; 這兒看到分隔符, 所以它繼續(xù)解析就像它仍舊在解析 heredoc 一樣哭懈。qq heredoc 能讓你插入變量, 所以當(dāng)解析器解析到簽名中的 $wtf 變量時, 解析器并不知道它是一段實際代碼中的簽名還是某些隨機的文本, 所以編譯器哭著說變量未找到遣总。

Won't Someone Think of The Reader?

下面有一個極好的錯誤能阻止你寫出恐怖的代碼:

my $a;
sub {
    $a.say;
    $^a.say;
}

# ===SORRY!=== Error while compiling /home/zoffix/test.p6
# $a has already been used as a non-placeholder in the surrounding sub block,
#   so you will confuse the reader if you suddenly declare $^a here
# at /home/zoffix/test.p6:4
# ------>         $^a.say;

這里有一點背景: 你可以在變量身上使用 $^ twigil 來創(chuàng)建一個隱式的簽名旭斥。為了能在嵌套的塊中使用這樣的變量, 這個語法實際上創(chuàng)建了不帶 twigil 的相同變量, 所以 $^a$a 是同一個東西, 并且上面的子例程的簽名是 ($a)垂券。

在我們的代碼中, 我們還在外部作用域中有個 $a 并且推測我們首先打印出它, 在使用 $^ twigil 在同樣一個作用域中創(chuàng)建另外一個 $a 之前, 但是這個子例程包含了參數(shù)... 真繞腦! 為了避免這樣, 就把你的變量重命名為某個不會沖突的東西好了菇爪。改成泰文怎么樣?

my $??????? = 'peace';
sub {
    $???????.say;
    $^???????????????.say;
}('to your variables');

# OUTPUT:
# peace
# to your variables

Well, Colour Me Errpressed!

如果你的終端支持它, 那么編譯器就會發(fā)出 ANSI 代碼來給輸出著點色:

for ^5 {
    say meow";
}
img

那很好也很顯眼奪目, 但是假設(shè)你想把從編譯器中捕獲的輸出顯示到任何地方, 你會原樣地得到 ANSI 代碼, 就像 31m===[0mSORRY![31m===[0m熙揍。

這很可怕, 但是幸運的是, 禁用顏色很簡單: 僅僅把 RAKUDO_ERROR_COLOR 這個環(huán)境變量的值設(shè)置為 0 就好了:

img

你也可以在程序中設(shè)置它届囚。你不得不足夠早地設(shè)置它, 所以在任何地方把它放置在程序的開頭并使用 BEGIN phaser 來設(shè)置它只要賦值被編譯完成:

BEGIN %*ENV<RAKUDO_ERROR_COLOR> = 0;

for ^5 {
    say meow";
}

An Exceptional Failure

Perl 6 有一個特殊的異常 -- Failure -- 直到你把它用作變量它才會被激發(fā), 并且你甚至可以通過在布爾上下文中使用它來徹底地消除它意系。你可以通過調(diào)用 fail 子例程產(chǎn)生你自己的 Failures 并且 Perl 6 在核心中在盡可能合適的時候使用它蛔添。

這兒有一段代碼, 其中我們定義了一個前綴操作符用來計算對象的圓周長, 給定一個半徑迎瞧。如果半徑是負值, 它就調(diào)用 fail, 并返回一個 Failure 對象:

sub prefix:<?> (\??) {
    ?? < 0 and fail 'Your object warps the Universe a new one';
    τ × ??;
}

say 'Calculating the circumference of the mystery object';
my $c? = ? ???;

say 'Calculating the circumference of the Earth';
my $c? = ? 6.3781 × 10?;

say 'Calculating the circumference of the Sun';
my $c? = ? 6.957 × 10?;

say "The circumference of the largest object is {max $c?, $c?, $c?} metres";

# OUTPUT:
# Calculating the circumference of the mystery object
# Calculating the circumference of the Earth
# Calculating the circumference of the Sun
# Your object warps the Universe a new one
#   in sub prefix:<?> at test.p6 line 2
#   in block <unit> at test.p6 line 7
#
# Actually thrown at:
#   in block <unit> at test.p6 line 15

在第七行中我們正計算一個半徑為負值的圓的周長, 所以如果它僅僅是一個常規(guī)的異常, 那么我們的代碼會當(dāng)場掛掉夹攒。相反, 通過輸出, 我們能夠看到我們繼續(xù)計算了 Earth 和 Sun 的周長, 直到我們到達最后一行胁塞。

在那兒我們嘗試在 $c? 變量中使用 Failure 作為 max 程序的一個參數(shù)啸罢。因為我們在查詢真實的值, Failure 被激發(fā)并給了我們一個很好的反向追蹤扰才。上面的錯誤信息包含了我們的 Failure 爆發(fā)(第十五行)點, 還有我們的接收點(第七行)還有錯誤來自哪兒(第二行)蕾总。真甜!

結(jié)論

Perl 6 擁有令人驚嘆的 Errors!

大西瓜啊琅捏。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蚀浆,一起剝皮案震驚了整個濱河市市俊,隨后出現(xiàn)的幾起案子滤奈,更是在濱河造成了極大的恐慌摆昧,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件僵刮,死亡現(xiàn)場離奇詭異据忘,居然都是意外死亡,警方通過查閱死者的電腦和手機搞糕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門勇吊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人窍仰,你說我怎么就攤上這事汉规。” “怎么了驹吮?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長常空,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上病曾,老公的妹妹穿的比我還像新娘逼蒙。我一直安慰自己陕截,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布暮的。 她就那樣靜靜地躺著,像睡著了一般缘屹。 火紅的嫁衣襯著肌膚如雪逻炊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天凤巨,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播激捏,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼任连,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤漠魏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后瞧毙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體释漆,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡栈戳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年驶冒,在試婚紗的時候發(fā)現(xiàn)自己被綠了蜡歹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屋厘。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡玫坛,死狀恐怖湿镀,靈堂內(nèi)的尸體忽然破棺而出勉痴,到底是詐尸還是另有隱情雏掠,我是刑警寧澤绑青,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布芥牌,位于F島的核電站柏靶,受9級特大地震影響弃理,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜屎蜓,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一痘昌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧炬转,春花似錦辆苔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至荐吵,卻和暖如春骑冗,著一層夾襖步出監(jiān)牢的瞬間赊瞬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工贼涩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留巧涧,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓遥倦,卻偏偏與公主長得像谤绳,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子谊迄,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 標(biāo)題: Rakudo and NQP Internals子標(biāo)題: The guts tormented imple...
    焉知非魚閱讀 1,372評論 1 3
  • 2016-10-20 號更新闷供。 源文件可以在 github 或 perl6.org上找到. General Rak...
    焉知非魚閱讀 977評論 0 0
  • Perl 6 from Ruby - Nutshell 基本語法 語句結(jié)束分號 Ruby 使用換行(有幾個例外)來...
    焉知非魚閱讀 758評論 0 1
  • 2009 有用的和有意思的循環(huán) 讓我們來看一個基本的例子. 這是一個最簡單清晰的語法的例子.在這并沒有使用括號來包...
    焉知非魚閱讀 549評論 0 0
  • 第一章 概要 Comming soon! 第二章 基礎(chǔ) 假設(shè)有一場乒乓球比賽烟央,比賽結(jié)果以這種格式記錄:Player...
    焉知非魚閱讀 339評論 0 0