简体   繁体   English

具有泛型类型的协议功能

[英]Protocol function with generic type

I would like to create a protocol like the following: 我想创建一个如下所示的协议:

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

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

I want to have parsers that return either a result of a specific type, or another parser. 我想让解析器返回特定类型的结果或另一个解析器。

If I use an associated type on Parser , then I can't use Parser in the enum . 如果我在Parser上使用关联类型,那么我不能在enum使用Parser If I specify a generic type on the parse() function, then I can't define it in the implementation without a generic type. 如果我在parse()函数上指定泛型类型,那么我不能在没有泛型类型的实现中定义它。

How can I achieve this? 我怎样才能做到这一点?


Using generics, I could write something like this: 使用泛型,我可以写这样的东西:

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

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

This way, a Parser would be parameterized by the result type. 这样, Parser将通过结果类型进行参数化。 parse() can return a result of the Result type, or any kind of parser that would output either a result of the Result type, or another parser parameterized by the same Result type. parse()可以返回Result类型的Result ,或任何类型的解析器,它将输出Result类型的Result ,或者由同一Result类型参数化的另一个解析器。

With associated types however, as far as I can tell, I'll always have a Self constraint: 然而,对于相关类型,据我所知,我将永远有一个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)
}

In this case, I can't have any type of parser that would return the same Result type anymore, it has to be the same type of parser. 在这种情况下,我不能再有任何类型的解析器返回相同的Result类型,它必须是相同类型的解析器。

I would like to obtain the same behavior with the Parser protocol as I would with a generic definition, and I would like to be able to do that within the bounds of the type system, without introducing new boxed types, just like I can with a normal generic definition. 我希望使用Parser协议获得与通用定义相同的行为,并且我希望能够在类型系统的范围内执行此操作,而无需引入新的盒装类型,就像我可以使用正常的通用定义。

It seems to me that defining associatedtype OutcomeParser: Parser inside the Parser protocol, then returning an enum parameterized by that type would solve the problem, but if I try to define OutcomeParser that way, I get the error: 在我看来,在Parser协议中定义associatedtype OutcomeParser: Parser ,然后返回由该类型参数化的enum将解决问题,但如果我尝试以这种方式定义OutcomeParser ,我得到错误:

Type may not reference itself as a requirement 类型可能不会将自身引用为要求

I wouldn't be so quick to dismiss type erasures as "hacky" or "working around [...] the type system" – in fact I'd argue that they work with the type system in order to provide a useful layer of abstraction when working with protocols (and as already mentioned, used in the standard library itself eg AnySequence , AnyIndex & AnyCollection ). 我不会那么快就把类型擦除视为“hacky”或“解决[...]类型系统” - 实际上我认为它们类型系统一起工作以提供一个有用的层使用协议时的抽象(如前所述,在标准库本身中使用,例如AnySequenceAnyIndexAnyCollection )。

As you said yourself, all you want to do here is have the possibility of either returning a given result from a parser, or another parser that works with the same result type. 正如你自己所说,你想要做的就是有可能从解析器返回给定的结果,或者使用相同结果类型的另一个解析器。 We don't care about the specific implementation of that parser, we just want to know that it has a parse() method that returns a result of the same type, or another parser with that same requirement. 我们不关心该解析器的具体实现,我们只想知道它有一个返回相同类型结果的parse()方法,或者具有相同要求的另一个解析器。

A type erasure is perfect for this kind of situation, as all you need to do is take a reference to a given parser's parse() method, allowing you to abstract away the rest of the implementation details of that parser. 类型擦除非常适合这种情况,因为您需要做的就是引用给定的解析器的parse()方法,允许您抽象出该解析器的其余实现细节。 It's important to note that you aren't losing any type safety here, you're being exactly as precise about the type of the parser as you requirement specifies. 重要的是要注意你在这里没有丢失任何类型的安全性,你对要求指定的解析器类型完全一样准确。

If we look at a potential implementation of a type-erased parser, AnyParser , hopefully you'll see what I mean: 如果我们看一下类型擦除解析器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()
    }
}

Now in your ParserOutcome , you can simply specify that the parser case has an associated value of type AnyParser<Result> – ie any kind of parsing implementation that can work with the given Result generic parameter. 现在在您的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()
}

You can see from this example that Swift is still enforcing type-safety. 从这个例子可以看出,Swift仍在强制执行类型安全。 We're trying to wrap a BarParser instance (that works with String s) in an AnyParser type erased wrapper that expects an Int generic parameter, resulting in a compiler error. 我们试图在一个期望Int泛型参数的AnyParser类型擦除包装器中包装一个BarParser实例(与String一起使用),从而导致编译器错误。 Once FooParser is parameterised to work with String s instead of Int , the compiler error will be resolved. 一旦FooParser被参数化以使用String而不是Int ,编译器错误将被解决。


In fact, as AnyParser in this case only acts as a wrapper for a single method, another potential solution (if you really detest type erasures) is to simply use this directly as your ParserOutcome 's associated value. 事实上,由于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()
}

Status of the features needed to make this work: 使这项工作所需的功能的状态:

  • Recursive protocol constraints ( SE-0157 ) Implemented (Swift 4.1) 递归协议约束( SE-0157 )已实现(Swift 4.1)
  • Arbitrary requirements in protocols ( SE-0142 ) Implemented (Swift 4) 协议中的任意要求( SE-0142 )已实施(Swift 4)
  • Generic Type Aliases ( SE-0048 ) Implemented (Swift 3) 通用类型别名( SE-0048 )已实现(Swift 3)

Looks like this is currently not possible without introducing boxed types (the "type erasure" technique), and is something looked at for a future version of Swift, as described by the Recursive protocol constraints and Arbitrary requirements in protocols sections of the Complete Generics Manifesto (since generic protocols are not going to be supported). 看起来如果没有引入盒装类型(“类型擦除”技术)目前是不可能的,并且是未来版本的Swift所看到的内容,如完整泛型宣言的 协议部分中的递归协议约束任意要求所描述的(因为不支持通用协议 )。

When Swift supports these two features, the following should become valid: 当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)
}

With generic typealias es , the subparser type could also be extracted as: 随着通用typealias ES ,子分析器类型也可以提取为:

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

I think that you want to use a generic constraint on the ParserOutcome enum. 我认为你想在ParserOutcome枚举上使用泛型约束。

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

This way you would not be able to use ParserOutcome with anything that is not conforming to the Parser protocol. 这样,您就无法将ParserOutcome与任何不符合Parser协议的东西一起使用。 You can actually add one more constraint to make it better. 您实际上可以添加一个约束以使其更好。 Adding the constraint that the result of the Parser outcome will be the same type as the Parser 's associated type. 添加Parser结果的结果与Parser关联类型的类型相同的约束。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM