Implicit Binding of Associated Values in Case Statements

Motivation

Binding associated values from an enum case to a local variable is verbose and often repetitive. For example:

enum Fruit {
  case apple(diameter: Double, color: String)
  case orange(diameter: Double)
  case grapefruit(diameter: Double)
  case banana

  var circumference: Double? {
    // Repeated code to pull associated value; encourages copy-pasting
    switch self {
    case .apple(let diameter, _): return diameter * PI
    case .orange(let diameter): return diameter * PI
    case .grapefruit(let diameter): return diameter * PI
    case .banana: return nil
    }
  }

  var color: String {
    switch self {
    case .apple(_, let color): return color
    case .orange: return "orange"
    case .grapefruit: return "pink"
    case .banana: return "yellow"
    }
  }
}

Proposal

Bind associated values to local variables $0, $1, ... $n, where n is the number of associated values for that case.

Conceptually similar to a few existing features:

Anonymous Closure Arguments

[1,2,3].map { $0 + 1 }

Implicit variable binding in catch block

do {
  try someRiskyOperation()
} catch {
  print(error)
}

Implicit variable binding for property setters and observers

class Person {
  init(name: String) {
    self.name = name
  }

  var name: String {
    willSet { print("setting name: \(newValue)") }
    didSet { print("set name: \(oldValue)") }
  }
}

Example usage

enum Fruit {
  case apple(diameter: Double, color: String)
  case orange(diameter: Double)
  case grapefruit(diameter: Double)
  case banana

  var circumference: Double? {
    switch self {
    case .apple: return $0 * PI
    case .orange: return $0 * PI
    case .grapefruit: return $0 * PI
    case .banana: return nil
    }
  }

  var color: String {
    switch self {
    case .apple: return $1
    case .orange: return "orange"
    case .grapefruit: return "pink"
    case .banana: return "yellow"
    }
  }
}
2 Likes

This would at least require some sort of opt-in in source since otherwise it would be source breaking for switch statements in closures that already use the $n variable names to refer to closure parameters.

4 Likes

You can always bind multiple cases at once, as long as the types match:

enum Fruit {
  case apple(diameter: Double, color: String)
  case orange(diameter: Double)
  case grapefruit(diameter: Double)
  case banana

  var circumference: Double? {
    switch self {
    case .apple(let diameter, _),
         .orange(let diameter),
         .grapefruit(let diameter):
      return diameter * PI
    case .banana:
      return nil
    }
  }

  var color: String {
    switch self {
    case .apple(_, let color): return color
    case .orange: return "orange"
    case .grapefruit: return "pink"
    case .banana: return "yellow"
    }
  }
}

Regardless, it'd be a -1 for me because $0 leaves no information left about the type/content of the associated values. This to me is a classic example of going against the principle of clarity over brevity.

3 Likes

+1

Maybe not using $0, $1, etc, but automatic binding to some other variable?

There might be something here, so +1 from me.

As an alternative (or the two ways can coexist):

switch self {
    case let v = .apple:
        return v.0 * pi // positional argument, or
        return v.diameter * pi // named argument (if there's a name)
    .....
}

Edit: added the name argument use-case above for cases with named associated values.