通用结构的扩展,其中元素是通用结构

问题描述 投票:0回答:3

我有一个通用结构

FutureValue<Element>
Failable<Element>
,它们都实现了
map

struct FutureValue<Element> {
   func map<U>(_ t: (Element) -> U) -> FutureValue<U> …
}

struct Failable<Element> {
   func map<U>(_ t: (Element) -> U) -> Failable<U> …
}

我想在

FutureValue
上写一个扩展,当它的
Element
any
Failable
时专门化它,这样我就可以实现一个类似
map
的函数,映射到包含的
Element
FutureValue<Failable<Element>>

如何在 Swift 中执行此操作?

swift generics
3个回答
4
投票

你只需要创建一个协议来捕获“任何失败”并捕获你想要的算法片段。

protocol AnyFailable {
    associatedtype Element
    func map<U>(_ t: (Element) -> U) -> Failable<U>
}

并表示所有的Failable都是AnyFailable。

extension Failable: AnyFailable {}

您可能希望在协议上添加方法以提取您需要的数据或提供方法。

然后,创建您的扩展:

extension FutureValue where Element: AnyFailable {
    func map<U>(_ t: (Element.Element) -> U) -> FutureValue<Failable<U>> {
        // You will probably need to provide your own implementation here
        return FutureValue<Failable<U>>(element: element.map(t))
    }
}

值得注意的是我是如何构建它的。我开始写一个更具体的形式基于

String
(只是随机选择一个东西):

extension FutureValue where Element == Failable<String> {
    func map<U>(_ t: (String) -> U) -> FutureValue<Failable<U>> {
        ...
    }
}

我写了一段简单的消费代码:

let f = FutureValue(element: Failable(element: "alice"))
print(f.map { $0.first })

从那里,我将我需要的部分提取到协议中。这往往会让你一步步朝着正确的方向前进。有时直接跳到最通用的形式是非常具有挑战性的。


1
投票

非常感谢 Rob 的超级回答

我最后采用的方法略有不同,所以我将其添加为第二个答案。对于受限于某种元素的泛型的扩展,我觉得这种方法更简单。它也是一种容易引入的“模式”,可以很容易地应用于类似的情况。

/* 
 Protocol for things that can be _concretely_ represented as a `Failable`.
 I keep it private so it's just used to constrain the protocol extension
 below.
*/
private protocol AsFailable {
    associatedtype Element
    var asFailable: Failable<Element> {get}
}

/*
 `Failable` can definitely be represented `AsFailable`…
*/
extension Failable: AsFailable {
    var asFailable: Failable<Element> {
        return self
    }
}

/*
 Use the `AsFailable` protocol to constrain an extension to `FutureValue`
 for any `FutureValue` who's `Element` is a `Failable`.
*/
extension FutureValue where Element: AsFailable {    
    func happyMap<U>(_ t: @escaping (Element.Element) -> U) 
    -> FutureValue<Failable<U>>  {
        return map { $0.asFailable.map(t) }
    }
}

Rob 的方法让我实现了

map
(如 OP 中所述),但是当我也想实现
flatMap
时,我开始陷入困境。切换到
AsFailable
的使用让我快速编写了
flatMap
的简单实现。

我认为

AsXXX
方法对于像这样的情况更简单,其中协议just需要充当约束。

这是

happyFlatMap
的样子:

func happyFlatMap<U>(_ t: @escaping (FailableElement) -> FutureValue<Failable<U>>)
    -> FutureValue<Failable<U>>
{
    typealias Out = FutureValue<Failable<U>>
    return flatMap {
        failable in
        switch failable.asFailable {

        case let .happy(element):
            return t(element)

        case let .error(error):
            return Out(Failable<U>.error(error))

        case let .canceled(reason):
            return Out(Failable<U>.canceled(reason))
        }
    }
}

0
投票

有一种方法可以在没有附加协议的情况下为扩展中的函数指定这样的条件:

extension FutureValue {
    func map<In, Out>(_ t: (In) -> Out) -> FutureValue<Failable<Out>> where FutureElement == Failable<In> { .init() }
}
相关问题
热门问答
最新问题