【问题标题】:Implementing an enum ForwardIndexType实现一个枚举 ForwardIndexType
【发布时间】:2014-11-01 07:04:13
【问题描述】:

我一直在努力为枚举正确实施ForwardIndexType 协议,特别是处理结束情况(即没有后继的最后一项)。 Swift 语言书并没有真正涵盖这个协议。

这是一个简单的例子

enum ThreeWords : Int, ForwardIndexType {
    case one=1, two, three

    func successor() ->ThreeWords {
            return ThreeWords(rawValue:self.rawValue + 1)!
    }
}

successor() 函数将返回下一个枚举器值,除了最后一个元素,它将失败并出现异常,因为.three 之后没有值

ForwardTypeProtocol 不允许successor() 返回条件值,所以似乎没有办法表明没有后继者。

现在在 for 循环中使用它来迭代枚举的所有可能值的封闭范围,最终情况会遇到问题:

for word in ThreeWords.one...ThreeWords.three {
    print("   \(word.rawValue)")
}
println()

//Crashes with the error:

fatal error: unexpectedly found nil while unwrapping an Optional value

在执行 for 循环中的语句之前,Swift 莫名其妙地调用了范围结束值的 successor() 函数。如果范围半开ThreeWords.one..<ThreeWords.three,则代码正确执行,打印1 2

如果我修改后继函数,使其不会像这样尝试创建大于.three 的值

   func successor() ->ThreeWords {
        if self == .three {
            return .three
        } else {
            return ThreeWords(rawValue:self.rawValue + 1)!
        }
    }

那么 for 循环不会崩溃,但它也错过了最后一次迭代,打印的结果就像范围是半开一样1 2

我的结论是swift的for循环迭代有一个bug;它不应该在封闭范围的结束值上调用successor()。其次,ForwardIndexType 应该能够返回一个可选项,以便能够表示某个值没有后继。

有人在这个协议上取得了更大的成功吗?

【问题讨论】:

    标签: swift enums


    【解决方案1】:

    确实,successor 似乎会在最后一个值上被调用。

    您可能希望file a bug,但要解决此问题,您可以简单地添加一个哨兵值作为继任者。

    【讨论】:

      【解决方案2】:

      看来,... 运营商

      func ...<Pos : ForwardIndexType>(minimum: Pos, maximum: Pos) -> Range<Pos>
      

      致电maximum.successor()。它构造Range&lt;T&gt;like

      Range(start: minimum, end: maximum.successor())
      

      所以,如果你想使用enum 作为Range.Index,你必须定义最后一个值的下一个

      enum ThreeWords : Int, ForwardIndexType {
          case one=1, two, three
          case EXHAUST
      
          func successor() ->ThreeWords {
              return ThreeWords(rawValue:self.rawValue + 1) ?? ThreeWords.EXHAUST
          }
      }
      

      【讨论】:

      • 这很糟糕,因为现在我所有的枚举 switch 语句都感染了所需的默认情况
      【解决方案3】:

      这是一个老问题,但我想总结一些事情并发布另一个可能的解决方案。

      正如@jtbandes 和@rintaro 已经声明的那样,使用start...end 运算符创建的封闭范围在内部使用start..&lt;end.successor() 创建 Afaik 这是 Swift 中的故意行为。

      在许多情况下,您也可以使用 Interval,在您考虑使用 Range 或 Swift 默认声明 Range 的地方。这里的重点是 intervals 不是集合。

      因此,间隔

      不可能
      for word in ThreeWords.one...ThreeWords.three {...}
      

      =================

      对于下面我假设上面的 sn-p 只是一个调试用例来交叉检查值。

      要声明一个 Interval,您需要明确指定类型。 HalfOpenInterval (..&lt;)ClosedInterval (...)

      var interval:ClosedInterval = ThreeWords.one...ThreeWords.four
      

      这需要您进行枚举Comparable。虽然Int已经是Comparable了,但还是需要添加到继承列表中

      enum ThreeWords : Int, ForwardIndexType, Comparable {
          case one=1, two, three, four
      
          func successor() ->ThreeWords {
              return ThreeWords(rawValue:self.rawValue + 1)!
          }
      }
      

      最后枚举需要符合Comparable。这是一种通用方法,因为您的枚举也符合协议RawRepresentable

      func <<T: RawRepresentable where T.RawValue: Comparable>(lhs: T, rhs: T) -> Bool {
          return lhs.rawValue < rhs.rawValue
      }
      

      就像我写的那样,您不能再循环遍历它,但是您可以使用开关进行快速交叉检查:

      var interval:ClosedInterval = ThreeWords.one...ThreeWords.four
      switch(ThreeWords.four) {
          case ThreeWords.one...ThreeWords.two:
              print("contains one or two")
          case let word where interval ~= word:
              print("contains: \(word) with raw value: \(word.rawValue)")
          default:
              print("no case")
      }
      

      打印“包含:四个原始值:4

      【讨论】:

        猜你喜欢
        • 2015-08-22
        • 1970-01-01
        • 1970-01-01
        • 2010-09-11
        • 2015-05-30
        • 1970-01-01
        • 2014-08-18
        • 1970-01-01
        相关资源
        最近更新 更多