我想创建一个如下所示的协议:
protocol Parser {
func parse() -> ParserOutcome<?>
}
enum ParserOutcome<Result> {
case result(Result)
case parser(Parser)
}
我想让解析器返回特定类型的结果或另一个解析器。
如果我在Parser
上使用相关类型,那么我就不能在Parser
中使用enum
。如果我在parse()
函数上指定泛型类型,那么我不能在没有泛型类型的实现中定义它。
我怎样才能做到这一点?
使用泛型,我可以写这样的东西:
class Parser<Result> {
func parse() -> ParserOutcome<Result> { ... }
}
enum ParserOutcome<Result> {
case result(Result)
case parser(Parser<Result>)
}
这样,Parser
将由结果类型参数化。 parse()
可以返回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
协议获得与通用定义相同的行为,我希望能够在类型系统的范围内执行此操作,而不会引入新的盒装类型,就像我可以使用正常的通用定义。
在我看来,在associatedtype OutcomeParser: Parser
协议中定义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仍在强制执行类型安全。我们试图将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()
}
使这项工作所需的功能的状态:
看起来这是目前不可能没有引入盒装类型(“类型擦除”技术),并且是未来版本的Swift,如Recursive protocol constraints的Arbitrary requirements in protocols和Complete Generics Manifesto部分所描述(因为generic protocols不会得到支持)。
当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)
}
使用generic typealias
es,subparser类型也可以提取为:
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
的关联类型相同的约束。