[Pitch] Use "where" in combination with "??" to provide Ternary-like functionality


(Charles Constant) #1

Would anyone be interested in "where" (or a similar keyword or operator)
being able to do this:

    *let val = "foo" where true* // "foo"
    *let val = "foo" where false* // nil

... and therefore being able to work in conjunction like this:

* let val = *
* "positive" where ( val > 0 ) ?? *
* "negative" where ( val < 0 ) ?? *
* "zero"*

In other words, a statement that given a value and a bool, and returns the
value if the bool is true, but nil if the bool is false.

There have been a few threads here about "switch assignment" and "extended
ternary" statements. I encounter frustrations regularly when I want to
write concise, but easy-to-read code for conditions that require more to
than two options. We have discussed problems using dicts, enums, etc in the
past.

The problem with most of the suggestions so far is that they aren't very
Swifty. But if "where" worked the way I'm pitching, a coder could impliment
most of the alternatives we've discussed (by using autoclosure, and custom
operators). Eg:

* let val:FooEnum = depends( bar ){*
* .Red where $0 == .A ?? *
* .Blue where $0 == .B ?? *
* .Green where $0 == .C ?? *

* .None*
* }*

This strikes me as the least disruptive, and most Swifty solution.

Would this appeal to anyone else?


(Charles Constant) #2

Correction. My example reused "val" This is what I should have typed:

* let foo = *
* "positive" where ( bar > 0 ) ?? *
* "negative" where ( bar < 0 ) ?? *
* "zero"*

Sorry for that. My mind seems to switch itself off whenever I submit to
this list :slight_smile:

Also, since my previous posts to this list about ternary and switch
assignment, etc. I now feel that including the variable name in the bool
side is useful. It would be a quick way to do the following sort of thing:

* let foo = *
* "positive" where ( bar > 0 ) ?? *
* "negative" where ( bar < 0 ) ?? *
* "animal zero" where ( is_animal == true ) ?? *
* "normal zero"*


(Charles Constant) #3

Here's a few examples of what this change would allow.

I just plucked the first instances of other people's switch statements that
I found on GitHub.

If there were an easy way to search GitHub for chained ternary expressions,
I would have added some examples of those too, since they could all be
improved with this where clause + ??.

mutating func toggle() {

switch self{

case Off:

self = On

case On:

self = Off

}

}

mutating func toggle() {

self = .On where (self == .Off) ?? .Off

}

switch switchNumberThree {

case 10, 11, 12:

println("It is \(switchNumberThree)")

default:

("It is none of them!")

}

println(

"It is \(switchNumberThree)" where 10...12 ~= switchNumberThree

?? "It is none of them!"

)

switch x {

case 1:

j++

case 2:

j++

case 3:

j++

case 4:

j++

fallthrough

case 5:

j++

fallthrough

default:

j++

}

j = j+1 where (4...5 ~= x) ?? j+2


#4

Why reinvent the wheel, when the old trusty (but a bit cryptic according to some) tri-op can do the trick…

Here's a few examples of what this change would allow.

I just plucked the first instances of other people's switch statements that I found on GitHub.

If there were an easy way to search GitHub for chained ternary expressions, I would have added some examples of those too, since they could all be improved with this where clause + ??.

  mutating func toggle() {
    switch self{
    case Off:
      self = On
    case On:
      self = Off
    }
  }

  mutating func toggle() {
    self = .On where (self == .Off) ?? .Off
  }

mutating func toggle() { self = self == .Off ? .On : .Off }

  switch switchNumberThree {
    case 10, 11, 12:
      println("It is \(switchNumberThree)")
    default:
      ("It is none of them!")
  }

  println(
    "It is \(switchNumberThree)" where 10...12 ~= switchNumberThree
    ?? "It is none of them!"
  )

print( 10...12 ~= switchNumberThree ? "It is \(switchNumberThree)"
       : "It's none of them" )

  switch x {
  case 1:
    j++
  case 2:
    j++
  case 3:
    j++
  case 4:
    j++
    fallthrough
  case 5:
    j++
    fallthrough
  default:
    j++
  }

  j = j+1 where (4...5 ~= x) ?? j+2

Broken conversion:
j += 4...5 ~= x ? 1 : 2

Proper conversion:
j += 4 ~= x ? 3 : 5 ~= x ? 2 : 1

Earlier e-mail example:

    let foo =
        "positive" where ( bar > 0 ) ??
        "negative" where ( bar < 0 ) ??
        "zero"

let foo = bar > 0 ? "positive" :
          bar < 0 ? "negative" :
          "zero"

Dany

···

Le 23 mai 2016 à 04:29, Charles Constant via swift-evolution <swift-evolution@swift.org> a écrit :


(charles@charlesism.com) #5

I'm not actually familiar with the term "tri op" but if you're referring to the ternary, it's only useful when you two, or three items.

If you chain a ternary to use more than three options it becomes error-prone and almost impossible for a human to read

When I'm at my desktop I'll add a couple better examples of what I'm proposing.

···

Sent from my iPhone

On May 23, 2016, at 6:18 PM, Dany St-Amant <dsa.mls@icloud.com> wrote:

Why reinvent the wheel, when the old trusty (but a bit cryptic according to some) tri-op can do the trick…

Le 23 mai 2016 à 04:29, Charles Constant via swift-evolution <swift-evolution@swift.org> a écrit :

Here's a few examples of what this change would allow.

I just plucked the first instances of other people's switch statements that I found on GitHub.

If there were an easy way to search GitHub for chained ternary expressions, I would have added some examples of those too, since they could all be improved with this where clause + ??.

  mutating func toggle() {
    switch self{
    case Off:
      self = On
    case On:
      self = Off
    }
  }

  mutating func toggle() {
    self = .On where (self == .Off) ?? .Off
  }

mutating func toggle() { self = self == .Off ? .On : .Off }

  switch switchNumberThree {
    case 10, 11, 12:
      println("It is \(switchNumberThree)")
    default:
      ("It is none of them!")
  }

  println(
    "It is \(switchNumberThree)" where 10...12 ~= switchNumberThree
    ?? "It is none of them!"
  )

print( 10...12 ~= switchNumberThree ? "It is \(switchNumberThree)"
       : "It's none of them" )

  switch x {
  case 1:
    j++
  case 2:
    j++
  case 3:
    j++
  case 4:
    j++
    fallthrough
  case 5:
    j++
    fallthrough
  default:
    j++
  }

  j = j+1 where (4...5 ~= x) ?? j+2

Broken conversion:
j += 4...5 ~= x ? 1 : 2

Proper conversion:
j += 4 ~= x ? 3 : 5 ~= x ? 2 : 1

Earlier e-mail example:

    let foo =
        "positive" where ( bar > 0 ) ??
        "negative" where ( bar < 0 ) ??
        "zero"

let foo = bar > 0 ? "positive" :
          bar < 0 ? "negative" :
          "zero"

Dany


(Charles Constant) #6

Right... per "[swift-evolution] [Idea] Find alternatives to `switch self`"
it was only "impossible" in my head.

Thanks Dany

This proposal is unnecessary.

···

On Mon, May 23, 2016 at 8:53 PM, charles@charlesism.com < charlesism.com@gmail.com> wrote:

I'm not actually familiar with the term "tri op" but if you're referring
to the ternary, it's only useful when you two, or three items.

If you chain a ternary to use more than three options it becomes
error-prone and almost impossible for a human to read

When I'm at my desktop I'll add a couple better examples of what I'm
proposing.

Sent from my iPhone

On May 23, 2016, at 6:18 PM, Dany St-Amant <dsa.mls@icloud.com> wrote:

Why reinvent the wheel, when the old trusty (but a bit cryptic according
to some) tri-op can do the trick…

Le 23 mai 2016 à 04:29, Charles Constant via swift-evolution < > swift-evolution@swift.org> a écrit :

Here's a few examples of what this change would allow.

I just plucked the first instances of other people's switch statements
that I found on GitHub.

If there were an easy way to search GitHub for chained ternary
expressions, I would have added some examples of those too, since they
could all be improved with this where clause + ??.

mutating func toggle() {
switch self{
case Off:
self = On
case On:
self = Off
}
}

mutating func toggle() {
self = .On where (self == .Off) ?? .Off
}

mutating func toggle() { self = self == .Off ? .On : .Off }

switch switchNumberThree {
case 10, 11, 12:
println("It is \(switchNumberThree)")
default:
("It is none of them!")
}

println(
"It is \(switchNumberThree)" where 10...12 ~= switchNumberThree
?? "It is none of them!"
)

print( 10...12 ~= switchNumberThree ? "It is \(switchNumberThree)"
       : "It's none of them" )

switch x {
case 1:
j++
case 2:
j++
case 3:
j++
case 4:
j++
fallthrough
case 5:
j++
fallthrough
default:
j++
}

j = j+1 where (4...5 ~= x) ?? j+2

Broken conversion:
j += 4...5 ~= x ? 1 : 2

Proper conversion:
j += 4 ~= x ? 3 : 5 ~= x ? 2 : 1

Earlier e-mail example:

* let foo = *
* "positive" where ( bar > 0 ) ?? *
* "negative" where ( bar < 0 ) ?? *
* "zero"*

let foo = bar > 0 ? "positive" :
          bar < 0 ? "negative" :
          "zero"

Dany


(Brent Royal-Gordon) #7

Earlier e-mail example:

    let foo =
        "positive" where ( bar > 0 ) ??
        "negative" where ( bar < 0 ) ??
        "zero"

let foo = bar > 0 ? "positive" :
          bar < 0 ? "negative" :
          "zero"

See, this just makes me want to remix ternary...

  let foo = (bar > 0) :: "positive" ??
      (bar < 0) :: "negative" ??
      "zero"

  // :: is not actually legal to declare, but notionally...
  infix operator :: {
    associativity left
    precedence 133
  }
  func :: <B: BooleanType, T>(lhs: B, rhs: @autoclosure () -> T?) -> T? {
    guard lhs else {
      return nil
    }
    return rhs()
  }

`::` is an operator which evaluates the condition on the left and returns the right if true, or nil otherwise. You can chain `::`s together with the existing `??` operator. You can terminate the sequence with a trailing `?? value` to ensure you won't get a nil. Or you can leave off the trailing `??`, or even omit the chain of `??`s altogether if `::` by itself does what your code needs.

Actually, the main problem I see here is that `??`'s, and therefore `::`'s, precedence is too tight—it binds more tightly than the comparison operators. One solution might be to put `::` and `??` at their current high precedence, but also have `:` and `?` with the same semantics at a precedence level just above assignment...

···

--
Brent Royal-Gordon
Architechies


(Charles Constant) #8

It did occur to me to use "::" but it seemed a bit cruel to have the
equivalent of "? :" be reversed. Ie: by using ":: ??" If the team had
chosen :: to be the Nil Coalescing Operator, rather than ?? it would be
more tempting.

I had some problems messing around with these symbols. I think it was that
"??" is 131 and something else (maybe "==") was 130. Also the compiler can
be grumpy about long chains of things. I assume that will get better. Maybe
it has already gotten better, I'm still not on the latest version.

Anyhow, I think I'm fine just using the existing ternary... now that Dany
has made me realize I've been avoiding, any inefficiently mentally parsing,
long ternary chains for no good reason for the past decade. I'm fine, aside
from feeling disoriented!