Perl6 中的異常處理

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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章