簡體   English   中英

具有泛型類型的協議功能

[英]Protocol function with generic type

我想創建一個如下所示的協議:

protocol Parser {
    func parse() -> ParserOutcome<?>
}

enum ParserOutcome<Result> {
    case result(Result)
    case parser(Parser)
}

我想讓解析器返回特定類型的結果或另一個解析器。

如果我在Parser上使用關聯類型,那么我不能在enum使用Parser 如果我在parse()函數上指定泛型類型,那么我不能在沒有泛型類型的實現中定義它。

我怎樣才能做到這一點?


使用泛型,我可以寫這樣的東西:

class Parser<Result> {
    func parse() -> ParserOutcome<Result> { ... }
}

enum ParserOutcome<Result> {
    case result(Result)
    case parser(Parser<Result>)
}

這樣, Parser將通過結果類型進行參數化。 parse()可以返回Result類型的Result ,或任何類型的解析器,它將輸出Result類型的Result ,或者由同一Result類型參數化的另一個解析器。

然而,對於相關類型,據我所知,我將永遠有一個Self約束:

protocol Parser {
    associatedtype Result

    func parse() -> ParserOutcome<Result, Self>
}

enum ParserOutcome<Result, P: Parser where P.Result == Result> {
    case result(Result)
    case parser(P)
}

在這種情況下,我不能再有任何類型的解析器返回相同的Result類型,它必須是相同類型的解析器。

我希望使用Parser協議獲得與通用定義相同的行為,並且我希望能夠在類型系統的范圍內執行此操作,而無需引入新的盒裝類型,就像我可以使用正常的通用定義。

在我看來,在Parser協議中定義associatedtype OutcomeParser: Parser ,然后返回由該類型參數化的enum將解決問題,但如果我嘗試以這種方式定義OutcomeParser ,我得到錯誤:

類型可能不會將自身引用為要求

我不會那么快就把類型擦除視為“hacky”或“解決[...]類型系統” - 實際上我認為它們類型系統一起工作以提供一個有用的層使用協議時的抽象(如前所述,在標准庫本身中使用,例如AnySequenceAnyIndexAnyCollection )。

正如你自己所說,你想要做的就是有可能從解析器返回給定的結果,或者使用相同結果類型的另一個解析器。 我們不關心該解析器的具體實現,我們只想知道它有一個返回相同類型結果的parse()方法,或者具有相同要求的另一個解析器。

類型擦除非常適合這種情況,因為您需要做的就是引用給定的解析器的parse()方法,允許您抽象出該解析器的其余實現細節。 重要的是要注意你在這里沒有丟失任何類型的安全性,你對要求指定的解析器類型完全一樣准確。

如果我們看一下類型擦除解析器AnyParser的潛在實現,希望你能看到我的意思:

struct AnyParser<Result> : Parser {

    // A reference to the underlying parser's parse() method
    private let _parse : () -> ParserOutcome<Result>

    // Accept any base that conforms to Parser, and has the same Result type
    // as the type erasure's generic parameter
    init<T:Parser where T.Result == Result>(_ base:T) {
        _parse = base.parse
    }

    // Forward calls to parse() to the underlying parser's method
    func parse() -> ParserOutcome<Result> {
        return _parse()
    }
}

現在在您的ParserOutcome ,您可以簡單地指定parser案例具有AnyParser<Result>類型的關聯值 - 即可以使用給定Result泛型參數的任何類型的解析實現。

protocol Parser {
    associatedtype Result
    func parse() -> ParserOutcome<Result>
}

enum ParserOutcome<Result> {
    case result(Result)
    case parser(AnyParser<Result>)
}

...

struct BarParser : Parser {
    func parse() -> ParserOutcome<String> {
        return .result("bar")
    }
}

struct FooParser : Parser {
    func parse() -> ParserOutcome<Int> {
        let nextParser = BarParser()

        // error: Cannot convert value of type 'AnyParser<Result>'
        // (aka 'AnyParser<String>') to expected argument type 'AnyParser<_>'
        return .parser(AnyParser(nextParser))
    }
}

let f = FooParser()
let outcome = f.parse()

switch outcome {
case .result(let result):
    print(result)
case .parser(let parser):
    let nextOutcome = parser.parse()
}

從這個例子可以看出,Swift仍在強制執行類型安全。 我們試圖在一個期望Int泛型參數的AnyParser類型擦除包裝器中包裝一個BarParser實例(與String一起使用),從而導致編譯器錯誤。 一旦FooParser被參數化以使用String而不是Int ,編譯器錯誤將被解決。


事實上,由於AnyParser在這種情況下僅作為單個方法的包裝器,另一個可能的解決方案(如果你真的討厭類型擦除)就是直接使用它作為ParserOutcome的關聯值。

protocol Parser {
    associatedtype Result
    func parse() -> ParserOutcome<Result>
}

enum ParserOutcome<Result> {
    case result(Result)
    case anotherParse(() -> ParserOutcome<Result>)
}


struct BarParser : Parser {
    func parse() -> ParserOutcome<String> {
        return .result("bar")
    }
}

struct FooParser : Parser {
    func parse() -> ParserOutcome<String> {
        let nextParser = BarParser()
        return .anotherParse(nextParser.parse)
    }
}

...

let f = FooParser()
let outcome = f.parse()

switch outcome {
case .result(let result):
    print(result)
case .anotherParse(let nextParse):
    let nextOutcome = nextParse()
}

使這項工作所需的功能的狀態:

  • 遞歸協議約束( SE-0157 )已實現(Swift 4.1)
  • 協議中的任意要求( SE-0142 )已實施(Swift 4)
  • 通用類型別名( SE-0048 )已實現(Swift 3)

看起來如果沒有引入盒裝類型(“類型擦除”技術)目前是不可能的,並且是未來版本的Swift所看到的內容,如完整泛型宣言的 協議部分中的遞歸協議約束任意要求所描述的(因為不支持通用協議 )。

當Swift支持這兩個功能時,以下內容應該有效:

protocol Parser {
    associatedtype Result
    associatedtype SubParser: Parser where SubParser.Result == Result

    func parse() -> ParserOutcome<Result, SubParser>
}

enum ParserOutcome<Result, SubParser: Parser where SubParser.Result == Result> {
    case result(Result)
    case parser(P)
}

隨着通用typealias ES ,子分析器類型也可以提取為:

typealias SubParser<Result> = Parser where SubParser.Result == Result

我認為你想在ParserOutcome枚舉上使用泛型約束。

enum ParserOutcome<Result, P: Parser where P.Result == Result> {
    case result(Result)
    case parser(P)
}

這樣,您就無法將ParserOutcome與任何不符合Parser協議的東西一起使用。 您實際上可以添加一個約束以使其更好。 添加Parser結果的結果與Parser關聯類型的類型相同的約束。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM