[Raku] 如何定義函式

作者:   發佈於: ,更新於:   #rakulang

定義函式的關鍵字是 sub,基本上的語法結構是要帶個函式名、特徵宣告、及本文。以函式名 example 為例:

sub example ( 特徵宣告 ) {
   本文
}

特徵宣告就是參數串列及傳回值,以 --> 分隔,寫在小括號裡面。

參數串列 --> 傳回值

以這種語法定義出的函式,在使用時的語法如:

變數 = example( 參數串列 );

以下以幾個不同的範例來說明各項細節。

例:華氏溫度換攝氏溫度

以華氏溫度轉攝氏溫度為例,其公式為:

C = (F - 32) / 1.8

其中 F 代表華氏溫度度數,而 C 代表攝式溫度度數。

這運算需要一個參數 $f,代表華氏溫度度數,算完後會有一個傳回值,也就是攝式溫度度數。兩種數值的型別都為有理數 Rat。因此特徵宣告的部份是 Rat $f --> Rat。全文如下:

sub celsius-from-farenheit (Rat $f --> Rat) {
    return ($f - 32) / 1.8;
}

攝氏溫度轉華氏溫度也是類似:

sub farenheit-from-celsius (Rat $c --> Rat) {
    return 1.8 * $c + 32;
}

例:BMI 值計算

BMI (身體質量指數) 的計算公式為:

BMI = 體重 / 身高²

其體重的單位需為公斤,而身高的單位需為公尺。

也就是說在特徵宣告的部份是有兩個參數與一個傳回值,像是這樣:

(Rat $weight, Rat $height --> Rat)

若先假設 $weight 代表以公斤為單位的體重數值,$height 代表以公尺為單位的身高數值,也就是不必進行任何單位換算,這函式可定義如下:

sub bmi (Rat $weight, Rat $height --> Rat) {
    return $weight / $height ** 2;
}

例:空氣品質指標數字轉口語敘述

環保暑空氣品質監測網上的表格,空氣品質指標是個整數數值,範圍在 0 到 500 之間。並且由小到大,分為能用以下這幾個不同的口語敘述來形容:良好、普通、對敏感族群不健康、對所有族群不健康、非常不健康、危害。

試以此表格為規格來設計一個函式,將空氣品質指標轉為口語敘述吧。

粗略來看,這函式的特徵宣告應為 (Int $x --> Str),也就是其參數為一個代表空氣品質指標的變數 $x,而傳回值為一字串。

sub airquality-in-zh-Hant-TW (Int $x --> Str) {
   本文
}

但看來表格只定義在整數數值的 0..500 這範圍內,參數若是落在這範圍之外,就不應處理。那麼,可在函式的特徵宣告部份加上限制。寫法如下:

(Int $x where { 0 <= $x <= 500 } --> Str)

本文部份,可依表格上所定義的各段邊界,以 if...elsif... 來定義:

sub airquality-in-zh-Hant-TW (Int $x where { 0 <= $x <= 500 } --> Str) {
    return do {
        if 0 <= $x <= 50       { "良好" }
        elsif 51 <= $x <= 100  { "普通" }
        elsif 101 <= $x <= 150 { "對敏感族群不健康" }
        elsif 151 <= $x <= 200 { "對所有族群不健康" }
        elsif 201 <= $x <= 300 { "非常不健康" }
        elsif 301 <= $x <= 500 { "危害" }
    };
}

或是以 given ... when... 來定義。

sub airquality-in-zh-Hant-TW (Int $x where { 0 <= $x <= 500 } --> Str) {
    return do {
        given $x {
            when 0..50    { "良好" }
            when 51..100  { "普通" }
            when 101..150 { "對敏感族群不健康" }
            when 151..200 { "對所有族群不健康" }
            when 201..300 { "非常不健康" }
            when 300..500 { "危害" }
        }
    };
}

又或者,可利用這規格表以 50 為範圍單位的這項特性,建出翻譯表來:

sub airquality-in-zh-Hant-TW (Int $x where { 0 <= $x <= 500 } --> Str) {
     my constant @translations = [
        "良好",
        "普通",
        "對敏感族群不健康",
        "對所有族群不健康",
        "非常不健康",
        "非常不健康",
        "危害",
        "危害",
        "危害",
        "危害",
    ];

    return @translations[ $x div 50 ];
}

這個以查表方式來實做的版本由於運算次數較少,所花費的執行時間也應較少,但由於不易與原規格表直接兩相對照,所以在可讀性方面算是有點扣分。優劣各有,不失為一種選擇。

例:同時取得商數及餘數的整數除法

取整數除法的商數的算符是 div,而除餘數的算符是 %,若能有一函式能讓人同時取得商數及餘數,或許在某些情境之下會比較方便。

特徵宣告的部份,可定為:

(Int $dividend, Int $divisor --> Array[Int])

$divident 為被除數,$divisor 為除數。傳回值為兩個整數,分別代表商數及餘數。在此先簡單以 Array[Int] 做為傳回值的型別。

函式全文如下:

sub divmod (Int $dividend, Int $divisor --> Array[Int]) {
    my $quotient = $dividend div $divisor;
    my $remainder = $dividend % $divisor;

    return Array[Int].new( $quotient, $remainder );
}

使用起來如下:

my ($q, $r) = divmod(14, 3);

say $q; #=> 4
say $r; #=> 2

雖然傳回值型別是個陣列,但若有賦值算式右方為陣列 (Array) 而左方為串列 (List),那麼 raku 會將陣列內容逐一對應到串列裡去。因此 $q 對應到陣列第零個元素,而 $r 對應到陣列第一個元素。

附帶一提如果將此函式名為 infix:<divmod>,那就相當於是在將 divmod 這個關鍵字定義成一個新的中綴算符,在使用時要放在兩算子中間:

sub infix:<divmod> (Int $dividend, Int $divisor --> Array[Int]) {
    my $quotient = $dividend div $divisor;
    my $remainder = $dividend % $divisor;
    return Array[Int].new( $quotient, $remainder );
}

my ($q, $r) = 14 divmod 3;

say $q; #=> 4
say $r; #=> 2