update: as of Swift 1.2, there is another way to do this, as you can now defer assignment to let
.
Ever find yourself having to use var
when a let
would be better, because you’re having to choose between multiple options?
var direction: Direction switch (char) { case "a": direction = .Left case "s": direction = .Right default: direction = .Forward } var cry: String if direction == .Forward { cry = "To glory!" } else { cry = "Tum-tee-tum" }
Often you’ll see people declaring those var
s as optionals set to nil
, because they think you have to give it a value. This isn’t actually necessary – so long as the compiler can tell it will always be assigned to before it is used, as it is here, it’ll let you defer setting the value. But you still can’t use let
instead of var
.
Most people are aware of the ternary operator, that means you can replace the if
version with this:
let cry = direction == .Forward ? "To glory!" : "Tum-tee-tum"
But this doesn’t scale at all well with multiple choices:
// hours of code formatting enjoyment available making this look sensible let direction: Direction = char == "a" ? .Left : char == "s" ? .Right : .Forward
But don’t despair, there’s a better way! Just wrap your switch in a closure expression:
let direction: Direction = { // you could put "_ -> Direction in" here instead of // typing the let, if you prefer switch (char) { case "a": return .Left case "s": return .Right default: return .Forward } }() // call the expression immediately
There, both sanity and let
preserved.
Of course, it’d be nice if there were a version of switch
(and maybe even if
) that was an expression, so you didn’t need the closure. Especially if it meant Swift could fully infer the type for you (with the closure trick, you have to give the type).
So that’s on my letter to Father Christmas. What would be really nice would be something like this:
let direction = case(char) { when "a" { .Left } when "s" { .Right } otherwise { .Forward } }
Calling the outer keyword case
(and the default otherwise
) is lifted from LISP. This would help differentiate it from the switch
statement format, to avoid confusion and backwards-compatibility troubles. Ditching the :
and replacing it with braces seems more consistent with the rest of Swift syntax rather than sticking with a hangover from C.1 The blocks against the when
clauses could follow the same pattern as closures, i.e. automatically return if they’re single expressions.2 The compiler would need to detect when the clauses returned incompatible types and throw an error (the ternary operator behaves similarly).
Anyway, there’s a glass of sherry waiting for Santa if he visits.3 In the meantime, give the closure trick a go.
- This idea courtesy of @OldManKris ↩
- Maybe they should even be closures (and you could pass function names instead), not sure about this one. ↩
- I think the Swift dev team have posted on the forums acknowledging switches-as-expressions would be useful and that they’ll probably get to it some time. ↩
This is something that I think Scala did quite nicely, and I agree Swift is sorely lacking. In Scala:
1. Any code block that lacks an explicit return statement will implicitly use the value of its last expression as its return value
2. The various flow control structures in Scala are expressions, not statements; the looping structures have void return types, while `if-else` and `match` (Scala’s equivalent to switch) expressions return the value of whichever branch is executed.
This allows for elegant, succinct assignment of conditional values to constants, in exactly the style you propose.
People have come up with various tricks to implement a ‘cond’ function using auto closures. Here’s one (the most general version is at the end of the post): http://appventure.me/2014/06/08/writing-simple-syntax-extensions-in-swift/
A ‘case’ function should be just a simplified version of ‘cond’.
[…] Source: airspeedvelocity.net […]
[…] new ability to defer initialization of let constants now makes much of of my “avoiding var” redundant, as you can now do this: […]