This is my research notebook. I'm an OSX / iOS indie developer. After 8 years of Objective-C I really enjoy Swift nowadays. Trying to publish all my research on Development, Swift & other technologies here.
BENEDIKT TERHECHTE
Sat, 23 Apr 2016 #
Enums are a beautiful way of structuring information in Swift. Sometimes you find yourself initializing enums from raw values, maybe because the values were intermittendly stored somewhere else, say in the NSUserDefaults
:
enum Device: String {
case phone, tablet, watch
}
let aDevice = Device(rawValue: "phone")
print(aDevice)
Prints Optional(main.Device.phone)
1 The Problem
As soon as you're using associated values in your enums, this doesn't work anymore:
enum Example {
case Factory(workers: Int)
case House(street: String)
}
Swift can't create an instance of Example
because the two cases, Factory and House have different associated types (the workers
integer and the street
string. Each invocation of Example
requires different parameters, so this can't be generalized.
However, that's not the case when your associated types all match up:
enum Device {
case phone(name: String, screenSize: CGSize)
case watch(name: String, screenSize: CGSize)
case tablet(name: String, screenSize: CGSize)
}
In thise case, all the associated types
are the same. There're a myriad of other ways to model this, but I found the device enum
to be a concise example for what I'm about to explain. Even though every Device
invocation is the same now, you still can't just call it with some sort of raw value and expect the correct type. Instead, what you have to do is perform a match in order to create the correct instance:
import Foundation
enum Device {
case phone(name: String, screenSize: CGSize)
case watch(name: String, screenSize: CGSize)
case tablet(name: String, screenSize: CGSize)
static func fromDefaults(rawValue: String, name: String, screenSize: CGSize) -> Device? {
switch rawValue {
case "phone": return Device.phone(name: name, screenSize: screenSize)
case "watch": return Device.watch(name: name, screenSize: screenSize)
case "tablet": return Device.tablet(name: name, screenSize: screenSize)
default: return nil
}
}
}
let b = Device.fromDefaults("phone", name: "iPhone SE", screenSize: CGSize(width: 640, height: 1136))
print(b)
prints Optional(main.Device.phone("iPhone SE", (640.0, 1136.0)))
This looks ok, but it is already a bit of repetitive code. Once you develop more than just three enum cases / two associated types, this will quickly get out of hand.
enum Vehicle {
case .car(wheels: Int, capacity: Int, weight: Int, length: Int, height: Int, width: Int, color: Int, name: Int, producer: Int, creation: NSDate, amountOfProducedUnits: Int)
case .ship(wheels: Int, capacity: Int, weight: Int, length: Int, height: Int, width: Int, color: Int, name: Int, producer: Int, creation: NSDate, amountOfProducedUnits: Int)
case .yacht(wheels: Int, capacity: Int, weight: Int, length: Int, height: Int, width: Int, color: Int, name: Int, producer: Int, creation: NSDate, amountOfProducedUnits: Int)
case .truck(wheels: Int, capacity: Int, weight: Int, length: Int, height: Int, width: Int, color: Int, name: Int, producer: Int, creation: NSDate, amountOfProducedUnits: Int)
case .motorbike(wheels: Int, capacity: Int, weight: Int, length: Int, height: Int, width: Int, color: Int, name: Int, producer: Int, creation: NSDate, amountOfProducedUnits: Int)
case .helicopter(wheels: Int, capacity: Int, weight: Int, length: Int, height: Int, width: Int, color: Int, name: Int, producer: Int, creation: NSDate, amountOfProducedUnits: Int)
case .train(wheels: Int, capacity: Int, weight: Int, length: Int, height: Int, width: Int, color: Int, name: Int, producer: Int, creation: NSDate, amountOfProducedUnits: Int)
...
}
I think you get my point.
2 The Solution
So.. how do we solve this? Interestingly, there's a quirky similarity between the initializer of an associated type and a closure. Take this code:
enum Example {
case test(x: Int)
}
let exampleClosure = Example.test
What is the type of exampleClosure
here? The type is (Int) -> Example
. That's right, calling an associated value enum
case without any parameters will yield a closure that, when called with the correct types, will return an instance of said type.
This means that, the following is valid, working Swift:
enum Fruit {
case apple(amount: Int)
case orange(amount: Int)
}
let appleMaker = Fruit.apple
let firstApple = appleMaker(amount: 10)
let secondApple = appleMaker(amount: 12)
print(firstApple, secondApple)
apple(10) apple(12)
So, how would that help us simplify the gross code duplication problem above? Have a look:
import Foundation
enum Device {
case phone(name: String, screenSize: CGSize)
case watch(name: String, screenSize: CGSize)
case tablet(name: String, screenSize: CGSize)
private static var initializers: [String: (name: String, screenSize: CGSize) -> Device] = {
return ["phone": Device.phone, "watch": Device.watch, "tablet": Device.tablet]
}()
static func fromDefaults(rawValue: String, name: String, screenSize: CGSize) -> Device? {
return Device.initializers[rawValue]?(name: name, screenSize: screenSize)
}
}
let iPhone = Device.fromDefaults("phone", name: "iPhone SE", screenSize: CGSize(width: 640, height: 1134))
print(iPhone)
Optional(main.Device.phone("iPhone SE", (640.0, 1134.0)))
So, let's try to figure out what happened here. We have a new property initializers
on our Device
. It's a Dictionary
of type [String: (name: String, screenSize: CGSize) -> Device]
. I.e. something that maps from a String
key to a closure with the same type as our Device
cases. The dictionary contains the initializers of each of our distinct cases, simply by using the same trick as above, just handing in the closure: phone:Device.phone
The fromDefaults
function, then, only has to know the key of the device we'd like to create, and it can call the appropriate closure. This leads to a much shorter implementation, especially for bigger enums (like our Vehicle example above). As you can see, creating a Device
instance is then as simple as:
Device.initializers["phone"]?(name: "iPhone 5", screenSize: CGSize(width: 640, height: 1134)))
Just as with raw values, in case there is no enum
case phone we'd just get an empty optional back.
This solution isn't perfect of course. You still have to have the initializers
dictionary, however it will be much less repetitve than having to match
over all cases manually.
Finally, I suppose it goes without saying that the code above ignores an important best practice to be concise and to be able to concentrate on the task at hand; Nevertheless: having stringified code like Device.initializers["phone"]
is not the best way to write this. Instead, those keys should be properly defined somewhere else.
Related Articles
Thu, 3 April 2018 : Expanding Swift's Reach Wed, 10 Jan 2018 : Useful Optional Extensions Sun, 10 Dec 2017 : Patterns for Working With Associated Types Sun, 8 Oct 2017 : Taming SourceKitService for Less Xcode Memory Consumption Sat, 30 Sep 2017 : Value Types for Simple Difference Detection Fri, 15 Jul 2016 : Data in Swift 3 parsing a Doom WAD File Thu, 28 Apr 2016 : SwiftWatch Sat, 23 Apr 2016 : Raw value initializers for enums with associated types Thu, 14 Apr 2016 : Force optionals in multi-unwrapped "guard let" or "if let" Tue, 29 Mar 2016 : Three tips for concise Swift using the Guard statement Tue, 2 Feb 2016 : Hirundo: Comfortably follow Swift Mailing Lists on OSX Tue, 8 Dec 2015 : Swift Package Manager: Create and Use a X11 package on Linux Mon, 30 Nov 2015 : Reduce all the things Sat, 24 Oct 2015 : The Swift Reflection API and what you can do with it Sat, 17 Oct 2015 : Advanced & Practical Enum usage in Swift Wed, 30 Sep 2015 : Getting your iPhone 6s Chip Foundry from Swift Tue, 25 Aug 2015 : Optional throw via try? in Swift 2 beta 6 Thu, 20 Aug 2015 : Match Me if you can: Swift Pattern Matching in Detail. Sun, 19 Jul 2015 : Tuples in Swift, Advanced Usage and Best Practices Fri, 19 Jun 2015 : Using try / catch in Swift with asynchronous closures Wed, 17 Jun 2015 : Generic method overloading by protocol in Swift Fri, 13 Jun 2014 : Swift optionals made simple Sun, 8 Jun 2014 : Creating a Swift syntax extension: the Lisp 'cond' function Tue, 21 Jan 2014 : Debugging entitlement issues in Maps, iCloud, In-App, Keychain, or GameCenter Tue, 29 Jan 2013 : Use VIM as an Xcode alternative Wed, 7 Dec 2011 : Fast NSDictionary traversal in Objective-C
{:description
"Once you add associated types to an enum the task of creating instances quickly becomes very repetitive. See how a simpl",
:keyword-tags (:swift :cocoa :ios),
:projects ({:project "Sarbatka", :link "/electronic-music.html"}),
:postlist
({:title "Blogstrapped.",
:url "/2011/12/01/blogstrapped/",
:tags "entrepeneurship blog",
:keyword-tags (:entrepeneurship :blog),
:date "Thu, 1 Dec 2011",
:keywords "entrepeneurship videro fear unknown stylemac",
:keyword-keywords
(:entrepeneurship :videro :fear :unknown :stylemac)}
{:title "Fast NSDictionary traversal in Objective-C",
:url "/2011/12/07/fast-nsdictionary-traversal-in-objective-c/",
:tags "objective-c ios cocoa",
:keyword-tags (:objective-c :ios :cocoa),
:date "Wed, 7 Dec 2011"}
{:title "How the iPad can improve focus",
:url "/2011/12/09/how-the-ipad-can-improve-focus/",
:tags "opinion",
:keyword-tags (:opinion),
:date "Fri, 9 Dec 2011"}
{:title "Use VIM as an Xcode alternative",
:url "/2013/01/29/use-vim-as-xcode-alternative-ios-mac-cocoa/",
:tags "cocoa objective-c ios",
:keyword-tags (:cocoa :objective-c :ios),
:date "Tue, 29 Jan 2013"}
{:title "Now Running Clojure",
:url "/2014/01/20/now-running-clojure/",
:tags "clojure blog",
:keyword-tags (:clojure :blog),
:date "Mon, 20 Jan 2014"}
{:title
"Debugging entitlement issues in Maps, iCloud, In-App, Keychain, or GameCenter",
:url
"/2014/01/21/debugging-entitlement-maps-icloud-gamecenter-issues/",
:tags "ios cocoa",
:keyword-tags (:ios :cocoa),
:date "Tue, 21 Jan 2014",
:keywords "ios cocoa entitlements",
:keyword-keywords (:ios :cocoa :entitlements)}
{:title
"Clojure/Enlive Static Site Generator that keeps your HTML intact",
:url
"/2014/01/22/clojure-enlive-static-site-generator-that-keeps-html-intact/",
:tags "blog clojure",
:keyword-tags (:blog :clojure),
:date "Wed, 22 Jan 2014",
:keywords "clojure static site generator jekyll html enlive",
:keyword-keywords
(:clojure :static :site :generator :jekyll :html :enlive)}
{:title "Finding a good URL Partitioning Scheme for PostgreSQL",
:url "/2014/01/24/finding-url-partitioning-scheme-postgres/",
:tags "postgresql clojure",
:keyword-tags (:postgresql :clojure),
:date "Fri, 24 Jan 2014",
:keywords "clojure postgresql partitioning scheme",
:keyword-keywords (:clojure :postgresql :partitioning :scheme)}
{:title "An Emacs Lisp tooling tutorial, writing a bulk mailer",
:url "/2014/01/29/emacs-lisp-tooling-tutorial-writing-bulk-mailer/",
:tags "emacs",
:keyword-tags (:emacs),
:date "Wed, 29 Jan 2014",
:keywords
"emacs lisp bulk mailer tutorial email vim vimscript evil",
:keyword-keywords
(:emacs :lisp :bulk :mailer :tutorial :email :vim :vimscript :evil)}
{:title
"Creating a Swift syntax extension: the Lisp 'cond' function",
:url "/2014/06/08/writing-simple-syntax-extensions-in-swift/",
:tags "swift ios cocoa",
:keyword-tags (:swift :ios :cocoa),
:date "Sun, 8 Jun 2014",
:keywords
"clojure lisp swift cond syntax macro extension cocoa ios feature",
:keyword-keywords
(:clojure
:lisp
:swift
:cond
:syntax
:macro
:extension
:cocoa
:ios
:feature)}
{:title "Swift optionals made simple",
:url "/2014/06/13/swift-optionals-made-simple/",
:tags "swift ios cocoa",
:keyword-tags (:swift :ios :cocoa),
:date "Fri, 13 Jun 2014",
:keywords
"lisp swift optional scala simple optionals switch chaining feature",
:keyword-keywords
(:lisp
:swift
:optional
:scala
:simple
:optionals
:switch
:chaining
:feature)}
{:title "Generic method overloading by protocol in Swift",
:url "/2015/06/17/swift-method-overloading-by-protocol/",
:tags "swift ios cocoa",
:keyword-tags (:swift :ios :cocoa),
:date "Wed, 17 Jun 2015",
:keywords
"swift optional simple overloading method protocol extensions generics feature",
:keyword-keywords
(:swift
:optional
:simple
:overloading
:method
:protocol
:extensions
:generics
:feature)}
{:title "Using try / catch in Swift with asynchronous closures",
:url "/2015/06/19/swift-try-catch-asynchronous-closures/",
:tags "swift ios",
:keyword-tags (:swift :ios),
:date "Fri, 19 Jun 2015",
:keywords "swift try catch errortype closure async result feature",
:keyword-keywords
(:swift :try :catch :errortype :closure :async :result :feature)}
{:title "Debugging advanced compilation errors in ClojureScript",
:url "/2015/07/02/debugging-clojurescript-advanced-compilation/",
:tags "clojure",
:keyword-tags (:clojure),
:date "Thu, 2 Jul 2015",
:keywords "debugging clojure clojurescript externs",
:keyword-keywords (:debugging :clojure :clojurescript :externs)}
{:title "Tuples in Swift, Advanced Usage and Best Practices",
:url "/2015/07/19/tuples-swift-advanced-usage-best-practices/",
:tags "swift",
:keyword-tags (:swift),
:date "Sun, 19 Jul 2015",
:keywords "swift tuples generics feature",
:keyword-keywords (:swift :tuples :generics :feature)}
{:title "Match Me if you can: Swift Pattern Matching in Detail.",
:url "/2015/08/20/swift-pattern-matching-in-detail/",
:tags "swift ios cocoa",
:keyword-tags (:swift :ios :cocoa),
:date "Thu, 20 Aug 2015",
:keywords
"feature lisp swift optional scala simple optionals switch chaining for pattern matching clojure haskell",
:keyword-keywords
(:feature
:lisp
:swift
:optional
:scala
:simple
:optionals
:switch
:chaining
:for
:pattern
:matching
:clojure
:haskell)}
{:title "Optional throw via try? in Swift 2 beta 6",
:url "/2015/08/25/optional-throw-swift/",
:tags "swift",
:keyword-tags (:swift),
:date "Tue, 25 Aug 2015",
:keywords
"swift error throw result either rethrow try syntax swift2",
:keyword-keywords
(:swift
:error
:throw
:result
:either
:rethrow
:try
:syntax
:swift2)}
{:title "Getting your iPhone 6s Chip Foundry from Swift",
:url "/2015/09/30/getting-iphone6s-foundry-from-swift/",
:tags "swift",
:keyword-tags (:swift),
:date "Wed, 30 Sep 2015",
:keywords
"swift iphone6s iphone tsmc samsung gestalt private api foundation",
:keyword-keywords
(:swift
:iphone6s
:iphone
:tsmc
:samsung
:gestalt
:private
:api
:foundation)}
{:title "Advanced & Practical Enum usage in Swift",
:url "/2015/10/17/advanced-practical-enum-examples/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Sat, 17 Oct 2015",
:keywords
"feature swift enum algebraic caseclass union case switch pattern simple practical advanced example",
:keyword-keywords
(:feature
:swift
:enum
:algebraic
:caseclass
:union
:case
:switch
:pattern
:simple
:practical
:advanced
:example)}
{:title "The Swift Reflection API and what you can do with it",
:url "/2015/10/24/swift-reflection-api-what-you-can-do/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Sat, 24 Oct 2015",
:keywords
"feature swift reflection struct class displayType mirror api reflecting any anyobject",
:keyword-keywords
(:feature
:swift
:reflection
:struct
:class
:displayType
:mirror
:api
:reflecting
:any
:anyobject)}
{:title "Reduce all the things",
:url "/2015/11/30/reduce-all-the-things/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Mon, 30 Nov 2015",
:keywords
"feature swift reduce map filter group partition split interpose chunk functional programming flatMap",
:keyword-keywords
(:feature
:swift
:reduce
:map
:filter
:group
:partition
:split
:interpose
:chunk
:functional
:programming
:flatMap)}
{:title
"Swift Package Manager: Create and Use a X11 package on Linux",
:url "/2015/12/08/swift-ubuntu-x11-window-app/",
:tags "swift linux",
:keyword-tags (:swift :linux),
:date "Tue, 8 Dec 2015",
:keywords "linux x11 swift libx11 xserver xorg",
:keyword-keywords (:linux :x11 :swift :libx11 :xserver :xorg)}
{:title "Hirundo: Comfortably follow Swift Mailing Lists on OSX",
:url "/2016/02/02/hirundo-mac-app-swift-mailing-lists/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Tue, 2 Feb 2016",
:keywords
"swift mac cocoa mailing list swift-dev swift-eveolution swift-users reading macosx",
:keyword-keywords
(:swift
:mac
:cocoa
:mailing
:list
:swift-dev
:swift-eveolution
:swift-users
:reading
:macosx)}
{:title "Three tips for concise Swift using the Guard statement",
:url "/2016/03/29/three-tips-for-clean-swift-code/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Tue, 29 Mar 2016",
:keywords
"swift mac cocoa guard let enum pattern matching patterns",
:keyword-keywords
(:swift :mac :cocoa :guard :let :enum :pattern :matching :patterns)}
{:title "Using Git Hooks to prevent commiting test code",
:url "/2016/04/04/prevent-accidental-test-code-commits/",
:tags "git",
:keyword-tags (:git),
:date "Mon, 4 Apr 2016",
:keywords "git hook commit debug test code",
:keyword-keywords (:git :hook :commit :debug :test :code)}
{:title
"Force optionals in multi-unwrapped \"guard let\" or \"if let\"",
:url
"/2016/04/14/force-optionals-in-guard-or-let-optional-binding/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Thu, 14 Apr 2016",
:keywords
"swift guard let unwrap bind binding unwrapped optional some none optionals",
:keyword-keywords
(:swift
:guard
:let
:unwrap
:bind
:binding
:unwrapped
:optional
:some
:none
:optionals)}
{:title "Raw value initializers for enums with associated types",
:url "/2016/04/23/associated-types-enum-raw-value-initializers/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Sat, 23 Apr 2016",
:keywords
"swift optional enum raw value initializers associated type",
:keyword-keywords
(:swift
:optional
:enum
:raw
:value
:initializers
:associated
:type)}
{:title "SwiftWatch",
:url "/2016/04/28/swiftwatch/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Thu, 28 Apr 2016",
:keywords
"swift hackernews reddit designernews lamernews socialnews swiftlang programming community post vote comment",
:keyword-keywords
(:swift
:hackernews
:reddit
:designernews
:lamernews
:socialnews
:swiftlang
:programming
:community
:post
:vote
:comment)}
{:title "Data in Swift 3 parsing a Doom WAD File",
:url "/2016/07/15/swift3-nsdata-data/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Fri, 15 Jul 2016",
:keywords "swift doom wad lumps data nsdata swift3 binary bytes",
:keyword-keywords
(:swift :doom :wad :lumps :data :nsdata :swift3 :binary :bytes)}
{:title "Value Types for Simple Difference Detection",
:url "/2017/09/30/value-types-for-simple-difference-detection/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Sat, 30 Sep 2017",
:keywords
"swift value types uitableview uicollectionview valuetypes struct class equatable tuple",
:keyword-keywords
(:swift
:value
:types
:uitableview
:uicollectionview
:valuetypes
:struct
:class
:equatable
:tuple)}
{:title "Taming SourceKitService for Less Xcode Memory Consumption",
:url "/2017/10/08/taming-sourcekitd/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Sun, 8 Oct 2017",
:keywords "xcode sourcekit swift SourceKitService",
:keyword-keywords (:xcode :sourcekit :swift :SourceKitService)}
{:title "Patterns for Working With Associated Types",
:url "/2017/12/10/patterns-for-working-with-associated-types/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Sun, 10 Dec 2017",
:keywords
"swift protocol protocols associated associatedtype typealias pattern pat",
:keyword-keywords
(:swift
:protocol
:protocols
:associated
:associatedtype
:typealias
:pattern
:pat)}
{:title "Useful Optional Extensions",
:url "/2018/01/10/optional-extensions/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Wed, 10 Jan 2018",
:keywords "swift protocol optional optionals extensions",
:keyword-keywords
(:swift :protocol :optional :optionals :extensions)}
{:title "Expanding Swift's Reach",
:url "/2018/05/03/expanding-swifts-reach/",
:tags "swift linux",
:keyword-tags (:swift :linux),
:date "Thu, 3 April 2018",
:keywords "swift linux server opensource",
:keyword-keywords (:swift :linux :server :opensource)}),
:tags
"associated, benedikt, c, clojure, clojurescript, cocoa, enum, html, initializers, ios, javascript, mac, objective-c, optional, photodesk, raw, research, stylemac, swift, terhechte, type, value",
:type :post,
:keywords
"swift optional enum raw value initializers associated type",
:title "Raw value initializers for enums with associated types",
:author "Benedikt Terhechte",
:summary
"Once you add associated types to an enum the task of creating instances quickly becomes very repetitive. See how a simple trick can greatly simplify this",
:feature-image
"/img-content/2016-04-23-associated-types-enum-raw-value-initializers-feature-image.jpg",
:categories
({:tag "blog", :url "/tags/blog/index.html", :count 3}
{:tag "clojure", :url "/tags/clojure/index.html", :count 4}
{:tag "cocoa", :url "/tags/cocoa/index.html", :count 20}
{:tag "emacs", :url "/tags/emacs/index.html", :count 1}
{:tag "entrepeneurship",
:url "/tags/entrepeneurship/index.html",
:count 1}
{:tag "git", :url "/tags/git/index.html", :count 1}
{:tag "ios", :url "/tags/ios/index.html", :count 21}
{:tag "linux", :url "/tags/linux/index.html", :count 2}
{:tag "objective-c", :url "/tags/objective-c/index.html", :count 2}
{:tag "opinion", :url "/tags/opinion/index.html", :count 1}
{:tag "postgresql", :url "/tags/postgresql/index.html", :count 1}
{:tag "swift", :url "/tags/swift/index.html", :count 23}),
:url "/2016/04/23/associated-types-enum-raw-value-initializers/",
:blog-index nil,
: -*- org-export-babel-evaluate "nil -*-",
:watching nil,
:site-title "Appventure",
:keyword-keywords
(:swift :optional :enum :raw :value :initializers :associated :type)}
[{:keyword-tags (:swift :cocoa :ios),
:tags "swift cocoa ios",
:date "Sat, 23 Apr 2016",
:footnotes nil,
:meta
{:feature-image
"/img-content/2016-04-23-associated-types-enum-raw-value-initializers-feature-image.jpg",
: -*- org-export-babel-evaluate "nil -*-",
:title "Raw value initializers for enums with associated types",
:keyword-tags (:swift :cocoa :ios),
:tags "swift cocoa ios",
:keyword-keywords
(:swift
:optional
:enum
:raw
:value
:initializers
:associated
:type),
:keywords
"swift optional enum raw value initializers associated type",
:summary
"Once you add associated types to an enum the task of creating instances quickly becomes very repetitive. See how a simple trick can greatly simplify this",
:description
"Once you add associated types to an enum the task of creating instances quickly becomes very repetitive. See how a simpl"},
:content
"<!-- #+feature-image: /img-content/2016-04-23-associated-types-enum-raw-value-initializers-feature-image.jpg -->\n\n<p>\n<a href=\"https://appventure.me/2015/10/17/advanced-practical-enum-examples/\">Enums</a> are a beautiful way of structuring information in Swift. Sometimes you find yourself initializing enums from raw values, maybe because the values were intermittendly stored somewhere else, say in the <code>NSUserDefaults</code>:\n</p>\n\n<div class=\"org-src-container\">\n\n<pre class=\"src src-swift\">enum Device: String {\n case phone, tablet, watch\n}\nlet aDevice = Device(rawValue: \"phone\")\nprint(aDevice)\n</pre>\n</div>\n\n<pre class=\"example\">\nPrints Optional(main.Device.phone)\n</pre>\n\n<div id=\"outline-container-sec-1\" class=\"outline-2\">\n<h2 id=\"sec-1\"><span class=\"section-number-2\">1</span> The Problem</h2>\n<div class=\"outline-text-2\" id=\"text-1\">\n<p>\nAs soon as you're using associated values in your enums, this doesn't work anymore:\n</p>\n\n<div class=\"org-src-container\">\n\n<pre class=\"src src-swift\">enum Example {\n case Factory(workers: Int)\n case House(street: String)\n}\n</pre>\n</div>\n\n<p>\nSwift can't create an instance of <code>Example</code> because the two cases, <i>Factory</i> and <i>House</i> have different associated types (the <code>workers</code> integer and the <code>street</code> string. Each invocation of <code>Example</code> requires different parameters, so this can't be generalized. \n</p>\n\n<p>\nHowever, that's not the case when your associated types all match up:\n</p>\n\n<div class=\"org-src-container\">\n\n<pre class=\"src src-swift\">enum Device {\n case phone(name: String, screenSize: CGSize)\n case watch(name: String, screenSize: CGSize)\n case tablet(name: String, screenSize: CGSize)\n}\n</pre>\n</div>\n\n<p>\nIn thise case, all the <code>associated types</code> are the same. There're a myriad of other ways to model this, but I found the device <code>enum</code> to be a concise example for what I'm about to explain. Even though every <code>Device</code> invocation is the same now, you still can't just call it with some sort of raw value and expect the correct type. Instead, what you have to do is perform a <a href=\"https://appventure.me/2015/08/20/swift-pattern-matching-in-detail/\">match</a> in order to create the correct instance:\n</p>\n\n<div class=\"org-src-container\">\n\n<pre class=\"src src-swift\">import Foundation\n\nenum Device {\n case phone(name: String, screenSize: CGSize)\n case watch(name: String, screenSize: CGSize)\n case tablet(name: String, screenSize: CGSize)\n\n static func fromDefaults(rawValue: String, name: String, screenSize: CGSize) -> Device? {\n\tswitch rawValue {\n\tcase \"phone\": return Device.phone(name: name, screenSize: screenSize)\n\tcase \"watch\": return Device.watch(name: name, screenSize: screenSize)\n\tcase \"tablet\": return Device.tablet(name: name, screenSize: screenSize)\n\tdefault: return nil\n\t}\n }\n}\nlet b = Device.fromDefaults(\"phone\", name: \"iPhone SE\", screenSize: CGSize(width: 640, height: 1136))\nprint(b)\n</pre>\n</div>\n\n<pre class=\"example\">\nprints Optional(main.Device.phone(\"iPhone SE\", (640.0, 1136.0)))\n</pre>\n\n<p>\nThis looks ok, but it <b>is</b> already a bit of repetitive code. Once you develop more than just three enum cases / two associated types, this will quickly get out of hand.\n</p>\n\n<div class=\"org-src-container\">\n\n<pre class=\"src src-swift\">enum Vehicle {\n case .car(wheels: Int, capacity: Int, weight: Int, length: Int, height: Int, width: Int, color: Int, name: Int, producer: Int, creation: NSDate, amountOfProducedUnits: Int)\n case .ship(wheels: Int, capacity: Int, weight: Int, length: Int, height: Int, width: Int, color: Int, name: Int, producer: Int, creation: NSDate, amountOfProducedUnits: Int)\n case .yacht(wheels: Int, capacity: Int, weight: Int, length: Int, height: Int, width: Int, color: Int, name: Int, producer: Int, creation: NSDate, amountOfProducedUnits: Int)\n case .truck(wheels: Int, capacity: Int, weight: Int, length: Int, height: Int, width: Int, color: Int, name: Int, producer: Int, creation: NSDate, amountOfProducedUnits: Int)\n case .motorbike(wheels: Int, capacity: Int, weight: Int, length: Int, height: Int, width: Int, color: Int, name: Int, producer: Int, creation: NSDate, amountOfProducedUnits: Int)\n case .helicopter(wheels: Int, capacity: Int, weight: Int, length: Int, height: Int, width: Int, color: Int, name: Int, producer: Int, creation: NSDate, amountOfProducedUnits: Int)\n case .train(wheels: Int, capacity: Int, weight: Int, length: Int, height: Int, width: Int, color: Int, name: Int, producer: Int, creation: NSDate, amountOfProducedUnits: Int)\n ...\n}\n</pre>\n</div>\n\n<p>\nI think you get my point.\n</p>\n</div>\n</div>\n\n<div id=\"outline-container-sec-2\" class=\"outline-2\">\n<h2 id=\"sec-2\"><span class=\"section-number-2\">2</span> The Solution</h2>\n<div class=\"outline-text-2\" id=\"text-2\">\n<p>\nSo.. how do we solve this? Interestingly, there's a quirky similarity between the initializer of an associated type and a closure. Take this code:\n</p>\n\n<div class=\"org-src-container\">\n\n<pre class=\"src src-swift\">enum Example {\n case test(x: Int)\n}\nlet exampleClosure = Example.test\n</pre>\n</div>\n\n<p>\nWhat is the type of <code>exampleClosure</code> here? The type is <code>(Int) -> Example</code>. That's right, calling an associated value <code>enum</code> case without any parameters will yield a closure that, when called with the correct types, will return an instance of said type.\n</p>\n\n<p>\nThis means that, the following is valid, working Swift:\n</p>\n\n<div class=\"org-src-container\">\n\n<pre class=\"src src-swift\">enum Fruit {\n case apple(amount: Int)\n case orange(amount: Int)\n}\nlet appleMaker = Fruit.apple\nlet firstApple = appleMaker(amount: 10)\nlet secondApple = appleMaker(amount: 12)\nprint(firstApple, secondApple)\n</pre>\n</div>\n\n<pre class=\"example\">\napple(10) apple(12)\n</pre>\n\n<p>\nSo, how would that help us simplify the gross code duplication problem above? Have a look:\n</p>\n\n<div class=\"org-src-container\">\n\n<pre class=\"src src-swift\" id=\"feature-image\">import Foundation\n\nenum Device {\n case phone(name: String, screenSize: CGSize)\n case watch(name: String, screenSize: CGSize)\n case tablet(name: String, screenSize: CGSize)\n\n private static var initializers: [String: (name: String, screenSize: CGSize) -> Device] = {\n\treturn [\"phone\": Device.phone, \"watch\": Device.watch, \"tablet\": Device.tablet]\n }()\n\n static func fromDefaults(rawValue: String, name: String, screenSize: CGSize) -> Device? {\n\treturn Device.initializers[rawValue]?(name: name, screenSize: screenSize)\n }\n}\n\nlet iPhone = Device.fromDefaults(\"phone\", name: \"iPhone SE\", screenSize: CGSize(width: 640, height: 1134))\nprint(iPhone)\n</pre>\n</div>\n\n<pre class=\"example\">\nOptional(main.Device.phone(\"iPhone SE\", (640.0, 1134.0)))\n</pre>\n\n<p>\nSo, let's try to figure out what happened here. We have a new property <code>initializers</code> on our <code>Device</code>. It's a <code>Dictionary</code> of type <code>[String: (name: String, screenSize: CGSize) -> Device]</code>. I.e. something that maps from a <code>String</code> key to a closure with the same type as our <code>Device</code> cases. The dictionary contains the initializers of each of our distinct cases, simply by using the same trick as above, just handing in the closure: <code>phone:Device.phone</code>\n</p>\n\n<p>\nThe <code>fromDefaults</code> function, then, only has to know the key of the device we'd like to create, and it can call the appropriate closure. This leads to a much shorter implementation, especially for bigger enums (like our <b>Vehicle</b> example above). As you can see, creating a <code>Device</code> instance is then as simple as:\n</p>\n\n<div class=\"org-src-container\">\n\n<pre class=\"src src-swift\">Device.initializers[\"phone\"]?(name: \"iPhone 5\", screenSize: CGSize(width: 640, height: 1134)))\n</pre>\n</div>\n\n<p>\nJust as with raw values, in case there is no <code>enum</code> case <b>phone</b> we'd just get an empty optional back. \n</p>\n\n<p>\nThis solution isn't perfect of course. You still have to have the <code>initializers</code> dictionary, however it will be much less repetitve than having to <code>match</code> over all cases manually.\n</p>\n\n<p>\nFinally, I suppose it goes without saying that the code above ignores an important best practice to be concise and to be able to concentrate on the task at hand; Nevertheless: having stringified code like <code>Device.initializers[\"phone\"]</code> is not the best way to write this. Instead, those keys should be properly defined somewhere else. \n</p>\n</div>\n</div>\n",
:keywords
"swift optional enum raw value initializers associated type",
:title "Raw value initializers for enums with associated types",
:filename
"2016-04-23-associated-types-enum-raw-value-initializers.org",
:id 1768632259,
:url "/2016/04/23/associated-types-enum-raw-value-initializers/",
:javadate #inst "2016-04-22T22:00:00.000-00:00",
:keyword-keywords
(:swift :optional :enum :raw :value :initializers :associated :type)}]