【问题标题】:Recursive Swift Either Enum with Generic Associated Values具有通用关联值的递归 Swift 枚举
【发布时间】:2021-03-05 11:45:02
【问题描述】:

我想开发一个递归 Either 类型作为 Swift 枚举(至少是部分工作版本 (;),因此一直在玩泛型(尝试使用类型系统)。

这是我目前所拥有的:

enum NumericEitherEnum<Left, Right> {
  case left(Left)
  case right(Right)

  var left: Left? {
    switch self {
      case .left(let leftie) where leftie is NumericEitherEnum<Int, Float>:
        return (leftie as? NumericEitherEnum<Int, Float>)?.left // This won't work (obviously)
      case .left(let left):
        return left
      case .right(_):
        return nil
    }
  }

  var right: Right? {
    switch self {
      case .left(_):
        return nil
      case .right(let right) where right is NumericEitherEnum:
        return (right as? NumericEitherEnum)?.right // This assumes the generic parameters of Left, Right in the current enum so won't work.
      case .right(let right):
        return right
    }
  }
}

我在 Xcode 中收到神秘的诊断错误消息,我无法解决:

  1. '用NumericEitherEnum&lt;Int, Float&gt;替换(leftie as? NumericEitherEnum&lt;Int, Float&gt;)?'
  2. 'Enum case 'left' 不能用作实例成员'

我想要达到的目标:

print(NumericEitherEnum<NumericEitherEnum<Int, Float>, NumericEitherEnum<Int, Float>>.left(.left(3)).left ?? "failed :(") // Parses the innermost value 3

修复 - 它不会修复错误或建议如何实际解决根本原因。我认为这可能是编译器解析的边缘情况(甚至可能是错误);)。我也意识到实际上返回 IntLeft? 类型在逻辑上没有意义,但有什么方法可以在类型系统中表达这一点(我尝试了关联类型,但我仍然不知道如何使这个动态)。处理具有不同泛型类型签名的嵌套枚举也存在问题(不知道如何表达)。

如何以更好的方式解决此问题?理想情况下,我希望有一个干净的调用站点,没有太多间接和额外的数据结构,但如果这实际上无法实现,我愿意尝试不同的数据结构。

【问题讨论】:

    标签: ios swift generics enums


    【解决方案1】:

    所以我发现 Swift 2 很久以前就引入了对递归枚举的支持 - source!虽然这很好,但我没有找到任何针对这个特定问题的 SO 问题,即 Generic也已回答的递归枚举 (here)。我现在总结一下最终的(部分解决方案)代码。

    事实证明,我需要从数据结构的角度来描述问题所在。本质上,我们希望.left 包含LeftRecursiveEither&lt;Left, Right&gt; 类型的值。有了这个简单的想法,我们可以创建两个枚举 - 一个用于封装枚举,一个用于嵌套枚举,可以采用 Value 或另一个封装。

    enum RecursiveEither<Left, Right> {
      case left(ValueOrLeftOrRight<Left, Left, Right>)
      case right(ValueOrLeftOrRight<Right, Left, Right>)
      
      var left: Left? {
        guard case .left(let leftie) = self else { return nil }
        return leftie.left
      }
    
      var right: Right? {
        guard case .right(let rightie) = self else { return nil }
        return rightie.right
      }
    }
    
    enum ValueOrLeftOrRight<Value, Left, Right> {
      case value(Value)
      indirect case left(ValueOrLeftOrRight<Left, Left, Right>)
      indirect case right(ValueOrLeftOrRight<Right, Left, Right>)
      
      var left: Left? {
        switch self {
        case .value(let left): return left as? Left
        case .left(let content): return content.left
        default: return nil
        }
      }
    
      var right: Right? {
        switch self {
        case .value(let right): return right as? Right
        case .right(let content): return content.right
        default: return nil
        }
      }
    }
    

    呼叫站点很适合这种设计:

    let e = RecursiveEither<Int, Int>.left(.left(.left(.left(.value(3)))))
    

    话虽如此,这个答案仍然存在限制 - 假设 LeftRight 即使在嵌套的 Either 中也不会改变。此外,可以仅使用一个 indirect 案例以将 Either 类型包装在另一个关联值中来进行不同的设计 - 这可能会提高内存效率,因为在运行时将更少的帧推入调用堆栈。

    我已经发布了gist 尝试解决这个问题。

    如果有人有改进此实现的建议,请随时添加。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-08-19
      • 1970-01-01
      • 1970-01-01
      • 2018-01-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多