Wait the light to fall

Swift 中的 protocol 和 Raku 中的 roles 一例

焉知非鱼

Protocol in Swift and Roles in Raku

Protocol 在 Swift 中是一组方法和属性的集合, 可用于代码复用。 Raku 中有与之类似的结构, 叫做 Role, 下面转换一个 Swift 的 Protocol 为 Raku 的 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)

其中的计算属性在 Raku 中可以使用重写属性的方法来完成。

Role in Raku #

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 |