[英]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”或“解決[...]類型系統” - 實際上我認為它們與類型系統一起工作以提供一個有用的層使用協議時的抽象(如前所述,在標准庫本身中使用,例如AnySequence
, AnyIndex
和AnyCollection
)。
正如你自己所說,你想要做的就是有可能從解析器返回給定的結果,或者使用相同結果類型的另一個解析器。 我們不關心該解析器的具體實現,我們只想知道它有一個返回相同類型結果的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()
}
使這項工作所需的功能的狀態:
看起來如果沒有引入盒裝類型(“類型擦除”技術)目前是不可能的,並且是未來版本的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.