我不会这么快就将类型擦除视为“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 仍然在强制执行类型安全。我们试图将一个BarParser
实例(与String
s 一起使用)包装在一个AnyParser
需要泛型参数的类型擦除包装器中Int
,从而导致编译器错误。一旦FooParser
参数化为使用String
s 而不是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()
}