swift – 具有通用类型的协议功能

前端之家收集整理的这篇文章主要介绍了swift – 具有通用类型的协议功能前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我想创建一个如下所示的协议:
protocol Parser {
    func parse() -> ParserOutcome<?>
}

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

我想要有解析器返回特定类型的结果或另一个解析器.

如果我在Parser上使用关联类型,那么我不能在枚举中使用Parser.如果我在parse()函数中指定一个通用类型,那么我不能在没有泛型类型的实现中定义它.

我该如何实现?

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

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

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

这样一来,Parser将被结果类型参数化. parse()可以返回Result类型的结果,也可以返回输出Result类型结果的任何类型的解析器,或返回由相同Result类型参数化的另一个解析器.

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

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协议获得与通用定义相同的行为,我希望能够在类型系统的范围内执行此操作,而不会引入新的Boxed类型,就像我可以使用正常的通用定义.

在我看来,定义相关类型OutcomeParser:Parser协议中的Parser,然后返回由该类型参数化的枚举将解决问题,但是如果我尝试以这种方式定义OutcomeParser,我会收到错误

Type may not reference itself as a requirement

我不会如此快速地将类型删除作为“黑客”或“围绕[类型系统”工作] – 实际上我认为它们与类型系统一起工作,以提供一个有用的层在使用协议(并且已经提及的标准库本身使用例如 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中,您可以简单地指定解析器大小写具有关联值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仍然执行类型安全.我们试图在一个AnyParser类型的擦除包装器中包装一个BarParser实例(与Strings一起使用),该包装器需要一个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()
}

猜你在找的Swift相关文章