Shorthand ternary and switch assignments

Many times I use self-executing closure to assign a value, such as:

let user = {
    guard let region = region, let group = group else { return User() }
    return User(region: region, group: group)
}()

let suiteName = {
    switch Environment.mode {
    case .development: return "group.com.example.dev"
    case .staging: return "group.com.example.staging"
    case .production: return "group.com.example.live"
    }
}()

let realm: Realm
do { realm = try Realm() }
catch { return completion(.failure(.databaseFailure(error))) }

A shorthand assignment of these would be very convenient:

let user = let region = region, let group = group
    ? User(region: region, group: group)
    : User()

let suiteName = switch Environment.mode {
    case .development: return "group.com.example.dev"
    case .staging: return "group.com.example.staging"
    case .production: return "group.com.example.live"
}

do let realm = try Realm() catch {
    return completion(.failure(.databaseFailure(error)))
}

What do you think.. Swifty or nay?

UPDATE: Disregard guard/do/try from discussion since proposed before.

1 Like

I'm not sure whether I like the first two, but what about changing the last to be an alternative to guard-else? That is:

guard let realm = try Realm() catch {
    return completion(.failure(.databaseFailure(error)))
}

guard would keep the same requirement that the body must return, or otherwise exit the enclosing scope.

3 Likes

Fairly sure this was proposed before. Can't remember where it wound up.

Edit:

Here are a couple:


I think one complication that needed straightening out was what happens when the throwing operation returns an optional?

Does the guard-let unwrap it? Do you then need to support guard-let-try-catch-else ??

Thanks @karim for recalling that and finding it! I've updated the title and post to only discuss the first 2 examples.

The first two examples seem to only be held back by the current capabilities of the type checker. There is, however, a want to make control statements (including loops) return values.

So the things you're proposing would be very close to that, though your first option might look a bit different:

let user = if let region, let group = group {
  return User(region, group)
} else {
  return User()
}

There's probably still design needed for this, like possibly changing return to yield or something, but I think this feature is not exactly a top priority right now :(

I'd be very happy to see control flow statements work as expressions. I'm referring to if/else and switch mostly, I think loops should rather be handled by generators/coroutines, and haven't thought about other types of statement.

I don't know anything about the type checker internals, but I tried around a bit to find the current limitations:

func if_f<T>(_ cond: Bool, then: () -> T, else e: () -> T) -> T {
    if cond { return then() } else { return e() }
} 

// this works fine
let v = if_f(1 == 2, then: { "lol" }, else: { "lel" })

// this works as well, bc the contextual type from print is Any
print(if_f(1 == 1, then: { "lol" }, else: { 1 }))

// however, this doesn't
let v2 = if_f(1 == 1, then: { "lol" }, else: { 1 })

// unless we give the type checker a little help:
let v3 = if_f(1 == 1, then: { "lol" }, else: { 1 }) as Any

So it might be possible to do at least the simple case, where all branches have the same static type, without type checker improvements?

Wrt syntax, I think using return here would unnecessarily overload the meaning of the keyword. yield should probably be left for coroutines/generators. I'd rather just have no special syntax, the way Rust, Ruby, F# and I'm sure other languages work:

let user = if let region, let group = group {
    User(region, group)
} else {
    User()
}

let suiteName = switch Environment.mode {
    case .development: "group.com.example.dev"
    case .staging: "group.com.example.staging"
    case .production: "group.com.example.live"
}
1 Like

print() takes an [Any].

1 Like

No, it takes an Any... which isn't exactly the same as [Any] although it would be nice if one could convert from the latter to the former.

func f(x: Any...) {
    print(type(of: x))
}

f(x: 1)

The output is Array<Any>.

Any news on this feature? It would be very handy to have it