Pitch: Multi-statement if/switch/do expressions

Having thought about this a bit more I'd like to circle back to an idea that briefly occurred to me earlier in this thread:

We could use the last expression rule, but require that all unused values prior to the final unused value are of type Void.

Thus, logging is enabled (because the output is Void)...

let sfSymbolIdentifier =
    switch deviceType {
        case .mac: "laptopcomputer"
        case .iPhone: "iphone"
        default: 
            log("Unexpected device type...")
            "questionmark.square.dashed"
    }

...intermediate expressions are enabled because they do not result in unused values...

let accessibleRepos: [Repo] =
    switch signedInEntity {
    case .companyMember (let employee, let company):

        ///
        let isAdmin: Bool =
            company
                .admins
                .contains(employee.id)

        ///
        employee
            .personalRepos
            .appending(company.publicRepos)
            .appending(
                isAdmin ? company.adminRepos : []
            )

    case .individual (let user):
        user.personalRepos
    }

...imperative control flow is fine because again, no unused values...

let backgroundColor =
    switch colorScheme {
    case .light: .white
    case .dark:
        var (r, g, b) = (0.0, 0.0, 0.0)
        for value in myCollection {
            // ... update rgb values
        }
        Color(r: r, g: g, b: b)
    @unknown default: .white
    }

...and in situations where you want to use a static member of the type but precede it with Void-returning expressions that result in ambiguity, you resolve it the same way as we already have available to us with result builders which is separating the expressions using a semicolon...

let backgroundColor: Color =
    switch colorScheme {
    case .light: .white
    case .dark: .charcoal
    @unknown default:
        log("Unrecognized color scheme: \(colorScheme)");
        .white
    }

With this approach we don't have to touch the return value when adding print statements before it, which is nice. We also can't change the return value by adding a line underneath the current return value, because that would make the previous return value an unused value that isn't Void. This possibility was for me one of the most unsettling aspects of an unrestricted last-expression rule, although at the moment I can't thoroughly articulate why. This approach also generalizes to function contexts if we want it to. And we don't have to suffer the various implications of a new return-adjacent keyword.

Previously, I expressed concern that the lack of visual indication of whether or not a result builder is in effect combined with a last-expression-y rule would dramatically degrade readability, but I'm now leaning towards the idea that if a function's name does not make it clear whether or not it produces a value then that's the real source of confusion and is the real thing that should be changed to remove that confusion.

8 Likes