Let's assume I have a protocol Parser
, defined as follows:
protocol Parser {
associatedtype Element
associatedtype Stream
func parse(_ stream: Stream) -> (Element, Stream)?
}
Now let's conform to this protocol with the following struct:
struct CharacterParser: Parser {
let character: Character
func parse(_ stream: String) -> (Character, String)? {
guard stream.first == character
else { return nil }
return (character, String(stream.dropFirst()))
}
}
Now I we can write a neat extension to Character
to create character parsers:
extension Character {
var parser: CharacterParser { return CharacterParser(character: self) }
}
let p = Character("a").parser
print(p.parse("abc"))
// Prints `Optional(("a", "bc"))`
Now let's say I want to hide the specifics of the parser's implementation and use the new opaque types from Swift 5.1. The compiler will let me write the following:
@available(OSX 10.15.0, *)
extension Character {
var parser: some Parser { return CharacterParser(character: self) }
}
So far so good. But now there's no way to call parse(:)
, as it seems the compiler is no longer able to resolve the type of the argument I'm supposed to provide. In other words, the following won't compile:
let p = Character("a").parser
print(p.parse("abc"))
// error: Cannot invoke 'parse' with an argument list of type '(String)'
The only solution I found was to define a "more specific" protocol that inherits from Parser
, e.g. StringParser
, that sets the associated type in a same-type constraint. Unfortunately, I do not particularly like this approach, as I feel it would not scale well if I were to define other methods returning Parser
instances with more elaborate type constraints. In other words, I would trade exposing specific types (e.g. SomeSpecificParserType
) with exposing specific protocols (e.g. SomeSpecificParserProtocol
), whereas I would like to stay at a higher abstraction level, ideally to only deal with some Parser
return types.
Is there any way I could provide additional information to specify that the associated type of the type I return from the property is String
solely on the property definition, so that Swift may later infer the concrete type of p.parse
?
Swift doesn't yet have a very good support when it comes to using opaque return types in the form of protocols with associated types. One of the reasons is that you cannot yet describe the associated types for the protocol when it comes to returning a value of this kind. Thus, the compiler can't infer what to use for the associated types.
If you need to hide the underlying parser, in order to keep it an implementation details, one solution would be to use a type eraser:
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)) }
}