具有通用类型的协议函数
Protocol function with generic type
我想创建如下协议:
protocol Parser {
func parse() -> ParserOutcome<?>
}
enum ParserOutcome<Result> {
case result(Result)
case parser(Parser)
}
我想要 return 特定类型或其他解析器的结果的解析器。
如果我在 Parser
上使用关联类型,那么我不能在 enum
上使用 Parser
。如果我在 parse()
函数上指定了一个泛型类型,那么我无法在没有泛型类型的实现中定义它。
我怎样才能做到这一点?
使用泛型,我可以这样写:
class Parser<Result> {
func parse() -> ParserOutcome<Result> { ... }
}
enum ParserOutcome<Result> {
case result(Result)
case parser(Parser<Result>)
}
这样,Parser
将由结果类型参数化。 parse()
可以 return Result
类型的结果,或者任何类型的解析器,可以输出 Result
类型的结果,或者由相同 Result
参数化的另一个解析器=19=]类型。
然而,据我所知,对于关联类型,我将始终有一个 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)
}
在这种情况下,我不能再有任何类型的解析器 return 相同的 Result
类型,它必须是相同类型的解析器。
我希望使用 Parser
协议获得与使用通用定义相同的行为,并且我希望能够在类型系统的范围内做到这一点,而无需引入新的盒装类型,就像我可以使用普通的通用定义一样。
在我看来,在 Parser
协议中定义 associatedtype OutcomeParser: Parser
,然后 returning 一个由该类型参数化的 enum
可以解决问题,但是如果我尝试以这种方式定义 OutcomeParser
,但出现错误:
Type may not reference itself as a requirement
我认为您想对 ParserOutcome
枚举使用通用约束。
enum ParserOutcome<Result, P: Parser where P.Result == Result> {
case result(Result)
case parser(P)
}
这样你就不能对任何不符合 Parser
协议的东西使用 ParserOutcome
。您实际上可以再添加一个约束以使其变得更好。添加 Parser
结果的结果将与 Parser
的关联类型相同的类型的约束。
完成这项工作所需功能的状态:
目前看来,如果不引入盒装类型("type erasure" 技术),这是不可能的,并且是 Swift 的未来版本所关注的内容,如 Recursive protocol constraints and Arbitrary requirements in protocols sections of the Complete Generics Manifesto (since 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,子解析器类型也可以提取为:
typealias SubParser<Result> = Parser where SubParser.Result == Result
我不会像 "hacky" 或 "working around [...] the type system" 那样快速地驳回类型擦除——事实上,我认为它们 与 一起工作类型系统,以便在使用协议时提供有用的抽象层(并且如前所述,在标准库本身中使用,例如 AnySequence
, AnyIndex
& AnyCollection
)。
正如您自己所说,您在这里要做的就是有可能从解析器或使用相同结果类型的另一个解析器返回给定结果。我们不关心那个解析器的具体实现,我们只想知道它有一个 parse()
方法,returns 一个相同类型的结果,或者另一个具有相同要求的解析器。
类型擦除非常适合这种情况,因为您需要做的就是引用给定解析器的 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()
}
我想创建如下协议:
protocol Parser {
func parse() -> ParserOutcome<?>
}
enum ParserOutcome<Result> {
case result(Result)
case parser(Parser)
}
我想要 return 特定类型或其他解析器的结果的解析器。
如果我在 Parser
上使用关联类型,那么我不能在 enum
上使用 Parser
。如果我在 parse()
函数上指定了一个泛型类型,那么我无法在没有泛型类型的实现中定义它。
我怎样才能做到这一点?
使用泛型,我可以这样写:
class Parser<Result> {
func parse() -> ParserOutcome<Result> { ... }
}
enum ParserOutcome<Result> {
case result(Result)
case parser(Parser<Result>)
}
这样,Parser
将由结果类型参数化。 parse()
可以 return Result
类型的结果,或者任何类型的解析器,可以输出 Result
类型的结果,或者由相同 Result
参数化的另一个解析器=19=]类型。
然而,据我所知,对于关联类型,我将始终有一个 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)
}
在这种情况下,我不能再有任何类型的解析器 return 相同的 Result
类型,它必须是相同类型的解析器。
我希望使用 Parser
协议获得与使用通用定义相同的行为,并且我希望能够在类型系统的范围内做到这一点,而无需引入新的盒装类型,就像我可以使用普通的通用定义一样。
在我看来,在 Parser
协议中定义 associatedtype OutcomeParser: Parser
,然后 returning 一个由该类型参数化的 enum
可以解决问题,但是如果我尝试以这种方式定义 OutcomeParser
,但出现错误:
Type may not reference itself as a requirement
我认为您想对 ParserOutcome
枚举使用通用约束。
enum ParserOutcome<Result, P: Parser where P.Result == Result> {
case result(Result)
case parser(P)
}
这样你就不能对任何不符合 Parser
协议的东西使用 ParserOutcome
。您实际上可以再添加一个约束以使其变得更好。添加 Parser
结果的结果将与 Parser
的关联类型相同的类型的约束。
完成这项工作所需功能的状态:
目前看来,如果不引入盒装类型("type erasure" 技术),这是不可能的,并且是 Swift 的未来版本所关注的内容,如 Recursive protocol constraints and Arbitrary requirements in protocols sections of the Complete Generics Manifesto (since 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,子解析器类型也可以提取为:
typealias SubParser<Result> = Parser where SubParser.Result == Result
我不会像 "hacky" 或 "working around [...] the type system" 那样快速地驳回类型擦除——事实上,我认为它们 与 一起工作类型系统,以便在使用协议时提供有用的抽象层(并且如前所述,在标准库本身中使用,例如 AnySequence
, AnyIndex
& AnyCollection
)。
正如您自己所说,您在这里要做的就是有可能从解析器或使用相同结果类型的另一个解析器返回给定结果。我们不关心那个解析器的具体实现,我们只想知道它有一个 parse()
方法,returns 一个相同类型的结果,或者另一个具有相同要求的解析器。
类型擦除非常适合这种情况,因为您需要做的就是引用给定解析器的 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()
}