Perl 6 程式設計教學:多型 (Polymorphism)

PUBLISHED ON JAN 1, 2018 — PROGRAMMING
FacebookTwitter LinkedIn LINE Skype EverNote GMail Email Email

    Duck type

    Duck type 是動態型別語言的一種特性,duck type 物件不需在意其實際的類別,僅需在意該類別是否有提供相對應的公開方法。如以下實例:

    class Duck {
        method speak {
            "Pack pack".say;
        }
    }
    
    class Dog {
        method speak {
            "Wow wow".say;
        }
    }
    
    class Tiger {
        method speak {
            "Halum halum".say;
        }
    }
    
    my @animals = (
            Duck.new,
            Dog.new,
            Tiger.new,
        );
        
    for @animals -> $a {
        $a.speak;
    }
    

    在這個例子中,只要 @animals 陣列中的物件有實作 speak 方法即可,我們不需要去檢查各個物件實際的類別。

    對於從靜態型別語言轉換過來的程式設計者,時常有檢查物件所屬的類別的衝動;然而,過度地檢查型別,便失去使用動態型別語言的優點。如果想確保型別安全,建議使用 role 來約束類別的行為,如同我們前文所舉的例子。

    子類型

    子類型 (subtyping) 也是一種實作多型的方法,可以透過繼承或 roles 來實作。由於 Perl 6 是動態型別語言,本身已經支援 duck typing,不太需要使用子類型實作多型。如果想實作子類型,建議使用 role。

    函式重載

    使用 multi 可以宣告同名但不同參數的函式或方法。我們在先前的範例中已經展示過其用法。

    運算子重載

    透過運算子重載,衍生類別也可以像內建類別般,使用運算子來操作類別;運算子重載常用在數學相關的類別,像是向量 (vector) 或矩陣 (matrix) 等。以下實例實作向量加法:

    role IVector {
        method elems { ... }
        method at($i) { ... }
    }
    
    class Vector does IVector {
        has Numeric @!vec;
        
        submethod BUILD(:array(@a)) {
            @!vec = @a;
        }
        
        method elems {
            @!vec.elems;
        }
        
        method at($i) {
            return @!vec[$i];
        }
    }
    
    # Overloading indexing method.
    multi sub postcircumfix:<[ ]>(IVector $v, $i) {
        $v.at($i);
    }
    
    # Overloading addition method.
    multi sub infix:<+>(IVector $p, IVector $q) {
        if $p.elems != $q.elems {
            die "Unequal vector length";
        }
            
        my @out;
            
        loop (my $i = 0; $i < $p.elems; $i++) {
            @out.push($p[$i] + $q[$i])    
        }
            
        Vector.new(array => @out);
    }
    
    my $p = Vector.new(array => (1, 2, 3));
    my $q = Vector.new(array => (2, 3, 4));
    my $v = $p + $q;
    $v[0] == 3 or die "Wrong value";
    $v[1] == 5 or die "Wrong value";
    $v[2] == 7 or die "Wrong value";
    

    某種程度來說,運算子重載偏向使用者自訂的語法糖,而非必備的語法特性。有許多現代語言支援運算子重載,但也有語言不支援,像是 Java 和 Go。Perl 6 的運算子重載相當靈活,甚至可以自訂新的運算子;但筆者對於自訂運算子的態度較為保守,過度地使用運算子重載,反而會造成代碼難以閱讀。

    泛型

    由於 Perl 6 是動態型別語言,不太需要使用泛型程式,即可將相同程式碼套用在不同型別上。Perl 6 的 paramaterized role 提供有限度的泛型程式支援,但不若其他語言的泛型系統來得完整,官方對此也著墨甚少,故此處不深入介紹。