14

我想创建一个如下协议:

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

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

我想让解析器返回特定类型的结果或另一个解析器。

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

我怎样才能做到这一点?


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

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

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

这样, aParser将由结果类型参数化。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内部定义,然后返回由该类型参数化的可以解决问题,但如果我尝试以这种方式定义,我会收到错误:ParserenumOutcomeParser

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

4

3 回答 3

5

我不会这么快就将类型擦除视为“hacky”或“围绕类型系统工作”——事实上,我认为它们类型系统一起工作是为了提供一个有用的层使用协议时的抽象(如前所述,在标准库本身中使用,例如AnySequence& AnyIndexAnyCollection

正如您自己所说,您在这里要做的就是有可能从解析器返回给定结果,或者从另一个使用相同结果类型的解析器返回。我们不关心该解析器的具体实现,我们只想知道它有一个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>——即可以使用给定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实例(与Strings 一起使用)包装在一个AnyParser需要泛型参数的类型擦除包装器中Int,从而导致编译器错误。一旦FooParser参数化为使用Strings 而不是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()
}
于 2016-07-07T13:10:42.930 回答
3

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

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

看起来这在不引入盒装类型(“类型擦除”技术)的情况下目前是不可能的,并且是在 Swift 的未来版本中看到的东西,如完整泛型宣言的协议部分中的递归协议约束任意要求所述(因为不支持通用协议)。

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

使用泛型typealiases,子解析器类型也可以提取为:

typealias SubParser<Result> = Parser where SubParser.Result == Result
于 2016-07-07T11:38:01.943 回答
0

我认为您想对ParserOutcome枚举使用通用约束。

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

这样你就不能使用ParserOutcome任何不符合Parser协议的东西。您实际上可以再添加一个约束以使其更好。Parser添加结果的结果将与Parser的关联类型相同的约束。

于 2016-07-07T07:48:07.950 回答