简体   繁体   English

递归 Swift 具有通用关联值的枚举

[英]Recursive Swift Either Enum with Generic Associated Values

I want to develop a recursive Either type as a Swift enum (at least a partially working version (;), and so have been playing around with generics (trying to work with the type system).我想开发一个递归的Either类型作为 Swift 枚举(至少是部分工作版本(;),所以一直在玩 generics (尝试使用类型系统)。

Here is what I have so far:这是我到目前为止所拥有的:

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
    }
  }
}

I get cryptic diagnostic error messages in Xcode, which I haven't been able to get around:我在 Xcode 中收到神秘的诊断错误消息,我无法解决:

  1. 'Replace (leftie as? NumericEitherEnum<Int, Float>)? '替换(leftie as? NumericEitherEnum<Int, Float>)? with NumericEitherEnum<Int, Float> 'NumericEitherEnum<Int, Float> '
  2. 'Enum case 'left' cannot be used as an instance member' '枚举案例'left'不能用作实例成员'

What I am trying to achieve:我想要达到的目标:

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

The fix-it doesn't fix the error or advise how to actually address the underlying cause.修复它不会修复错误或建议如何实际解决根本原因。 I think this is probably an edge case for the compiler to parse (maybe even bug);).我认为这可能是编译器解析的边缘情况(甚至可能是错误);)。 I also realise that in fact it doesn't make sense logically to return an Int for a Left?我也意识到实际上返回IntLeft? type but is there any way I can express this within the type system (I tried associated types but I still don't know how to make this dynamic).类型,但有什么方法可以在类型系统中表达这个(我尝试了关联类型,但我仍然不知道如何使这个动态)。 There's also a problem to handle nested enums that have a different generic type signature (not sure how to express this).处理具有不同泛型类型签名的嵌套枚举也存在问题(不知道如何表达)。

How can I resolve this issue in a better way?我怎样才能更好地解决这个问题? Ideally, I want to have a clean call site without too much indirection and extra data structures but would be open to trying out a different data structure if this isn't practically achievable.理想情况下,我希望有一个干净的调用站点,没有太多间接和额外的数据结构,但如果这实际上无法实现,我愿意尝试不同的数据结构。

So I discovered that Swift 2 introduced support for recursive enums ages ago - source , While this is great.所以我发现 Swift 2 很久以前就引入了对递归枚举的支持 - 源代码,虽然这很棒。 I didn't manage to find any SO questions for this specific problem ie Generic Recursive Enums that had also been answered ( here ).我没有找到针对这个特定问题的任何 SO 问题,即也已回答通用递归枚举( 此处)。 I will summarise the final (partial solution) code now.我现在将总结最终(部分解决方案)代码。

It turns out that I needed to describe what the problem is in terms of data structures.事实证明,我需要用数据结构来描述问题所在。 Essentially we want .left to contain either a value of type Left or a RecursiveEither<Left, Right> .本质上,我们希望.left包含LeftRecursiveEither<Left, Right>类型的值。 With this simple idea, we can create two enums - one for the wrapper enum, and one for the nested enum which can take a Value or another wrapper.有了这个简单的想法,我们可以创建两个枚举 - 一个用于包装器枚举,一个用于嵌套枚举,它可以采用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
    }
  }
}

The call site is nice with this design:呼叫站点很适合这种设计:

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

That being said, there are still limitations to this answer - assuming Left and Right won't change even in nested Either .话虽如此,这个答案仍然存在限制 - 假设LeftRight即使在嵌套的Either中也不会改变。 Also, it's possible to design this differently with only one indirect case to wrap the Either type in another associated value - that might be more memory efficient since fewer frames get pushed onto the call stack at runtime.此外,可以仅使用一种indirect情况来设计这种不同的方式,以将Either类型包装在另一个关联值中——这可能更有效,因为在运行时将更少的帧推入调用堆栈。

I have posted a gist with attempts on this problem.我已经发布了一个关于这个问题的尝试的要点

If anyone has suggestions to improve this implementation, feel free to add to this.如果有人有改进此实现的建议,请随时添加。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM