Allow optional unwrapping in where clause

+1 for where let clauses.

To keep the discussion grounded in reality, here are two examples off the top of my code base.

Example 1: Preparing for a storyboard segue using switch/case/where

Before:

case let destination as UINavigationController:
    guard let destination = destination.topViewController as? HiddenPuzzlesViewController else {
        break
    }
    destination.dataSource = self
    destination.delegate = self

After:

case let destination as UINavigationController where let destination = destination.topViewController as? HiddenPuzzlesViewController:
    destination.dataSource = self
    destination.delegate = self

Example 2: Updating visible cells in a collection view using for/where

Before:

override func setEditing(_ editing: Bool, animated: Bool) {
    super.setEditing(editing, animated: animated)

    guard let cells = collectionView?.visibleCells else {
        return
    }

    for cell in cells {
        guard let cell = cell as? PuzzleListCell else {
            continue
        }
        cell.setEditing(editing, animated: animated)
    }
}

After:

override func setEditing(_ editing: Bool, animated: Bool) {
    super.setEditing(editing, animated: animated)

    for cell in collectionView?.visibleCells where let cell = cell as? PuzzleListCell {
        cell.setEditing(editing, animated: animated)
    }
}

Note that both examples feature shadowing. I would expect it to work like e.g. guard let works inside of a switch/for statement today. Also note that in the second example, the for loop iterates over an optional collection as proposed in the optional iteration thread.

IMO, in both cases, readability is significantly improved.

1 Like

Why not simply:

for cell as? PuzzleListCell in collectionView?.visibleCells {
    cell.setEditing(editing, animated: animated)
}

?

Maybe it should be without the question mark, since the following does work today:

func f(_ anys: [Any]) {
    for case let e as Int in anys { print(e) }
}
let optInts: [Int?] = [1, 2, nil, 3]
f(optInts as [Any])
// will print
// 1
// 2
// 3

So the last example could perhaps be written like this today:

override func setEditing(_ editing: Bool, animated: Bool) {
    super.setEditing(editing, animated: animated)

    if let cells = collectionView?.visibleCells {
        for case let cell as PuzzleListCell in cells {
            cell.setEditing(editing, animated: animated)
        }
    }
}

I've written some for...guard loops in the past, and it happens quite a lot that my first thought is using the where clause, which can perform the check, but can't give you the type safety afterwards.

But imho it would be better not to extend the capabilities of where, and bring for more in line with if and while instead by allowing a comma-separated list of conditions:

for user in users, user.hasBirthdayToday, let profilePic = user.profilePic {
    addBirthdayCap(to: profilePic)
}

What I like about this approach is that it could be extended:

for let sections = optionalSections, section in sections, row in section {
  print(row.title)
}

This would not only allow convenient iteration over optional sequences, but also walking nested structures.

2 Likes

That's a bit of an expansion to the idea of multiple patterns in a for. It's an interesting idea, but I'm not sure how practical it would be given that a lot of times I've used nested loops, I generally want to break the inner iteration at some point. So unless you allow specifying labels to each iterable (which I guess would be possible) that would limit what you could usefully do with this.

TIL! Thanks.

Refactored as follows:

override func setEditing(_ editing: Bool, animated: Bool) {
    super.setEditing(editing, animated: animated)

    guard let cells = collectionView?.visibleCells else {
        return
    }

    for case let cell as PuzzleListCell in cells {
        cell.setEditing(editing, animated: animated)
    }
}
1 Like