Dealing with Optional values (specifically Implicitly Unwrapped Optionals) is unfortunately an everyday part of our life as forward-looking iOS developers. The entirety of the Cocoa API won’t be audited for optionals for a good long while, so these things are here to stay. This means we’re going to introduce some actual runtime errors into our application where we might not have encountered them before.
Let’s look at a fairly standard Objective-C object that we might be pulling into our app through a library:
@interface TBUser
@property (nonatomic, copy) NSString *fullName;
@property (nonatomic, copy) NSString *emailAddress;
@property (nonatomic, copy) NSString *twitterUsername;
@property (nonatomic) NSURL *websiteURL;
@end
Now, back in our Swift application, we start building a View Model to present this data to the view object:
struct UserViewModel {
let user: TBUser
}
This view model is instantiated with an instance of TBUser
(remember that
using a Struct
gives us the initializer for free). We can then start
building out the computed properties for the view model to pass the data from
the model:
extension UserViewModel {
var displayName: String {
return user.fullName
}
var email: String {
return user.emailAddress
}
var twitterHandle: String {
return "@\(user.twitterUsername)"
}
}
All of this looks fairly straightforward. However, once we run the application and view a user, we get a crash:
fatal error: unexpectedly found nil while unwrapping an Optional value
Upon bridging to Swift from Objective-C, those properties are being turned into implicitly unwrapped optionals. And apparently, our library isn’t guaranteeing that we actually get values back for some of these properties.
“This seems simple enough” we say, and throw a quick guard around the implementation:
var twitterHandle: String {
if let username = user.twitterUsername {
return "@\(username)"
}
return ""
}
And there we go. Now, when we navigate to that same user, we don’t see that crash. We get the formatted username when there is one, and an empty string when it doesn’t exist.
Except… that if let
is kind of gross. And then we start looking around
and realize that this might be something we have to do a lot. It’s a fairly
common pattern when dealing with optional values:
- Check to see if there is a value
- Do something with the unwrapped value
- Do something else if the optional is
.None
Luckily, we can use a functional concept to simplify this for us.
Enter, fmap
(Quick note for Functional Programming nerds: we’re really just talking about
fmap
in the context of Optional
here. I know this is a simplification.
Please don’t email me.)
The function fmap
(represented as an infix operator as <$>
in Haskell and
which we’ll define as <^>
in Swift) can be implemented as so:
infix operator <^> { associativity left }
func <^><A, B>(f: A -> B, a: A?) -> B? {
switch a {
case .Some(let x): return f(x)
case .None: return .None
}
}
Put into words, fmap
takes a function and an optional value. If the optional
value is .Some
, it passes the contained value into the function. If the
optional value is .None
it just returns .None
.
So our first step here is to create that function that we want to pass to
fmap
.
func usernameFormat(name: String) -> String {
return "@\(name)"
}
Note that now we’re dealing with a non-optional instance of String
. So we
have a guarantee from the compiler that when this function is called, we will
have a name to format. Rad.
Now we can try to start to work that into the original implementation:
var twitterHandle: String {
if let username = user.twitterUsername {
return usernameFormat(username)
}
return ""
}
Now, we introduce fmap
. Hold onto your butts:
var twitterHandle: String {
let handle = usernameFormat <^> user.twitterUsername
return handle ?? ""
}
That actually looks really nice to my eye. And we get to take advantage of the
insanely awesome nil coalescing operator (??
). This operator
returns the value of the optional, or the default value if the optional is
.None
. This is the same as the fromMaybe
function from Haskell.
So again, as a recap, putting into words what’s happening here:
We create the handle
constant (which is of the type String?
after type
inference) with the return value from fmap
ing usernameFormat
over the
optional username. If the username is .None
this will mean that handle
is
.None
. However, if the username is .Some
, the contained value will be
passed into usernameFormat
, which will set handle
to the formatted string,
wrapped in .Some
.
Then, we return the contained value of handle
, or an empty string if
handle
is .None
.
Whew.
Let’s look at a more interesting example.
Multiple optional values
Now we want to display that website on the screen. We add the following function to our View Model:
var website: String {
let url = user.websiteURL
return "\(url.host)\(url.path)"
}
Navigating to a user in the UI reveals another unexpected bug. Instead of
nicely formatted URLs like example.com/path
, we have something else
entirely:
Optional("example.com")/Optional("path")
That’s… not what we wanted at all. Even worse, some websites are coming
back as nil/nil
. So what’s going on here?
Turns out, not only is the user’s websiteURL
property an implicitly
unwrapped optional, but the host
and path
properties on NSURL
are
bridged into optionals as well. If we decided to use if let
here, we’d end
up with the following:
var website: String {
if let url = user.websiteURL {
if let host = url.host {
if let path = url.path {
return "\(host)/\(path)"
}
}
}
return ""
}
Again, to my eye those nested if let
statements are ugly and hard to parse.
Luckily for us, we can use the same pattern we used before with a small
addition to handle this cleanly.
Enter, apply
The function apply
(represented with the infix operator <*>
in Haskell,
and defined in Swift by us as the same) can be implemented as so:
infix operator <*> { associativity left }
func <*><A, B>(f: (A -> B)?, a: A?) -> B? {
switch f {
case .Some(let fx): return fx <^> a
case .None: return .None
}
}
It’s very similar to our implementation of fmap
. In fact, it even uses
fmap
in the implementation. The main difference here is that the function
itself is an optional. Yes, that’s right, functions can be optional values
just like anything else. Wild, right?
So walking through our implementation of apply
, it works much like fmap
,
except that we’re pattern matching on the function optional (f
), not the
normal optional value (a
). If we have a function, we return the result of
fmap
ing the function over a
. Remember that this itself will then check to
see if a
exists. If f
is .None
, we return .None
.
The cool thing about this is that it allows us to chain things together in a
way that means when one thing fails (returns .None
), everything else fails
down the line.
So how can we use this? Much like the original solution, we will create a function to hold onto that formatting code, but this time we’ll implement the function in curried form, which allows for partial application:
private func websiteFormat(host: String)(path: String) -> String {
return "\(host)\(path)"
}
Notice that each argument is in its own set of parens. That’s the important
part here. With this syntax, when we give the websiteFormat
function the
host
argument, it actually returns a new function that takes the path
argument. When we hand this new function the path
argument, it satisfies all
of the required arguments, and returns the string we expect.
So now, we can take our fmap
pattern from before and use apply
to chain it
together with the second argument:
var website: String {
let url = user.websiteURL
let websiteString = websiteFormat <^> url?.host <*> url?.path
return websiteString ?? ""
}
We create a local variable url
. It’s typed as NSURL!
, but we’re going to
treat it like a full blown optional.
We use fmap
and apply
to partially apply websiteFormat
to the host
,
and then if that succeeds, we pass in the path
. If that succeeds as well, we
get .Some
with a nicely formatted string. If it fails, we get .None
.
We then use the ??
operator to return the value, or an empty string.
I think that looks significantly nicer than those earlier nested if let
statements. And we don’t have to check for .None
when accessing url
because we’re using the optional chaining syntax.
So if url
is .None
this whole thing results in .None
and we return an
empty string.
Wrap up
Even outside of the scope of amazing type-safe JSON parsing, these concepts from Functional Programming can
make our life dealing with ubiquitous optional values just as safe (if not
safer) than when we could message nil
all we wanted.
What’s next
Read my colleague Tony’s posts on using these same concepts (and a few others) for parsing JSON values safely and cleanly.
- Efficient JSON in Swift with Functional Concepts and Generics
- Real World JSON Parsing with Swift
- Parsing Embedded JSON and Arrays in Swift
Also, check out Argo, the JSON parsing library that was developed alongside those posts.