Using "If Let..." With Many Expressions

Using if let... with many expressions

Update for Swift 1.2

Since Swift 1.2, if let allows unwrapping multiple optionals, so you can now just write this, as in your example:

if let x = someDict[someKey], y = someDict[someOtherKey] { … }

You can even interleave conditions such as:

if let x = someDict[someKey] where x == "value", y = someDict[someOtherKey] { … }

This used to be valid before Swift 1.2

Here's how you would do it without an ugly force-upwrapping:

switch (dict["a"], dict["b"]) {
case let (.Some(a), .Some(b)):
println("match")
default:
println("no match")
}

Still pretty verbose, actually.

This works because an optional type of the form Type? is actually shorthand for Optional<Type>, which is an enum that looks roughly like this:

enum Optional<T> {
case None
case Some(T)
}

You can then use pattern matching as for any other enum.

Edit: I've seen people write helper functions like this one (sorry for the lack of attribution, I don't remember where I saw it):

func unwrap<A, B>(a: A?, b: B?) -> (A, B)? {
switch (a, b) {
case let (.Some(a), .Some(b)):
return (a, b)
default:
return nil
}
}

Then you can keep using the if let construct, namely like this:

if let (a, b) = unwrap(dict["a"], dict["b"]) {
println("match: \(a), \(b)")
} else {
println("no match")
}

How to write multiple condition in if let statement?

You can use a simple tuple:

if let (Some(6), Some(7)) = (a, b) {
// do something
}

How to read if let expressions?

You shouldn't expect the keywords in the syntax of any programming language to map directly to words in English, or any other natural language.

let expressions in Rust always accept a pattern, which lets you do things like:

let foo = (1, (2, 3));
let (a, (b, _)) = foo;

However, this only works when the pattern is irrefutable, that is it will always match.

if let extends this to accommodate types that may not always match.

How you are reading it is just fine: "if the pattern matches then...", or "if this let binding succeeds then...".

Using multiple let-as within a if-statement in Swift

Update for Swift 3:

The following will work in Swift 3:

if let latitudeDouble = latitude as? Double, let longitudeDouble = longitude as? Double {
// latitudeDouble and longitudeDouble are non-optional in here
}

Just be sure to remember that if one of the attempted optional bindings fail, the code inside the if-let block won't be executed.

Note: the clauses don't all have to be 'let' clauses, you can have any series of boolean checks separated by commas.

For example:

if let latitudeDouble = latitude as? Double, importantThing == true {
// latitudeDouble is non-optional in here and importantThing is true
}

Swift 1.2:

Apple may have read your question, because your hoped-for code compiles properly in Swift 1.2 (in beta today):

if let latitudeDouble = latitude as? Double, longitudeDouble = longitude as? Double {
// do stuff here
}

Swift 1.1 and earlier:

Here's the good news - you can totally do this. A switch statement on a tuple of your two values can use pattern-matching to cast both of them to Double at the same time:

var latitude: Any! = imageDictionary["latitude"]
var longitude: Any! = imageDictionary["longitude"]

switch (latitude, longitude) {
case let (lat as Double, long as Double):
println("lat: \(lat), long: \(long)")
default:
println("Couldn't understand latitude or longitude as Double")
}

Update: This version of the code now works properly.

Using Let inside of a if statement

Your let forms have no body in which the variable abs is used.

Using two let expressions is also probably not what you want. You should use one, and then use if when calculating the value to bind the variable to.

(define abs
(lambda (l)
(let ((val (if (>= (car l) 0)
(car l)
(- (car l)))))
val)
)
)

Using the Swift if let with logical AND operator &&

As of Swift 1.2, this is now possible. The Swift 1.2 and Xcode 6.3 beta release notes state:

More powerful optional unwrapping with if let — The if let construct
can now unwrap multiple optionals at once, as well as include
intervening boolean conditions. This lets you express conditional
control flow without unnecessary nesting.

With the statement above, the syntax would then be:

if let tabBarController = window!.rootViewController as? UITabBarController where tabBarController.viewControllers.count > 0 {
println("do stuff")
}

This uses the where clause.

Another example, this time casting AnyObject to Int, unwrapping the optional, and checking that the unwrapped optional meets the condition:

if let w = width as? Int where w < 500
{
println("success!")
}

For those now using Swift 3, "where" has been replaced by a comma. The equivalent would therefore be:

if let w = width as? Int, w < 500
{
println("success!")
}

swift: if-let with parallel assignment

Before deciding whether it is possible or not, consider why if - let ... conditionals work with a single optional value: the reason this code compiles

if let constVar = testVar {
...
}

is that all optional types conform to the LogicalValue protocol, which handles the null checking of the optional value.

This explains why your trick with an optional tuple did not work either: the implementation of the LogicalValue checked if the tuple itself is non-null, ignoring its components. The logic behind Apple's decision is clear: rather than making an exception for tuples when all their element types are optional, they took the uniform approach, and treated the tuple in the same way that they treat other optional types.

Of course, implementing the logic that you are trying to implement is easy with an additional line of code:

if a != nil && b != nil {
let (m, n) = (a!, b!)
println("m: \(m), n: \(n)")
} else {
println("too bad")
}

Avoid consecutive if let declarations in Swift

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.

How can I use if statement in let statement?

An if statement in ML is an expression. That means it has to have a value. Remember, value declaration in SML looks like this:

val <name> = <expression>

Since an if ... then ... else statement is an expression, you can put in on the right side of the =.

fun function i =
let
val h = if i = 1 then ...
else ...
in
...
end

Another thing. Remember that valOf can go wrong. Using it on input is pretty much asking for your program to crash, since the user could type in anything.



Related Topics



Leave a reply



Submit