Any clever way to add a simple assignment in a cascading if "let"?

I have a large cascading let - and midway through I'd like to make one assignment that's not an optional:


var b = [String: [ [String: Int]] ]()
if let a = b["foo"]  where a.count > 0,
   let c = a[0], // ** FAILS - not an optional assignment
   let d = c["Howdie"] where d > 5
{
}


I'm finding more and more that I have this case. Now, I can just go use a[0] everywhere instead of the desired 'c', but the code is harder to read, and often in the body of the if I need 'c' too.


I know I could "hack" a way by making my code more complex - that is, instead of looking for an array of [String: Int], I could use [String: Any] instead,:


var b = [String: [ [String: Any]] ]()
if let a = b["foo"]  where a.count > 0,
   let c = a[0] as? [String: Int], // OK
   let d = c["Howdie"] where d > 5
{
}


Is there something I'm missing? [Sigh, often is :-) ]

Accepted Reply

How is this?

if let a = b["foo"]  where a.count > 0,
    case let c = a[0],
    let d = c["Howdie"] where d > 5
{
    //...
}

Replies

If you are using Swift 2.0, I think the new guard let construct may offer a solution for you.


guard let a = b["foo"] where a.count > 0 else { return }     // you can bail by throwing, return, break, continue as appropriate
let c = a[0]
guard let d = c["Howdie"] where d > 5 else { return }

Yes. You must use a label to break out of a do block.

d: do {
    guard let a = b["foo"] where a.count > 0 else {break d}
    let c = a[0]
    if let d = c["Howdie"] where d > 5 {}
}

How is this?

if let a = b["foo"]  where a.count > 0,
    case let c = a[0],
    let d = c["Howdie"] where d > 5
{
    //...
}

If else-omitted guard could break the closest code-block (is this once discussed?), it would be more preferrable than the complicated if-let syntax:

do {
    guard let a = b["foo"] where a.count > 0
    let c = a[0]
    guard let d = c["Howdie"] where d > 5
    //...
}

I had no idea that "case" could do that. That doesn't seem much like a "case"!

Can you modify this example to show how the "else" clause is handled? That said, I can use your 'case':


guard let a = bb["foo"] where a.count > 0,
      case let c = a[0],
      let d = c["Howdie"] where d > 5
else { fatalError("Yikes") }


Thank you ever so much for this assistance! I'd just never thought of it!


[ I'm constantly reminded of how there is so much more to learn about Swift, even though I've used it exclusively since last June. ]

I am going to guess that since 'case' is pattern matching, case can by design fail. However, this usage is sort of like using '*' in a regular expression - the assignment always succeeds.

It's me to thank you. Until you addressed the issue, I never thought of using if-case in this way. (Code shown by Jessy was my preferred style.)

Why not just use ".first" instead of [0]? This gets you a result which is optional, allowing you to do this without using case. You also don't need the "where" clause on the assignment of a, since if count is equal to 0, a.first will simply return nil.


var b = [String: [ [String: Int]] ]()
if let a = b["foo"],
    let c = a.first,
    let d = c["Howdie"] where d > 5
{
}

Now that we know about case, that's an efficiency drop, but if a isn't needed,

if let c = b["foo"]?.first

works.

The code I posted is contrived - the index could be arbitrary (one viewController from an array - think tabBarController or splitViewController). But I was interested in a general solution, not just something that works just for my contrived example - case is it.