Enumerations. They’re not just for breakfast any more. Let me offer you a quick tour of this core Swift feature, with bonus dwarves.
Enumerations contain sets of named distinct elements. A hand enumeration might include thumb, index, middle, ring, and pinkie. A level enumeration could establish low, medium, and high values.
Enumerations need not make sense or be complete in any way. The set of armpit, sneeze, pixiedust, and penguin is as valid an enumeration as hearts, moons, stars, and clovers. (For the pedants among you, let me mention that over the years Lucky Charms included diamonds, horseshoes, balloons, trees, rainbows, and pots of gold, among others.)
Numeric Enumerations
In many programming languages, enumerated values are based on numeric values. Swift supports numeric enumerations by adding a type at the start of the declaration. This is an integer enumeration. It represents a common use-case for enumerations.
enum Dwarf: Int { case Bashful = 1, Doc, Dopey, Grumpy, Happy, Sleepy, Sneezy }
You can place all your cases on a single line or use multiple case declarations:
enum Dwarf: Int { case Bashful = 1 case Doc case Dopey case Grumpy case Happy case Sleepy case Sneezy }
Or mix and match as you like:
enum Dwarf: Int { case Bashful = 1, Doc, Dopey case Grumpy // the Grumpy stands alone case Happy, Sleepy, Sneezy }
Order matters. Only Bashful uses an explicit assignment. This leaves Swift to infer the remaining values, starting with 2 and continuing from there. Although Dwarf uses 1 as its first value, the default count starts at zero.
enum Level : Int {case Low, Medium, High} Level.High.rawValue // 2
However, you can set any value you like.
enum Charm : Int {case heart = 5, moon = 10, star = 15, clover = 20} Charm.star.rawValue // 15
Printing Dwarves
Create an instance by assignment. Since d uses a Dwarf type annotation, you can omit the enumeration prefix. There’s enough typing here for Swift to infer the rest.
var d : Dwarf = .Sleepy
Extend enumerations just like any other construct. This extension adds a printable value using the Streamable protocol. As Davide D points out, using a fixed array isn’t ideal for any enumerations that may change over time. I have a fair belief that a Cousin Oliver Dwarf won’t be showing up any time soon on the set of Snow White.
extension Dwarf : Streamable { func writeTo<Target : OutputStreamType>( inout target: Target) { print(["Bashful", "Doc", "Dopey", "Grumpy", "Happy", "Sleepy", "Sneezy"][ rawValue - 1], &target) } } println(d) // Sleepy
This extension adds a debug output. It displays both the raw enumeration value and the human-readable name. Debug output should always contain information that goes beyond “looks pretty” to include any material that’s relevant to a debugging scenario.
extension Dwarf: DebugPrintable { var debugDescription : String { return ["Bashful", "Doc", "Dopey", "Grumpy", "Happy", "Sleepy", "Sneezy"][ rawValue - 1] + "{\(rawValue)}" } } debugPrintln(d) // Sleepy{6}
Switches
Enumerations naturally lend themselves to switch statements. When working with enumerations, Swift switches must be exhaustive. Include every enumeration case or use default to mop up the remaining cases.
You can list several dwarves in a single switch case:
switch (d) { case .Bashful, .Doc, .Dopey, .Grumpy : println("G or lower") default: println("Dwarf is H or higher") }
You cannot, at least in the default implementation, apply dwarven ranges to your switch cases even though in this implementation, the dwarves are intrinsically numeric and ordered. (Please, I beg you, use that sentence out of context in your next coffee-shop conversation.)
for dwarf : Dwarf in [.Sneezy, .Bashful, .Doc] { switch (dwarf) { case Dwarf.Bashful...Dwarf.Grumpy : println("\(dwarf) : G or lower") default: println("\(dwarf) : H or higher") } }
You end up with this error: “Playground execution failed: error: could not find an overload for ‘…’ that accepts the supplied arguments” There’s an easy solution. Make your dwarves implement ~=. It takes two steps: conform to ForwardIndexType and add the ~= function at the top level. (Note: Davide D recommends implementing Comparable instead of Forward Index Type.)
// Step #1: ForwardIndexType / successor() extension Dwarf : ForwardIndexType { func successor() -> Dwarf { return Dwarf(rawValue:self.rawValue + 1)! } } // Step #2: Implement ~= func ~=(lhs: Range<Dwarf>, rhs : Dwarf) -> Bool { return (lhs.startIndex.rawValue..<lhs.endIndex.rawValue) ~= rhs.rawValue }
And now the switch statement works.
Non-numeric Enumerations
Enumerations don’t require intrinsic values. Here’s a super-basic enumeration that includes nothing but a type name and enumeration labels.
enum PepBoys {case Manny, Moe, Jack}
While dwarves have raw values:
d.rawValue // 6
Default enums do not. They have hash values
PepBoys.Manny.hashValue // 0 PepBoys.Moe.hashValue // 1 PepBoys.Jack.hashValue // 2
As you’ll discover, some enumerations in their default state offer neither.
Embedded Values
The Optionals type, widely used in Swift programming, embeds values using a simple enumeration:
enum Optional<T> {case None, case Some(T)}
You can embed values too. Although you can use (at this time) at most one kind of generic type, you can use as many non-generic types as you like. This next example doesn’t use generics at all. It creates a container that stores an int, a string, a double, or nothing at all:
enum Container { case NilContainer case IntContainer(Int) case StringContainer(String) case DoubleContainer(Double) }
This example creates several Container instances and prints them out. This initial implementation is both long and wordy.
for c in [Container.NilContainer, Container.IntContainer(42), Container.StringContainer("Hello!"), Container.DoubleContainer(M_PI)] { switch c { case .NilContainer: println("nil") case .DoubleContainer (let d) : println("Double: \(d)") case .StringContainer (let s) : println("String: \(s)") case .IntContainer (let i): println("Int: \(i)") } }
Raw values simplify this printing task.
extension Container { var rawValue: AnyObject? { switch self { case .NilContainer: return nil case .DoubleContainer (let d): return d case .StringContainer (let s): return s case .IntContainer (let i): return i } } }
Now, you can just print the raw value for each item:
for c in [Container.NilContainer, Container.IntContainer(42), Container.StringContainer("Hello!"), Container.DoubleContainer(M_PI)] { println(c.rawValue) }
The constructors are still cumbersome. With Optionals, you create an optional by passing a value:
Optional(23)
So why not extend Container to perform the same kind of infered typing for construction?
extension Container { init() {self = .NilContainer} init<T>(_ t : T){ switch t { case let value as Int : self = .IntContainer(value) case let value as Double: self = .DoubleContainer(value) case let value as String: self = .StringContainer(value) default: self = .NilContainer } } }
Now creating each type is super simple:
for c in [Container(), Container(63), Container("Sailor"), Container(M_PI_4)] { println(c.rawValue)}
If you extend the Container enumeration for Printable, you can skip the awkward c.rawValue and include a more informative presentation.
extension Container: Printable { var description : String { switch self { case .NilContainer: return "nil" case .IntContainer: return "Int<\(rawValue!)>" case .DoubleContainer: return "Double<\(rawValue!)>" case .StringContainer: return "String<\"\(rawValue!)\">" } } }
Now, just call:
for c in [Container(), Container(63), Container("Sailor"), Container(M_PI_4)] { println(c)}
And you’re set.
Enumerations and Playgrounds
One final note. Hey dev guys, if you are reading, why doesn’t the gutter display the streamable / printable / debug printable value when one’s available? Disappointed!
2 Comments
I know they’re just quick examples, but I disagree on how you did a few things:
• Use an hardcoded array for the cases properties: This makes the compiler unable to correct you if you add, remove or move any of the cases. The preferred (and more boring) way is to do a switch on self. If it is reused you can make a private computed property that does that and just call that.
• default: It’s true that it makes easier to cover remaining cases and in some complex enums it’s not really avoidable, but in this case you should avoid it because, as before, the compiler will not notice differences (apart from removing one of the cases explicitly specified)
• extension of ~=: if you want to do that it means that your data is ordered, and the dwarves were not. If you wanted something ordered you should’ve just made it conform to Comparable instead of overloading ~= so that you could’ve used the stdlib one (func ~=(pattern: Range, value: I) -> Bool)
• making Dwarf be an Int: I think we should use rawValues only for data that, actually, really is of that type. If you still wanted Dwarf to be ordered you could’ve added an orderIndex property to it (and make it Comparable)
I repeat, I know those were only quick examples but then people follow those 🙂 Also, this is not necessarily “the truth” so I wanted to know if you thought of these matters
In this example, the dwarves are definitely ordered and finite (ask Snow White) using underlying numbers. But (and here’s the very interesting part) they aren’t in real life. I hear that Doc gets his own trailer and that a production intern sorts Bashful’s M&Ms. Normally, I try to use exhaustive cases as you mentioned but I also wanted to make this quick and easy. Please refresh and see if you’re okay with the edits I’ve made.
If you have any additional and specific best practices you want to throw out there (that aren’t necessarily Dwarf related), please drop in a comment and thank you for reading!