首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >具有泛型的协议函数

具有泛型的协议函数
EN

Stack Overflow用户
提问于 2016-07-07 05:54:04
回答 3查看 7.7K关注 0票数 14

我想制定一项协议,如下所示:

代码语言:javascript
运行
复制
protocol Parser {
    func parse() -> ParserOutcome<?>
}

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

我希望有返回特定类型的结果或另一个解析器的解析器。

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

我怎样才能做到这一点?

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

代码语言:javascript
运行
复制
class Parser<Result> {
    func parse() -> ParserOutcome<Result> { ... }
}

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

这样,Parser就会被结果类型参数化。parse()可以返回Result类型的结果,或者任何类型的解析器,这些解析器可以输出Result类型的结果,也可以返回由同一Result类型参数化的另一个解析器。

然而,对于关联类型,据我所知,我总是有一个Self约束:

代码语言:javascript
运行
复制
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会解决这个问题,但是如果我尝试以这种方式定义OutcomeParser,就会得到错误:

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

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2016-07-07 11:38:01

开展这项工作所需的特性的现状:

  • 递归协议约束(SE-0157) 实现(SWIFT4.1)
  • 协议(硒-0142)中的任意需求--实现(Swift 4)
  • 实现泛型类型别名(SE-0048) (Swift 3)

看起来,如果不引入装箱类型(“类型擦除”技术),这看起来是不可能的,而且是为Swift的未来版本而研究的,正如递归协议约束全泛型宣言协议中的任意要求节所描述的那样(因为通用协议将不受支持)。

当Swift支持这两个功能时,以下内容应该是有效的:

代码语言:javascript
运行
复制
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)
}

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

代码语言:javascript
运行
复制
typealias SubParser<Result> = Parser where SubParser.Result == Result
票数 3
EN

Stack Overflow用户

发布于 2016-07-07 13:10:42

我不会那么快地将类型擦除视为"hacky“或”工作于.类型系统“--事实上,我认为它们与类型系统一起工作是为了在处理协议时提供一个有用的抽象层(正如前面提到的,在标准库中使用,例如AnySequenceAnyIndexAnyCollection)。

正如您自己说的,您在这里要做的就是有可能从解析器返回给定的结果,或者返回使用相同结果类型的另一个解析器。我们不关心解析器的具体实现,我们只想知道它有一个返回相同类型的结果的parse()方法,或者另一个具有相同需求的解析器。

类型擦除对于这种情况是完美的,因为您所需要做的就是引用给定解析器的parse()方法,允许您抽象出该解析器的其余实现细节。需要注意的是,您在这里不会丢失任何类型的安全性,您对解析器的类型的精确程度与您所需指定的相同。

如果我们查看类型擦除解析器AnyParser的潜在实现,希望您能够理解我的意思:

代码语言:javascript
运行
复制
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泛型参数一起工作的任何类型的解析实现。

代码语言:javascript
运行
复制
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实例(与String一起工作)封装在一个AnyParser类型的擦除包装器中,该包装器需要一个Int泛型参数,从而导致编译器错误。一旦FooParser被参数化以使用String而不是Int,编译器错误将被解决。

实际上,由于AnyParser在本例中只充当单个方法的包装器,另一个潜在的解决方案(如果您真的讨厌类型擦除)就是直接使用它作为ParserOutcome的关联值。

代码语言:javascript
运行
复制
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()
}
票数 6
EN

Stack Overflow用户

发布于 2016-07-07 07:48:07

我认为您希望在ParserOutcome枚举上使用泛型约束。

代码语言:javascript
运行
复制
enum ParserOutcome<Result, P: Parser where P.Result == Result> {
    case result(Result)
    case parser(P)
}

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

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/38238371

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档