How to define a ternary operator in Scala which pr

2020-03-12 04:51发布

问题:

I'm writing a code generator which produces Scala output.

I need to emulate a ternary operator in such a way that the tokens leading up to '?' remain intact.

e.g. convert the expression c ? p : q to c something. The simple if(c) p else q fails my criteria, as it requires putting if( before c.

My first attempt (still using c/p/q as above) is

c match { case(true) => p; case _ => q }

another option I found was:

class ternary(val g: Boolean => Any) { def |: (b:Boolean) = g(b) }

implicit def autoTernary (g: Boolean => Any): ternary = new ternary(g)

which allows me to write:

c |: { b: Boolean => if(b) p else q }

I like the overall look of the second option, but is there a way to make it less verbose?

Thanks

回答1:

Even though the syntax doesn't evaluate in the expected order--it binds the conditional to the first option!--you can make your own ternary operator like this:

class IfTrue[A](b: => Boolean, t: => A) { def |(f: => A) = if (b) t else f }
class MakeIfTrue(b: => Boolean) { def ?[A](t: => A) = new IfTrue[A](b,t) }
implicit def autoMakeIfTrue(b: => Boolean) = new MakeIfTrue(b)

The trick is to interpret ? as a method on a MakeIfTrue object that binds the condition to the object to return in the "true" case. The resulting IfTrue object now uses the | method as a request to evaluate the condition, returning the stored true option if the condition is true, or the just-passed-in one if it's false.

Note that I've used stuff like => A instead of just A--by-name parameters--in order to not evaluate the expression unless it's actually used. Thus, you'll only evaluate the side that you actually need (just like an if statement).

Let's see it in action:

scala> List(1,3,2).isEmpty ? "Empty" | "Nonempty"
res0: java.lang.String = Nonempty

scala> (4*4 > 14) ? true | false
res1: Boolean = true

scala> class Scream(s: String) { println(s.toUpperCase + "!!!!") }
defined class Scream

scala> true ? new Scream("true") | new Scream("false")
TRUE!!!!
res3: Scream = Scream@1ccbdf7

(P.S. To avoid confusion with the Actor library ?, you probably ought to call it something else like |?.)



回答2:

Let's keep it simple:

Java:

tmp = (a > b) ? a : b;

Scala:

tmp = if (a > b) a else b

Besides simplicity, it is clear and fast because: do not allocate objects you don't need, keeps the garbage collector out of equation (as it always should be) and makes better use of processor caches.



回答3:

You could use something like this

sealed trait TernaryOperand[A] {
  def >(q: => A): A
}

case class TernarySecond[A](val p: A) extends TernaryOperand[A] {
  def >(q: => A) = p
}

case class TernaryThird[A]() extends TernaryOperand[A] {
  def >(q: => A) = q
}

implicit def ternary(c: Boolean) = new {
  def ?[A](p: => A): TernaryOperand[A] = if (c) TernarySecond(p) else TernaryThird()
}

val s1 = true ? "a" > "b"
println(s1) //will print "a"

val s2 = false ? "a" > "b"
println(s2) //will print "b"

This code converts any boolean value to an anonymous type that has a method called ?. Depending on the value of the boolean, this method will either return TernarySecond or TernaryThird. They both have a method called > which returns the second operand or the third one respectively.



回答4:

Ternary operator which adds my improvement to the best of Rex Kerr’s and Michel Krämer’s implementations:

  • My improvement to use Scala’s new value class to avoid boxing overhead.
  • Call by-name on 2nd and 3rd operands so only the chosen one is evaluated.
  • Michel’s call by-value on the 1st (Boolean) operand to avoid by-name overhead; it is always evaluated.
  • Rex’s concrete class for the condition to avoid any anonymous class overhead.
  • Michel’s evaluation of the condition to determine which class to construct to avoid of overhead of a two argument constructor.

.

sealed trait TernaryResult[T] extends Any {
  def |(op3: => T): T
}

class Ternary2ndOperand[T](val op2: T) extends AnyVal with TernaryResult[T] {
  def |(op3: => T) = op2
}

class Ternary3rdOperand[T](val op2: T) extends AnyVal with TernaryResult[T] {
  def |(op3: => T) = op3
}

class Ternary(val op1:Boolean) extends AnyVal {
   def ?[A](op2: => A): TernaryResult[A] = if (op1) new Ternary2ndOperand(op2) else new Ternary3rdOperand(op2)
}

object Ternary {
   implicit def toTernary(condition: Boolean) = new Ternary(condition)
}

Note the improvement over if else is not just the 6 characters saved. With Scala IDE’s syntax coloring on keywords being the same (e.g. purple) for if, else, null, and true, there is better contrast in some cases (which isn't shown by the syntax coloring below as currently rendered on this site):

if (cond) true else null
cond ? true | null