OptionSetType和枚举

时间:2016-04-24 04:03:50

标签: swift enums optionsettype

我有一个名为enum ProgrammingLanguage { case Swift, Haskell, Scala } 的枚举:

Programmer

现在我有一个名为let favouriteLanguages: ProgrammingLanguage = .Swift 的类,其中包含以下属性:

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

看到程序员如何拥有几种最喜欢的语言,我认为写这样的东西会很好:

OptionSetType

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

ProgrammingLanguage不符合

  1. OptionSetType
  2. RawRepresentable
  3. case ProgrammingLanguage: String, OptionSetType { case Swift, Haskell, Scala }
  4. 当我看到 Raw Representable 错误时,我立即想到了枚举的关联类型。我希望能够打印枚举值,所以我将我的枚举签名更改为以下内容:

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

2 个答案:

答案 0 :(得分:17)

编辑:我对我以前的自我感到惊讶,因为当时没有提到这一点,但是......而不是试图强制其他值类型进入OptionSet协议(Swift 3已删除{{从名称开始,可能最好考虑使用这些类型的API,并在适当的地方使用Type个集合。

Set类型很奇怪。它们既是集合又不是集合 - 您可以从多个标志构造一个,但结果仍然是单个值。 (你可以做一些工作来找出一个等同于这样一个值的单一标志集合,但是根据它可能不是唯一的类型中的可能值。)

另一方面,能够拥有一个某些或多个独特的某些东西,对于API的设计非常重要。您是否希望用户说他们拥有多个收藏夹,或者强制执行只有一个?是多少"收藏"你想允许吗?如果用户声明多个收藏夹,是否应按用户特定顺序排列?这些都是OptionSet - 样式类型难以回答的问题,但如果您使用OptionSet类型或其他实际集合则更容易。

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

请参阅docs for OptionSetType

  

OptionSetSetAlgebraType的任何类型提供RawValue方便的一致性。

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

因此,如果您的原始值类型为BitwiseOperationsType,那么您运气不好 - 您无法获得设定的代数内容,因为String不支持按位操作。 ("有趣"在这里,如果你可以称之为,那就是你可以将String扩展到支持String,如果你的实现满足axioms,您可以使用字符串作为选项集的原始值。)

您在运行时的第二个语法错误,因为您已经创建了无限递归 - 从BitwiseOperationsType调用self.init(rawValue:)会保持锣直到它打击堆栈。

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

  1. 枚举的语义契约是它是一个封闭的集合。通过声明OptionSetType枚举,您说ProgrammingLanguage类型的值必须是ProgrammingLanguageSwiftScala之一,而不是还要别的吗。价值" Swift和Scala"不在那个集合中。

  2. Haskell的基础实现基于整数位域。 A" Swift和Haskell"值,(OptionSetType)实际上只是[.Swift, .Haskell]。如果您的原始值集不是位对齐的,则会导致问题。也就是说,如果.Swift.rawValue | .Haskell.rawValue.Swift.rawValue == 1 == 0b01,则按位或其中.Haskell.rawValue == 2 == 0b100b11 == 3相同。

  3. TLDR:如果你想要.Scala.rawValue一致,请声明一个结构。

    并使用OptionSetType来声明您的类型成员。

    并选择原始值,以便您希望与其他成员的可能(按位或)组合区别开来。

    static let

    保持值不同的好方法:使用上面的二进制文字语法,或者使用位移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)
    }
    

答案 1 :(得分:1)

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

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/