protocol Parser { func parse() -> ParserOutcome<?> } enum ParserOutcome<Result> { case result(Result) case parser(Parser) }
我想要有解析器返回特定类型的结果或另一个解析器.
如果我在Parser上使用关联类型,那么我不能在枚举中使用Parser.如果我在parse()函数中指定一个通用类型,那么我不能在没有泛型类型的实现中定义它.
我该如何实现?
使用泛型,我可以写这样的东西:
class Parser<Result> { func parse() -> ParserOutcome<Result> { ... } } enum ParserOutcome<Result> { case result(Result) case parser(Parser<Result>) }
这样一来,Parser将被结果类型参数化. parse()可以返回Result类型的结果,也可以返回输出Result类型结果的任何类型的解析器,或返回由相同Result类型参数化的另一个解析器.
然而,关联类型,据我所知,我总是有一个自我约束:
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协议获得与通用定义相同的行为,我希望能够在类型系统的范围内执行此操作,而不会引入新的Boxed类型,就像我可以使用正常的通用定义.
在我看来,定义相关类型OutcomeParser:Parser协议中的Parser,然后返回由该类型参数化的枚举将解决问题,但是如果我尝试以这种方式定义OutcomeParser,我会收到错误:
Type may not reference itself as a requirement
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中,您可以简单地指定解析器大小写具有关联值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仍然执行类型安全.我们试图在一个AnyParser类型的擦除包装器中包装一个BarParser实例(与Strings一起使用),该包装器需要一个Int泛型参数,导致编译器错误.一旦FooParser被参数化以使用Strings而不是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() }