Использование непрозрачного типа со связанным типом - PullRequest
0 голосов
/ 07 октября 2019

Давайте предположим, что у меня есть протокол 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, например, StringParser, который устанавливает связанныйвведите ограничение того же типа. К сожалению, мне не особенно нравится этот подход, так как я чувствую, что он не будет хорошо масштабироваться, если бы я определил другие методы, возвращающие Parser экземпляры с более сложными ограничениями типов. Другими словами, я бы торговал, выставляя определенные типы (например, SomeSpecificParserType), выставляя определенные протоколы (например, SomeSpecificParserProtocol), тогда как я хотел бы остаться на более высоком уровне абстракции, в идеале, чтобы иметь дело только с some Parser возвращаемыми типами.

Можно ли каким-либо образом предоставить дополнительную информацию, чтобы указать, что связанный тип возвращаемого из свойства типа * String исключительно для определения свойства, так что Swift может позднее вывести конкретный тип p.parse * * 1030

1 Ответ

1 голос
/ 07 октября 2019

Swift пока не имеет очень хорошей поддержки, когда дело доходит до использования непрозрачных типов возврата в форме протоколов со связанными типами. Одна из причин заключается в том, что вы еще не можете описать связанные типы для протокола, когда дело доходит до возврата значения такого рода. Таким образом, компилятор не может определить, что использовать для связанных типов.

Если вам нужно скрыть базовый синтаксический анализатор, чтобы сохранить детали реализации, одним из решений будет использование ластика типов:

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)) }
}
...