选项集类型和枚举

19

我有一个名为ProgrammingLanguage的枚举:

enum ProgrammingLanguage {
  case Swift, Haskell, Scala
}

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

let favouriteLanguages: ProgrammingLanguage = .Swift

看到程序员可能有多个喜爱的语言,我想写点类似这样的东西:

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

经过一番调查,我意识到需要符合 OptionSetType,但是这样做,出现了以下3个错误:

编程语言不符合

  1. SetAlgebraType
  2. OptionSetType
  3. RawRepresentable

当我看到原始可表示性(Raw Representable)的错误时,我立刻想到了枚举的关联类型。无论如何,我都想能够打印枚举值,因此我将枚举签名更改为以下内容:

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

这样做消除了其中的两个警告。但我仍然面临一个问题,即我不符合协议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]

1
我怀疑你无法将 enumOptionSetType 结合起来,因为它们的 rawValue 相互干扰。 - vadian
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Kelvin Lau
我猜测代码陷入了一个无限循环(在编译时并不重要)或类似的情况,然后在缓冲区溢出时崩溃。 - vadian
我认为你将associated valuesraw values混淆了:https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html#//apple_ref/doc/uid/TP40014097-CH12-ID148 - Sajjon
相关 https://twitter.com/johnsundell/status/906097785883242496 - mfaani
3个回答

19

编辑:我感到惊讶的是,我以前没有在当时就直截了当地说出这个问题,但是……与其试图将其他值类型强制转换为OptionSet协议(Swift 3已从名称中删除Type),考虑在您使用这些类型的API中使用Set集合可能更好。

OptionSet类型很奇怪。它们既是集合又不是集合——您可以从多个标志构建一个,但结果仍然是单个值。 (您可以进行一些工作来确定等效于该值的单独标志集合,但根据类型中可能的值,它可能不是唯一的。)

另一方面,拥有一个某物或多个唯一的某物对API的设计可能很重要。您是否希望用户说他们有多个最喜欢的,还是强制只有一个?您想允许多少“收藏”?如果用户声明多个收藏夹,应该按用户特定的顺序排名吗?这些都是难以在OptionSet-style类型中回答的问题,但如果您使用Set类型或其他实际集合,则会容易得多。

这个答案的其余部分a)使用Swift 2名称,b)即使它对于您的API是一个糟糕的选择,也假定您仍然要实现OptionSet......


请参阅OptionSetType文档

 

为任何其原始值为BitwiseOperationsType的类型提供方便的SetAlgebraType一致性。

换句话说,您可以声明OptionSetType一致性,以适用于还采用RawRepresentable的任何类型。但是,仅当您关联的原始值类型是符合BitwiseOperationsType的才会获得魔术集代数语法支持(通过运算符和ArrayLiteralConvertible一致性)。

因此,如果您的原始值类型为String,那么您就没有运气了——您不会获得集合代数东西,因为String不支持按位运算。(这里的“有趣”之处,如果您愿意这样说的话,就是您可以扩展String以支持BitwiseOperationsType,如果您的实现满足公理,则可以将字符串用作选项集的原始值。)

第二个句法在运行时出现错误,因为您创建了一个无限递归——从init(rawValue:)调用self.init(rawValue:)会一直继续直到它吹掉堆栈。

这可能是一个错误(请报告),即使没有编译时错误,枚举也不应该能够声明OptionSetType一致性,因为:

  1. 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)
    

3
小注:似乎已经有init(rawValue: )的默认实现,所以这里可以省略它。 - Martin R
2
@MartinR: 如果您的唯一实例属性是 rawValue,那么您将免费获得 init(rawValue:)。但是,如果您添加更多属性,则会失去依赖于存在初始化程序的协议符合性,即确切的 init(rawValue:)。我发现把它放在那里很有帮助,这样可以防止未来出现此类破坏性变化,并且可以使协议符合性更加明确。 - rickster
你是否知道任何关于“扩展String以支持BitwiseOperationsType的解决方案?这将使生活变得更加轻松... - mfaani
如果你真的想走这条路,可以查看我链接的 BitwiseOperations 协议文档(Swift 3 中从名称中删除了 Type)。你需要一个 ^ 运算符,它接受两个字符串并返回一个可以被视为它们之间对称差异的值;例如,a ^ a == .allZeroesa ^ .allZeroes == a。其他运算符也是如此。 - rickster

12

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

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/


哇,你真是个天才。你让我的一天变得美好!非常感谢你! - Tommy

0

你可以使用这个小型库中提供的方法:https://github.com/allexks/Options

这样,你只需要让你的枚举符合 CaseIterable 协议,然后就可以简单地编写:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接