Perl 6 中的 Roles Vs. Protocol

weixin_34019929發表於2016-01-12

Protocol 在 Swift 中是一組方法和屬性的集合, 可用於程式碼複用。 Perl 6 中有與之類似的結構, 叫做 Role, 下面轉換一個 Swift 的 Protocol 為 Perl 6 的 Role, 把部門人員的相關資訊列印為一個表格:

Protocol in Swift

import UIKit

func padding(amount: Int) -> String {
    var paddingString = ""
    for _ in 0..<amount {
        paddingString += " "
    }
    return paddingString
}

// 協議

protocol TabularDataSource {
    var numberOfRows: Int    { get }
    var numberOfColumns: Int { get }
    
    func labelForRow(row: Int) -> String        // 行標籤
    func labelForColumn(column: Int) -> String  // 列標籤
    
    func itemForRow(row: Int, column: Int) -> Int // 表格中的單元格
}



struct Person {
    let name: String
    let age: Int
    let yearsOfExperience: Int
}

// 讓 **Department** 遵守 **TabularDataSource**協議
struct Department: TabularDataSource {
    let name: String
    var people = [Person]()
    
    init(name: String) {
        self.name = name
    }
    
    mutating func addPerson(person: Person) {
        people.append(person)
    }
    
    // 實現協議中宣告的屬性和方法
    var numberOfRows: Int {
        return people.count
    }
    
    var numberOfColumns: Int {
        return 2
    }
    
    func labelForRow(row: Int) -> String {
        return people[row].name
    }
    
    func labelForColumn(column: Int) -> String {
        switch column {
            case 0: return "Age"
            case 1: return "Years of Experence"
            default: fatalError("Invalid column!")
        }
    }
    
    func itemForRow(row: Int, column: Int) -> Int {
         let person = people[row] // 指定的行
         switch column {
             case 0: return person.age
             case 1: return person.yearsOfExperience
             default:fatalError("Invalid column!")
        }
    }
}

var deparment = Department(name: "Engineering")
deparment.addPerson(Person(name: "Joe", age: 30, yearsOfExperience: 6))
deparment.addPerson(Person(name: "Karen", age: 40, yearsOfExperience: 18))
deparment.addPerson(Person(name: "Fred", age: 50, yearsOfExperience: 20))

// 傳入一個資料來源
func printTable(dataSource: TabularDataSource) {
    let rowLabels = (0 ..< dataSource.numberOfRows).map { dataSource.labelForRow($0) }
    let columnLabels = (0 ..< dataSource.numberOfColumns).map { dataSource.labelForColumn($0) }
    
    // 建立一個陣列儲存每個行標籤的寬度
    let rowLabelWidths = rowLabels.map { $0.characters.count }
    
    // 限定行標籤的最大長度
    guard let maxRowLabelWidth = rowLabelWidths.maxElement() else {
        return
    }
    
    // 建立第一行, 包含列標題
    var firstRow = padding(maxRowLabelWidth) + " |"
    
    // 跟蹤每列的寬度
    var columnWidths = [Int]()
    
    for columnLabel in columnLabels {
        let columnHeader = " \(columnLabel) |"
        firstRow += columnHeader
        columnWidths.append(columnHeader.characters.count)
    }
    print(firstRow)
    
    for i in 0 ..< dataSource.numberOfRows {
        let paddingAmount = maxRowLabelWidth - rowLabelWidths[i]
        var out = rowLabels[i] + padding(paddingAmount) + " |"
        
        for j in 0 ..< dataSource.numberOfColumns {
            let item = dataSource.itemForRow(i, column: j)
            let itemString = " \(item) |"
            let paddingAmount = columnWidths[j] - itemString.characters.count
            out += padding(paddingAmount) + itemString
        }
        print(out)
    }
}

printTable(deparment)

其中的計算屬性在 Perl 6 中可以使用重寫屬性的方法來完成。

Role in Perl 6

use v6;

sub padding(Int $amount) {
    my $paddingString = "";
    $paddingString ~= " " for  0 ..^ $amount;
    return $paddingString;
}

# 宣告一個介面, 只定義了方法和屬性, 沒有做實現
role TabularDataSource {
    has $.numberOfRows is rw;
    has $.numberOfColumns is rw;

    method labelForRow(Int $row)             { ... }
    method labelForColumn(Int $column)       { ... }
    method itemForRow(Int $row, Int $column) { ... }
}

class Person {
    has Str $.name;
    has Int $.age;
    has Int $.yearsOfExperience;
}

class Department does TabularDataSource {
    has $.name;
    has @.people;

    method new(Str $name) {
        self.bless(name => $name);
    }
    # 實現介面中的方法

    # 重寫方法 has $.numberOfRows 其實是 has $!numberOfRows 加上 method numberOfRows() { ... } 方法。
    method numberOfRows() {
        return @!people.elems;
    }

    method numberOfColumns() {
        return 2;
    }

    method addPerson(Person $person) is rw {
        @!people.append($person);
    }
    # 如果類遵守了某個 role 但是未實現其中的方法, 則會報錯如下:
    # Method 'labelForRow' must be implemented by Department because it is required by a role
    method labelForRow(Int $row) {
        return @!people[$row].name;
    }

    method labelForColumn(Int $column) {
        given $column {
            when 0  { return "Age" }
            when 1  { return "Years of Experence" }
            default { die("Invalid column!")}
        }
    }

    method itemForRow(Int $row, Int $column) {
        my $person = @!people[$row];
        given $column {
            when 0  { return $person.age               }
            when 1  { return $person.yearsOfExperience }
            default { die("Invalid column")            }
        }
    }
}

my $department = Department.new("Engineering");
$department.addPerson(Person.new(name => "Joe",   age => 30, yearsOfExperience => 6));
$department.addPerson(Person.new(name => "Karen", age => 40, yearsOfExperience => 18));
$department.addPerson(Person.new(name => "Fred",  age => 50, yearsOfExperience => 20));

sub printTable(TabularDataSource $dataSource) {
   my @rowLabels = (0 ..^ $dataSource.numberOfRows ).map: { $dataSource.labelForRow($_)};
   my @columnLabels = (0 ..^ $dataSource.numberOfColumns).map: {$dataSource.labelForColumn($_)};

   my @rowLabelWidths = @rowLabels.map: {$_.chars};
   my $maxRowLabelWidth = @rowLabelWidths.max // return;

   my $firstRow = padding($maxRowLabelWidth) ~ " |";
   my @columnWidths;

   for @columnLabels -> $columnLabel {
       my $columnHeader = " $columnLabel |";
       $firstRow ~= $columnHeader;
       @columnWidths.append($columnHeader.chars);
   }
   say($firstRow);

   for 0 ..^ $dataSource.numberOfRows -> $i {
        my $paddingAmount = $maxRowLabelWidth - @rowLabelWidths[$i];
        my $out = @rowLabels[$i] ~ padding($paddingAmount) ~ " |";

        for 0 ..^ $dataSource.numberOfColumns -> $j {
            my $item = $dataSource.itemForRow($i, $j);
            my $itemString = " $item |";
            my $paddingAmount = @columnWidths[$j] - $itemString.chars;
            $out ~= padding($paddingAmount) ~ $itemString;
        }
        say($out);
   }

}

printTable($department);

role 中的 { ... } 是 yadayada操作符, 起佔位作用, 表示方法會在別處實現。類中的方法同樣也可以這樣宣告。

最後輸出:

      | Age | Years of Experence |
Joe   |  30 |                  6 |
Karen |  40 |                 18 |
Fred  |  50 |                 20 |

相關文章