In Swift I used if let
declarations to check if my object is not nil
if let obj = optionalObj
{
}
But sometimes, I have to face with consecutive if let
declarations
if let obj = optionalObj
{
if let a = obj.a
{
if let b = a.b
{
// do stuff
}
}
}
I'm looking for a way to avoid consecutive if let
declarations.
I would try something like :
if let obj = optionalObj && if let a = obj.a && if let b = a.b
{
// do stuff
}
But the swift compiler do not allow this.
Any suggestion ?
I wrote a little essay on the alternatives some time ago: https://gist.github.com/pyrtsa/77978129090f6114e9fb
One approach not yet mentioned in the other answers, which I kinda like, is to add a bunch of overloaded every
functions:
func every<A, B>(a: A?, b: B?) -> (A, B)? {
switch (a, b) {
case let (.Some(a), .Some(b)): return .Some((a, b))
default: return .None
}
}
func every<A, B, C>(a: A?, b: B?, c: C?) -> (A, B, C)? {
switch (a, b, c) {
case let (.Some(a), .Some(b), .Some(c)): return .Some((a, b, c))
default: return .None
}
}
// and so on...
These can be used in if let
statements, case
expressions, as well as optional.map(...)
chains:
// 1.
var foo: Foo?
if let (name, phone) = every(parsedName, parsedPhone) {
foo = ...
}
// 2.
switch every(parsedName, parsedPhone) {
case let (name, phone): foo = ...
default: foo = nil
}
// 3.
foo = every(parsedName, parsedPhone).map{name, phone in ...}
Having to add the overloads for every
is boilerplate'y but only has to be done in a library once. Similarly, with the Applicative Functor approach (i.e. using the <^>
and <*>
operators), you'd need to create the curried functions somehow, which causes a bit of boilerplate somewhere too.
In swift 1.2 you can do
if let a = optA, let b = optB {
doStuff(a, b)
}
In your specific case, you can use optional chaining:
if let b = optionaObj?.a?.b {
// do stuff
}
Now, if you instead need to do something like
if let a = optA {
if let b = optB {
doStuff(a, b)
}
}
you're out of luck, since you can't use optional chaining.
Would you prefer a cool one-liner instead?
doStuff <^> optA <*> optB
Keep reading. For how scaring it might look, this is really powerful and not so crazy to use as it seems.
Fortunately, this is a problem easily solved using a functional programming approach. You can use the Applicative
abstraction and provide an apply
method for composing multiple options together.
Here's an example, taken from http://robots.thoughtbot.com/functional-swift-for-dealing-with-optional-values
First we need a function to apply a function to an optional value only only when it contains something
// this function is usually called fmap, and it's represented by a <$> operator
// in many functional languages, but <$> is not allowed by swift syntax, so we'll
// use <^> instead
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
}
}
Then we can compose multiple options together using apply, which we'll call <*>
because we're cool (and we know some Haskell)
// <*> is the commonly-accepted symbol for apply
infix operator <*> { associativity left }
func <*><A, B>(f: (A -> B)?, a: A?) -> B? {
switch f {
case .Some(let value): return value <^> a
case .None: return .None
}
}
Now we can rewrite our example
doStuff <^> optA <*> optB
This will work, provided that doStuff
is in curried form (see below), i.e.
func doStuff(a: A)(b: B) -> C { ... }
The result of the whole thing is an optional value, either nil
or the result of doStuff
Here's a complete example that you can try in the playground
func sum(a: Int)(b: Int) -> Int { return a + b }
let optA: Int? = 1
let optB: Int? = nil
let optC: Int? = 2
sum <^> optA <*> optB // nil
sum <^> optA <*> optC // Some 3
As a final note, it's really straightforward to convert a function to its curried form. For instance if you have a function taking two parameters:
func curry<A, B, C>(f: (A, B) -> C) -> A -> B -> C {
return { a in { b in f(a,b) } }
}
Now you can curry any two-parameter function, like +
for example
curry(+) <^> optA <*> optC // Some 3
In some cases you can use optional chaining. For your simple example:
if let b = optionalObj?.a?.b {
// do stuff
}
To keep your nesting down and to give yourself the same variable assignments, you could also do this:
if optionalObj?.a?.b != nil {
let obj = optionalObj!
let a = obj.a!
let b = a.b!
}
After some lecture thanks to Martin R, I found an interesting workaround: https://stackoverflow.com/a/26012746/2754218
func unwrap<T, U>(a:T?, b:U?, handler:((T, U) -> ())?) -> Bool {
switch (a, b) {
case let (.Some(a), .Some(b)):
if handler != nil {
handler!(a, b)
}
return true
default:
return false
}
}
The solution is interesting, but it would be better if the method uses variadic parameters.
I naively started to create such a method:
extension Array
{
func find(includedElement: T -> Bool) -> Int?
{
for (idx, element) in enumerate(self)
{
if includedElement(element)
{
return idx
}
}
return nil
}
}
func unwrap<T>(handler:((T...) -> Void)?, a:T?...) -> Bool
{
let b : [T!] = a.map { $0 ?? nil}
if b.find({ $0 == nil }) == nil
{
handler(b)
}
}
But I've this error with the compiler: Cannot convert the expression's type '[T!]' to type '((T...) -> Void)?'
Any suggestion for a workaround ?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With