【问题标题】:OptionSetType and enumsOptionSetType 和枚举
【发布时间】:2016-08-17 13:55:39
【问题描述】:

我有一个名为ProgrammingLanguage的枚举:

enum ProgrammingLanguage {
  case Swift, Haskell, Scala
}

现在我有一个名为 Programmer 的类,具有以下属性:

let favouriteLanguages: ProgrammingLanguage = .Swift

看到程序员可以拥有几种最喜欢的语言,我想写这样的东西会很好:

let favouriteLanguages: ProgrammingLanguage = [.Swift, .Haskell]

经过一番研究,我意识到我需要遵守OptionSetType,但在这样做时,我提出了以下3个错误:

ProgrammingLanguage 不符合

  1. SetAlgebraType
  2. OptionSetType
  3. RawRepresentable

当我看到 Raw Representable 错误时,我立即想到了枚举的关联类型。无论如何,我希望能够打印枚举值,所以我将我的枚举签名更改为以下内容:

case ProgrammingLanguage: String, OptionSetType {
  case Swift, Haskell, Scala
}

这使 2 个警告静音。但是我仍然有一个不符合协议SetAlgebraType 的问题。

经过一番反复试验,我发现枚举的关联类型为Int 已修复(这是有道理的,因为RawRepresentable 协议要求您实现签名init(rawValue: Int) 的初始化程序)。但是,我对此并不满意;我希望能够轻松获得枚举的字符串表示形式。

有人可以告诉我如何轻松做到这一点,为什么 OptionSetType 需要 Int 关联类型?

编辑:

以下声明编译正确,但运行时出错:

enum ProgrammingLanguage: Int, OptionSetType {
  case Swift, Scala, Haskell
}

extension ProgrammingLanguage {
  init(rawValue: Int) {
    self.init(rawValue: rawValue)
  }
}

let programmingLanguages: ProgrammingLanguage = [.Swift, .Scala]

【问题讨论】:

  • 我怀疑你能不能把enumOptionSetType结合起来,因为rawValue会互相干扰。
  • @vadian 它编译正确真的很有趣。运行时,应用程序在尝试初始化选项集的行崩溃,调试器显示在崩溃前调用了 31970 次 init。
  • 我猜代码会陷入无限循环(在编译时并不重要)或类似情况,然后在缓冲区溢出时崩溃。
  • 我相信你把associated valuesraw values混淆了:developer.apple.com/library/content/documentation/Swift/…

标签: swift enums optionsettype


【解决方案1】:

编辑:我对我以前的自我感到惊讶,因为当时没有提前说出来,但是......而不是试图强制其他值类型进入 OptionSet 协议(Swift 3 从名称中删除了 Type ),最好考虑使用这些类型的 API,并在适当的情况下使用 Set 集合。

OptionSet 类型很奇怪。它们都是集合而不是集合——您可以从多个标志构造一个,但结果仍然是单个值。 (您可以做一些工作来找出与此类值等效的单个标志集合,但根据类型中可能的值,它可能不是唯一的。)

另一方面,能够拥有一个something,或多个独特的something,对于API 的设计可能很重要。你想让用户说他们有多个最爱,还是强制要求只有一个?您要允许多少个“收藏夹”?如果用户声明了多个收藏夹,它们是否应该按照用户特定的顺序排列?这些都是在OptionSet 样式类型中难以回答的问题,但如果您使用Set 类型或其他实际集合,则容易得多。

这个答案的其余部分 a) 是旧的,使用 Swift 2 名称,并且 b) 假设您无论如何都在尝试实现 OptionSet,即使它对您的 API 来说是一个糟糕的选择...


docs for OptionSetType

RawValueBitwiseOperationsType 的任何类型提供方便的SetAlgebraType 一致性。

换句话说,您可以为同样采用RawRepresentable 的任何类型声明OptionSetType 一致性。但是,当且仅当关联的原始值类型符合 BitwiseOperationsType 时,您才能获得神奇的集合代数语法支持(通过运算符和 ArrayLiteralConvertible 一致性)。

所以,如果您的原始值类型是 String,那么您就很不走运了——您不会获得集合代数的东西,因为 String 不支持按位运算。 (这里的“有趣”的事情,如果你可以这么称呼它,就是你可以扩展String 以支持BitwiseOperationsType,如果你的实现满足axioms,你可以使用字符串作为选项集的原始值.)

您在运行时的第二个语法错误是因为您创建了一个无限递归 - 从 init(rawValue:) 调用 self.init(rawValue:) 会一直敲锣,直到它炸毁堆栈。

这可以说是一个错误 (please file it),您甚至可以在没有编译时错误的情况下尝试该错误。枚举不应该能够声明OptionSetType的一致性,因为:

  1. 枚举的语义契约是它是一个封闭集。通过声明ProgrammingLanguage 枚举,您是在说ProgrammingLanguage 类型的值必须是SwiftScalaHaskell 之一,而不是其他任何值。 “Swift and Scala”的值不在该集合中。

  2. OptionSetType 的底层实现基于整数位域。 “Swift 和 Haskell”值 ([.Swift, .Haskell]) 实际上只是 .Swift.rawValue | .Haskell.rawValue。如果您的一组原始值不是位对齐的,这会导致麻烦。也就是说,如果.Swift.rawValue == 1 == 0b01.Haskell.rawValue == 2 == 0b10,它们的按位或是0b11 == 3,与.Scala.rawValue 相同。

TLDR:如果你想要OptionSetType 一致性,声明一个结构。

并使用static let 声明您的类型的成员。

并选择您的原始值,以便您希望与其他成员的可能(按位或)组合不同的成员实际上是。

struct ProgrammingLanguage: OptionSetType {
    let rawValue: Int

    // this initializer is required, but it's also automatically
    // synthesized if `rawValue` is the only member, so writing it
    // here is optional:
    init(rawValue: Int) { self.rawValue = rawValue }

    static let Swift    = ProgrammingLanguage(rawValue: 0b001)
    static let Haskell  = ProgrammingLanguage(rawValue: 0b010)
    static let Scala    = ProgrammingLanguage(rawValue: 0b100)
}

保持你的值不同的好方法:使用上面的二进制文字语法,或者用位移一来声明你的值,如下所示:

    static let Swift    = ProgrammingLanguage(rawValue: 1 << 0)
    static let Haskell  = ProgrammingLanguage(rawValue: 1 << 1)
    static let Scala    = ProgrammingLanguage(rawValue: 1 << 2)

【讨论】:

  • 小记:init(rawValue: )好像有默认实现,这里可以省略。
  • @MartinR:是的,如果您唯一的实例属性是rawValue,您将免费获得init(rawValue:)。但是如果你添加更多的属性,你会失去协议一致性,这取决于有一个完全是init(rawValue:) 的初始化程序。无论如何,我发现把它放在那里很有帮助——它可以防止未来发生这种重大变化,并且它使协议一致性更加明确。
  • 您知道“扩展String 以支持BitwiseOperationsType”的任何解决方案吗?它会让生活变得更轻松...
  • 如果你真的想走这条路,请参阅我链接到的文档以了解 BitwiseOperations 协议(Swift 3 从名称中删除了 Type)。您需要一个 ^ 运算符,它接受两个字符串并返回一个可以视为它们之间的对称差异的值;例如a ^ a == .allZeroesa ^ .allZeroes == a。其他运营商依此类推。
【解决方案2】:

我想你可以简单地以现代方式实现它{^_^}。

protocol Option: RawRepresentable, Hashable, CaseIterable {}

extension Set where Element: Option {
    var rawValue: Int {
        var rawValue = 0
        for (index, element) in Element.allCases.enumerated() where contains(element) {
            rawValue |= (1 << index)
        }
        return rawValue
    }
}

...然后

enum ProgrammingLanguage: String, Option {
    case Swift, Haskell, Scala
}
typealias ProgrammingLanguages = Set<ProgrammingLanguage>

let programmingLanguages: ProgrammingLanguages = [.Swift, .Haskell]

参考:https://nshipster.com/optionset/

【讨论】:

  • 哇,你真是个天才。你让我今天一整天都感觉很好!非常感谢!
【解决方案3】:

您可以使用这个迷你库中的方法:https://github.com/allexks/Options

这样你需要做的就是让你的枚举符合CaseIterable然后你可以简单地写:

let favouriteLanguages: Options<ProgrammingLanguage> = [.Swift, .Haskell]

【讨论】:

    猜你喜欢
    • 2015-11-13
    • 1970-01-01
    • 2012-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多