Optional Forward Application
UPDATE: Ole Begemann, Will Hains, and a bunch of helpful redditors all point out a fundamental oversight I’ve made here:
My implementation of applyOptional
, below, is functionally identical to calling flatMap
on an optional type. And given this, there’s already prior art for an operator that calls flatMap
(a.k.a. bind
) on an Optional
(a.k.a. a Maybe
monad): >>=
.
I talk a little more about this in this post, but the takeaway is, just use the built-in flatMap
:
let urlString: String? = textField.text
let maybeURL = urlString.flatMap(URL.init)
And if we want to define an operator for this, we should use >>=
instead of my made up |?>
:
infix operator >>=
func >>= <T, U>(x: T?, f: (T)->U?) -> U? {
return x.flatMap(f)
}
let urlString: String? = textField.text
let maybeURL = urlString >>= URL.init
The original post follows:
Swift’s out-of-the-box compatibility with existing ObjC frameworks is an amazing technical feat and one of its greatest strengths. But that doesn’t mean I’m never annoyed when using, say, Foundation. Among my biggest pet peeves is the lack of optional overloads to common failable initializers like URL(string:)
:
let urlString: String? = textField.text
let maybeURL: URL?
if let someString = urlString {
maybeURL = URL(string: someString)
} else {
maybeURL = nil
}
// Do something with `maybeURL`...
It’s wordy. It’s got that dangling uninitialized let
just sort of hanging out. It has branching execution for no good reason.
And ultimately it just feels unnecessary. The whole idea of a failable initializer is that it can return a nil
in response to invalid input. It seems a small jump to extended the input to include optional types — the .none
values of which we would presume to be invalid. Then nil
s would just pass through.
But for whatever reason, that’s not how the API were translated to Swift. I’ve worked around this in a number of ways over the years. I started out using the nil coalescing operator:
let urlString: String? = textField.text
let maybeURL = URL(string: urlString ?? "🚫")
“🚫” is an invalid URL, so when urlString
is nil
, maybeURL
is nil
as well.
I don’t hate this — it’s concise and has a sort of literal readability to it. But it’s always felt like a hack based on undefined behavior. Eventually I moved to a more conventional solution:
extension URL {
init?(maybeString: String?) {
guard let string = maybeString else {
return nil
}
self.init(string: string)
}
}
let urlString: String? = textField.text
let maybeURL = URL(maybeString: urlString)
This works well enough. But it’s just as wordy as our original solution. The only difference is we give it a name behind an extension.
And therein lies another weakness: we have to define an extension like this for every failable initializer we use. There’s a fair amount of boiler plate in each, and there’s always the chance for collisions when extending Foundation like this.
Eventually I settled on generic solution in the form of a free function:
func applyOptional<T, U>
(_ x: T?, to f: (T)->U?) -> U? {
guard let someX = x else {
return nil
}
return f(someX)
}
let urlString: String? = textField.text
let maybeURL = applyOptional(urlString,
to: URL.init(string:))
This has a lot going for it. Not only can it generically handle optional initializers, it can be applied to functions returning optionals generally. This enhances its potential for reuse and thus its value as an abstraction.
But it’s never felt quite right. The first parameter feels buried. “Optional” isn’t descriptive enough to give me a sense of its purpose. And I keep reading URL.init(string:)
as a newly created URL rather than a function reference. Something is missing.
I didn’t realize what until I started watching the excellent Point•Free video series.1 Early on they create a Swift version of F#’s forward application operator, |>
. I realized their implementation was very similar to my applyOptional(_:to:)
, and that got me thinking about free functions in terms of operators:
infix operator |?>
public func |?> <T, U>(x: T?, f: (T)->U?) -> U? {
guard let someX = x else {
return nil
}
return f(someX)
}
let urlString: String? = textField.text
let maybeURL = urlString |?> URL.init(string:)
So yeah, I took the triangle operator and put a question mark in it to make it more optional-looking. Nothing ground-breaking, but it feels loads better to use. Freed from their parentheses, the bare urlString
and URL.init(string:)
stand out in their purpose. And the way the operator points the optional into the function ref makes their relation clear in a way the to:
label never quite captured.
Not that there aren’t tradeoffs, of course. applyOptional
is way easier to google for than |?>
, for one thing. But until Foundation adds init?(string: String?)
initializers,2 expect my code to be dotted with these little FP gems.
1: If you’re reading this blog, I can’t imagine you wouldn’t find entertainment and value in these videos.
My favorite part? Each one is accompanied by a carefully edited transcription. I don’t always have the time or energy to watch a presentation. Reading is sometimes more comfortable. And transcriptions make searching for half-remembered concepts a snap, as well.↩︎
2: Which I’m still not ruling out…↩︎