我有一个名为enum ProgrammingLanguage {
case Swift, Haskell, Scala
}
的枚举:
Programmer
现在我有一个名为let favouriteLanguages: ProgrammingLanguage = .Swift
的类,其中包含以下属性:
let favouriteLanguages: ProgrammingLanguage = [.Swift, .Haskell]
看到程序员如何拥有几种最喜欢的语言,我认为写这样的东西会很好:
OptionSetType
经过一番研究后,我意识到我需要遵守SetAlgebraType
,但在这样做的过程中,我提出了以下3个错误:
ProgrammingLanguage不符合
OptionSetType
RawRepresentable
case ProgrammingLanguage: String, OptionSetType {
case Swift, Haskell, Scala
}
当我看到 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}}
答案 0 :(得分:17)
编辑:我对我以前的自我感到惊讶,因为当时没有提到这一点,但是......而不是试图强制其他值类型进入OptionSet
协议(Swift 3已删除{{从名称开始,可能最好考虑使用这些类型的API,并在适当的地方使用Type
个集合。
Set
类型很奇怪。它们既是集合又不是集合 - 您可以从多个标志构造一个,但结果仍然是单个值。 (你可以做一些工作来找出一个等同于这样一个值的单一标志集合,但是根据它可能不是唯一的类型中的可能值。)
另一方面,能够拥有一个某些或多个独特的某些东西,对于API的设计非常重要。您是否希望用户说他们拥有多个收藏夹,或者强制执行只有一个?是多少"收藏"你想允许吗?如果用户声明多个收藏夹,是否应按用户特定顺序排列?这些都是OptionSet
- 样式类型难以回答的问题,但如果您使用OptionSet
类型或其他实际集合则更容易。
这个答案的其余部分a)是旧的,使用Swift 2名称,b)假设您无论如何都要尝试实施Set
,即使它对您的API来说是一个糟糕的选择...
为
OptionSet
为SetAlgebraType
的任何类型提供RawValue
方便的一致性。
换句话说,您可以为也采用BitwiseOperationsType
的任何类型声明OptionSetType
一致性。但是,当且仅当关联的原始值类型符合RawRepresentable
时,才能获得神奇的集合 - 代数语法支持(通过运算符和ArrayLiteralConvertible
一致性)。
因此,如果您的原始值类型为BitwiseOperationsType
,那么您运气不好 - 您无法获得设定的代数内容,因为String
不支持按位操作。 ("有趣"在这里,如果你可以称之为,那就是你可以将String
扩展到支持String
,如果你的实现满足axioms,您可以使用字符串作为选项集的原始值。)
您在运行时的第二个语法错误,因为您已经创建了无限递归 - 从BitwiseOperationsType
调用self.init(rawValue:)
会保持锣直到它打击堆栈。
它可以说是一个错误(please file it),你甚至可以在没有编译时错误的情况下尝试这个错误。枚举不应该能够声明init(rawValue:)
一致性,因为:
枚举的语义契约是它是一个封闭的集合。通过声明OptionSetType
枚举,您说ProgrammingLanguage
类型的值必须是ProgrammingLanguage
,Swift
或Scala
之一,而不是还要别的吗。价值" Swift和Scala"不在那个集合中。
Haskell
的基础实现基于整数位域。 A" Swift和Haskell"值,(OptionSetType
)实际上只是[.Swift, .Haskell]
。如果您的原始值集不是位对齐的,则会导致问题。也就是说,如果.Swift.rawValue | .Haskell.rawValue
和.Swift.rawValue == 1 == 0b01
,则按位或其中.Haskell.rawValue == 2 == 0b10
与0b11 == 3
相同。
.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]