Avoid using var with this one weird trick

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 vars 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.

  1. This idea courtesy of @OldManKris 
  2. Maybe they should even be closures (and you could pass function names instead), not sure about this one. 
  3. 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. 

4 thoughts on “Avoid using var with this one weird trick

  1. 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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s