Aaron Hayman
Aaron Hayman

Reputation: 8502

Swift 2 value extraction from Enum

Prior to Swift 2, I would often use enums with associated values and add functions to extract specific values, like so:

public enum Maybe <T> {
  case Unknown
  case Known(T)

  public var value: T? {
    switch self {
    case .Unknown: return nil
    case .Known(let value): return value
    }
  }
}

This would allow me to do something like this:

let maybe = .Known("Value")
let val = maybe.value ?? "Another Value"

I would like to get rid of these convenience functions and rely on Swift 2's new syntax. This is possible doing something like:

let val: String
if case .Known(let value) = maybe {
  val = value
} else {
  val = "Another Value"
}

But I can't figure out how to condense this back into a single line using the ?? operator or even ternary operator.

Is this even possible or am I stuck with defining "extraction" optionals on the enum?

Update (clarification)

The Maybe enum is just an example, but the solution would need to work on Enums that have multiple associated values... like an Either:

public enum Either<L, R> {
  case Left(Box<L>)
  case Right(Box<R>)

  public func left() -> L?{
    switch self {
    case let Left(value):
      return value.value
    default:
      return nil
    }
  }

  public func right() -> R?{
    switch self {
    case let Right(value):
      return value.value
    default:
      return nil
    }
  }
}  

The syntax I'm looking for would be something like:

let val = (case .Known(let value) = maybe) ?? "AnotherValue"

What I want to do is easily extract an associated value for a specific case, else provide a default.

For Either it might be something like:

let val = (case .Left(let value) = either) ?? "AnotherValue"

Make sense?

Upvotes: 1

Views: 268

Answers (1)

Rob Napier
Rob Napier

Reputation: 299275

The syntax you want isn't possible in Swift today (and feels unlikely for Swift tomorrow, but I often am surprised). The best available solutions are extraction functions like left() -> L? and right() -> R?. case is not a generic value-returning function that you can extend. See Rob Rix's Either for some of the best current thinking on this problem.

A key choice in Swift is that there are many statements that are not expressions. switch is one of them. Until Swift makes things like switch and if be expressions, it will be very hard to build this kind of syntax IMO.


Just define ?? for it:

func ??<T>(lhs: Maybe<T>, @autoclosure defaultValue: () throws -> T) rethrows -> T {
    switch lhs {
    case .Unknown: return try defaultValue()
    case .Known(let value): return value
    }
}


let maybe = Maybe.Known("Value")

let val = maybe ?? "Another Value"

Doing it this way gives us some nice features in Swift2. For instance, we can lazily evaluate the rhs, and we can handle throwing in the rhs:

func computeIt() -> String {
    print("LAZY!")
    return "Computed"
}

maybe ?? computeIt() // Does not print "LAZY!"
Maybe.Unknown ?? computeIt() // Does print "LAZY!"

enum Error: ErrorType {
    case Failure
}
func fail() throws -> String {
    throw Error.Failure
}

try maybe ?? fail() // Doesn't throw

do {
    try Maybe.Unknown ?? fail() // throws
} catch {
    print("THROW")
}

Upvotes: 3

Related Questions