Perl6 中的異常處理
perl6 的異常處理機制,個人認爲大體上分爲兩個部分,一個是 exception
, 另一個是 failure
,有時候又叫 soft failure
,意思爲遇到錯誤的時候不及時的拋出,在使用它的時候纔會被拋出來。
Failure
failure
是 Perl6 作爲弱類型的標誌,這種錯誤被 Perl6 發現, 但是並沒有立即拋出
Golang 是一種典型的強類型和靜態類型的語言,看一小段 Golang 的代碼
func main() {
result := 1 / 0
fmt.Println("hello")
fmt.Println(result)
}
這段代碼在編譯時期就會被拋出異常,因爲 Golang 是強類型語言,它不容忍代碼裏有錯誤沒有被 catch 也沒有被做其他的處理而被忽略的,換句話說,強類型的語言不容許 soft failure
而 Perl6 卻不太一樣
my $result = 1 / 0;
say "hello";
say $result;
output:
hello
Attempt to divide by zero when coercing Rational to Str
in block <unit> at main.p6 line 4
可以看到這裏的 hello
被打印出來了
爲了做對比,看一下同爲腳本語言的 Python
foo = 1 / 0
print(foo)
output
Traceback (most recent call last):
File "strong.py", line 1, in <module>
foo = 1 / 0
ZeroDivisionError: integer division or modulo by zero
因爲 Python
是強類型的,它也不容許異常被忽略
說白了,Failure
只是 soft failure
的一個代表類型,它會允許錯誤遺留在代碼中(只要你不去顯式的使用它)
下面的代碼不會發生任何異常
my $result = 1 / 0;
say "hello";
output
hello
Exception
異常是任何時候可以中斷程序的一個東西,當然這個有例外, 如果你捕獲了它,不然它就會一直往外層拋出,最終造成程序中斷。Perl6
中的異常對象大多存在於 X
模塊中
我們可以簡單的創建一個自己的異常,並且拋出
sub foo() {
X::AdHoc.new(payload => "This is my exception").throw
}
foo();
output
This is my exception
in sub foo at main.p6 line 1
in block <unit> at main.p6 line 5
通過創建一個 AdHoc
對象然後調用它的 throw 方法就能將它拋出了
當然你可以通過調用 die subroutine 來實現相同的效果,與上面的代碼等價
sub foo() {
die "This is my exception"
}
foo();
除此之外,還有很多的 Exception
類型,這個留到後面再說
錯誤的捕獲
在 Perl6 中,幾乎跟其他語言的慣有方法一致,都是通過 try catch
代碼塊來將其捕獲,在 catch
中進行處理,接着上面的例子
my $result;
try {
$result = 1 / 0;
say $result;
CATCH {
say $_.^name;
}
}
異常的對象會保存在 CATCH 塊中的 $_
變量中,這是 Perl 一貫的做法, 這裏不再贅述了。
這裏會有一個坑,這段代碼的執行結果是
X::Numeric::DivideByZero
Attempt to divide by zero when coercing Rational to Str
in block <unit> at main.p6 line 4
很顯然打印出了異常的類型,但是異常依舊被拋出了!
這是因爲 CATCH 是類似於一個 given 的結構,可以用 when
來分支各種類型的異常,進行處理,單純的直接在裏面寫處理邏輯是不會被捕獲的, 它還是會向外面拋,這樣的設計,得益我們可以輕鬆實現記錄異常信息,但是不用 rethrow
。
下面讓我們嘗試捕獲它
my $result;
try {
$result = 1 / 0;
say $result;
CATCH {
default {say $_.^name}
}
}
在 default 分支裏寫上處理邏輯,default 分支是 "萬能"的,你可以在這裏捕獲任意類型的異常,但是當我們知道該異常的類型時,我們可以針對性的捕獲
my $result;
try {
$result = 1 / 0;
say $result;
CATCH {
when X::Numeric::DivideByZero {say $_.^name}
}
}
現在我們已經可以成功的捕獲異常了!
但是嘗試把上面的 say $result
語句去掉呢?
my $result;
try {
$result = 1 / 0;
CATCH {
when X::Numeric::DivideByZero {say $_.^name}
}
}
這時運行發現,不會打印異常的名字了!
上面說了,這是一個 failure
,是 Exception
的一層封裝,它並不會立即拋出,而是等到使用它的時候它纔會被拋出,因此,這段代碼是不會拋出異常的,當然也不會被捕獲!
上面 try 的代碼只有一行,這樣寫是不是有點太重了?所以我們可以使用單行 try
my $result = 1 / 0;
try say $result;
say $!.^name; #X::Numeric::DivideByZero
在外部,錯誤對象被存進了 $!
,我們可以在外面進行異常的處理,值得注意的是,單行 try 並不會重新拋出。
Warning
除了異常,我們還可以自己定義 Warning
,Warning 也是一種類型的異常,不同的是,它並不會導致程序直接中斷。
sub generateWarn(){
warn "this is warn";
}
generateWarn();
(0 .. 10)>>.say;
output
this is warn
in sub generateWarn at main.p6 line 1
0
1
2
3
4
5
6
7
8
9
10
發現儘管 warn 被拋出,但是程序還是能正常的運行下去
因爲 warn 是一種特殊的異常,所以我們不能再用 CATCH
去捕獲了,現在,我們要用 CONTROL
來進行捕獲
sub generateWarn(){
warn "this is warn";
}
try {
generateWarn();
CONTROL {
default {say $_.^name}
}
}
(0 .. 10)>>.say;
output
CX::Warn
0
1
2
3
4
5
6
7
8
9
10
警告⚠️不會給你的程序帶來致命的危害,它有的時候就想嘰嘰喳喳的小鳥,你可以讓它們安靜下來,用 quietly
!
sub generateWarn(){
warn "this is warn";
}
quietly {
generateWarn();
}
(0 .. 10)>>.say;
任何被 quietly
包住的代碼塊,都會屏蔽掉 warn,不讓它跑到標準輸出流中
output
0
1
2
3
4
5
6
7
8
9
10