使用具有关联类型的不透明类型
Using an opaque type with an associated type
假设我有一个协议Parser
,定义如下:
protocol Parser {
associatedtype Element
associatedtype Stream
func parse(_ stream: Stream) -> (Element, Stream)?
}
现在让我们使用以下结构来遵守此协议:
struct CharacterParser: Parser {
let character: Character
func parse(_ stream: String) -> (Character, String)? {
guard stream.first == character
else { return nil }
return (character, String(stream.dropFirst()))
}
}
现在我们可以为 Character
编写一个简洁的扩展来创建字符解析器:
extension Character {
var parser: CharacterParser { return CharacterParser(character: self) }
}
let p = Character("a").parser
print(p.parse("abc"))
// Prints `Optional(("a", "bc"))`
现在假设我想隐藏解析器实现的细节并使用 Swift 5.1 中的新不透明类型。编译器会让我这样写:
@available(OSX 10.15.0, *)
extension Character {
var parser: some Parser { return CharacterParser(character: self) }
}
到目前为止一切顺利。但是现在无法调用 parse(:)
,因为编译器似乎不再能够解析我应该提供的参数类型。换句话说,以下不会编译:
let p = Character("a").parser
print(p.parse("abc"))
// error: Cannot invoke 'parse' with an argument list of type '(String)'
我找到的唯一解决方案是定义一个继承自 Parser
的 "more specific" 协议,例如StringParser
,在同类型约束中设置关联类型。不幸的是,我不是特别喜欢这种方法,因为我觉得如果我要定义其他方法 returning Parser
具有更详细类型约束的实例,它不会很好地扩展。换句话说,我会用暴露特定类型(例如 SomeSpecificParserType
)和暴露特定协议(例如 SomeSpecificParserProtocol
)来交换,而我想保持在更高的抽象级别,理想情况下只处理 some Parser
return 类型。
我有什么办法可以提供额外的信息来指定 属性 中类型 I return 的关联类型仅在 属性 上是 String
定义,以便 Swift 稍后可以推断出 p.parse
?
的具体类型
Swift 在以协议形式使用不透明 return 类型和关联类型时,还没有很好的支持。原因之一是当涉及到 returning 这种类型的值时,您还不能描述协议的关联类型。因此,编译器无法推断关联类型使用什么。
如果您需要隐藏底层解析器,以便保留其实现细节,一种解决方案是使用类型橡皮擦:
struct AnyParser<Element, Stream>: Parser {
private var _parse: (Stream) -> (Element, Stream)?
init<P: Parser>(_ parser: P) where P.Element == Element, P.Stream == Stream {
_parse = parser.parse
}
func parse(_ stream: Stream) -> (Element, Stream)? {
return _parse(stream)
}
}
extension Character {
var parser: AnyParser<Self, String> { return .init(CharacterParser(character: self)) }
}
假设我有一个协议Parser
,定义如下:
protocol Parser {
associatedtype Element
associatedtype Stream
func parse(_ stream: Stream) -> (Element, Stream)?
}
现在让我们使用以下结构来遵守此协议:
struct CharacterParser: Parser {
let character: Character
func parse(_ stream: String) -> (Character, String)? {
guard stream.first == character
else { return nil }
return (character, String(stream.dropFirst()))
}
}
现在我们可以为 Character
编写一个简洁的扩展来创建字符解析器:
extension Character {
var parser: CharacterParser { return CharacterParser(character: self) }
}
let p = Character("a").parser
print(p.parse("abc"))
// Prints `Optional(("a", "bc"))`
现在假设我想隐藏解析器实现的细节并使用 Swift 5.1 中的新不透明类型。编译器会让我这样写:
@available(OSX 10.15.0, *)
extension Character {
var parser: some Parser { return CharacterParser(character: self) }
}
到目前为止一切顺利。但是现在无法调用 parse(:)
,因为编译器似乎不再能够解析我应该提供的参数类型。换句话说,以下不会编译:
let p = Character("a").parser
print(p.parse("abc"))
// error: Cannot invoke 'parse' with an argument list of type '(String)'
我找到的唯一解决方案是定义一个继承自 Parser
的 "more specific" 协议,例如StringParser
,在同类型约束中设置关联类型。不幸的是,我不是特别喜欢这种方法,因为我觉得如果我要定义其他方法 returning Parser
具有更详细类型约束的实例,它不会很好地扩展。换句话说,我会用暴露特定类型(例如 SomeSpecificParserType
)和暴露特定协议(例如 SomeSpecificParserProtocol
)来交换,而我想保持在更高的抽象级别,理想情况下只处理 some Parser
return 类型。
我有什么办法可以提供额外的信息来指定 属性 中类型 I return 的关联类型仅在 属性 上是 String
定义,以便 Swift 稍后可以推断出 p.parse
?
Swift 在以协议形式使用不透明 return 类型和关联类型时,还没有很好的支持。原因之一是当涉及到 returning 这种类型的值时,您还不能描述协议的关联类型。因此,编译器无法推断关联类型使用什么。
如果您需要隐藏底层解析器,以便保留其实现细节,一种解决方案是使用类型橡皮擦:
struct AnyParser<Element, Stream>: Parser {
private var _parse: (Stream) -> (Element, Stream)?
init<P: Parser>(_ parser: P) where P.Element == Element, P.Stream == Stream {
_parse = parser.parse
}
func parse(_ stream: Stream) -> (Element, Stream)? {
return _parse(stream)
}
}
extension Character {
var parser: AnyParser<Self, String> { return .init(CharacterParser(character: self)) }
}