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.