【问题标题】:How to unwrap arbitrarily deeply nested optionals in Swift?如何在 Swift 中解开任意深度嵌套的选项?
【发布时间】:2018-10-21 08:00:04
【问题描述】:

作为 Swift 的一个练习,我正在尝试编写一个扩展方法来解开任意深度嵌套的选项。 这没什么实际用处,只是对 Swift 类型系统的探索。

任意深度嵌套的可选项的示例是 Optional<Optional<Optional<Int>>>Optional<Optional<Optional<Optional<Int>>>>

我发现这样做的唯一方法是使用类型擦除:

protocol TypeErasedOptional {
    func deeplyUnwrap() -> Any?
}

extension Optional: TypeErasedOptional {
    func deeplyUnwrap() -> Any? {
        switch self {
        case .none: return nil
        case .some(let wrapped as TypeErasedOptional): return wrapped.deeplyUnwrap()
        case .some(let wrapped): return wrapped
        }
    }

    func unwrap<T>(_ type: T.Type = T.self) -> T? {
       switch deeplyUnwrap() {
       case .none: return nil
       case .some(let wrapped as T): return wrapped
       default: return nil
       }
    }
}

这很好用。我们可以解开一个深度嵌套的可选项,但不幸的是我们必须重述Wrapped 类型:

let x = Optional<Optional<Optional<Int>>>(3)
let y = x.unwrap(Int.self)

如果没有类型擦除,我想不出任何方法来做到这一点。一旦你使用类型擦除,你必须重述类型以取回它。我不想要这个。是否可以让更精通 Swift 的人告诉我这无法完成或是否有其他方法?

【问题讨论】:

  • Swift 的铸造机械可以钻透任意级别的可选性,所以你可以说let y = x as? Int :)
  • @Hamish 我很清楚!但这不会那么有趣。
  • 很公平 - 我很确定如果不以某种方式重新声明 Wrapped 类型,您将无法实现这一点,因为您无法递归展开通用占位符(即你不能表达“如果Wrapped类型是Optional,递归考虑它的Wrapped类型”)。
  • 为了好玩,您可以创建自己的包装器类型来跟踪原始包装值 (gist.github.com/hamishknight/e08e97f3170f8f30f8ca9df21a6ec365),但这可能不是您的想法;)
  • 这在 Swift 4.1+ 中更有用,编译器不再自动完全解开嵌套的选项。

标签: swift swift4


【解决方案1】:

这里有一个解决方案,可提供多达六层的扁平化Optional

extension Optional {
    func flatten() -> Wrapped? {
        return self
    }

    func flatten<T>() -> T? where Wrapped == T? {
        return map { $0.flatten() } ?? nil
    }

    func flatten<T>() -> T? where Wrapped == T?? {
        return map { $0.flatten() } ?? nil
    }

    func flatten<T>() -> T? where Wrapped == T??? {
        return map { $0.flatten() } ?? nil
    }

    func flatten<T>() -> T? where Wrapped == T???? {
        return map { $0.flatten() } ?? nil
    }

    func flatten<T>() -> T? where Wrapped == T????? {
        return map { $0.flatten() } ?? nil
    }
}

上述解决方案的优点是类型安全,缺点是它是静态类型的(例如不能在Any 变量上调用flatten()),并且您需要添加更多和如果您需要支持越来越多的嵌套级别,则需要更多的重载。

【讨论】:

  • 这看起来效率很低。应该有一个单一且通用的方法。
  • @George_E 我在答案的最后提到了这一点。如果您有更好的解决方案,请随时添加答案。
【解决方案2】:

Optional.flatMap(_:) 似乎可以解决问题。例如:

var a: String??? = "aaa"
let b: String = a?.flatMap{$0} ?? "<nil>"

print(b) // "aaa"

documentation 没有明确提及此功能,但它确实有效。

编辑:不是,请参阅下面的评论。

【讨论】:

  • ?.flatMap?? 的组合正在一起工作,以便在这里删除三层。它不是递归的。尝试将a 设置为String????,它会停止工作。
  • 这太糟糕了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-07-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-20
  • 1970-01-01
相关资源
最近更新 更多