具有泛型类型的协议功能

时间:2016-07-07 05:54:04

标签: swift generics types swift-protocols associated-types

我想创建如下协议:

protocol Parser {
    func parse() -> ParserOutcome<?>
}

enum ParserOutcome<Result> {
    case result(Result)
    case parser(Parser)
}

我希望解析器返回特定类型的结果或其他解析器。

如果我在Parser上使用关联类型,则我无法在Parser中使用enum。如果我在parse()函数上指定泛型类型,那么我不能在没有泛型类型的实现中定义它。

我怎样才能做到这一点?

使用泛型,我可以这样写:

class Parser<Result> {
    func parse() -> ParserOutcome<Result> { ... }
}

enum ParserOutcome<Result> {
    case result(Result)
    case parser(Parser<Result>)
}

这样,Parser将由结果类型参数化。 parse()可以返回Result类型的结果,或者输出Result类型结果的任何类型的解析器,或者由同一Result参数化的其他解析器类型。

但是,对于相关类型,据我所知,我将始终有一个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)
}

在这种情况下,我不能再使用任何类型的解析器返回相同的Result类型,它必须是相同类型的解析器。

我想使用Parser协议获得与通用定义相同的行为,我希望能够在类型系统的范围内执行此操作,而不会引入新的盒装类型就像我可以使用普通的通用定义一样。

在我看来,在associatedtype OutcomeParser: Parser协议中定义Parser,然后返回由该类型参数化的enum将解决问题,但如果我尝试定义{{1}那样,我收到了错误:

  

类型可能不会将自身引用为要求

3 个答案:

答案 0 :(得分:3)

我不会那么快地将类型删除视为“hacky”或“解决[...]类型系统” - 实际上我认为他们使用工作类型系统,以便在使用协议时提供有用的抽象层(如前所述,在标准库中使用,例如AnySequenceAnyIndex&amp; 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中,您可以简单地指定parser案例具有类型AnyParser<Result>的关联值 - 即可以使用给定{{}的任何类型的解析实现1}}通用参数。

Result

从这个例子可以看出,Swift仍在强制执行类型安全。我们试图将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() } 实例(适用于BarParser s)包装在String类型的擦除包装器中,该包装器需要AnyParser泛型参数,从而导致编译器错误。参数化Int以使用FooParser而不是String后,编译错误将得到解决。

事实上,由于Int在这种情况下仅作为单个方法的包装器,另一个可能的解决方案(如果你真的讨厌类型擦除)就是直接使用它作为你的AnyParser'相关价值。

ParserOutcome

答案 1 :(得分:2)

使这项工作所需的功能状态:

  • 递归协议约束(SE-0157已实施(Swift 4.1)
  • 协议中的任意要求(SE-0142已实施(Swift 4)
  • 通用类型别名(SE-0048已实施(Swift 3)

在没有引入盒装类型(&#34;类型擦除&#34;技术)的情况下,目前看来这是不可能的,并且正在考虑将来版本的Swift,如Recursive protocol constraintsArbitrary requirements in protocolsComplete Generics Manifesto部分(因为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 typealiases,subparser类型也可以提取为:

typealias SubParser<Result> = Parser where SubParser.Result == Result

答案 2 :(得分:0)

我认为你想在ParserOutcome枚举上使用通用约束。

enum ParserOutcome<Result, P: Parser where P.Result == Result> {
    case result(Result)
    case parser(P)
}

这样,您就无法将ParserOutcome与任何不符合Parser协议的内容一起使用。您实际上可以添加一个约束以使其更好。添加约束,Parser结果的结果将与Parser相关类型的类型相同。