How to define a ternary operator in Scala which preserves leading tokens?
Asked Answered
G

4

9

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

Grits answered 24/4, 2010 at 19:54 Comment(2)
e.g. something using '_' in place of b:Boolean would be nice, but I couldn't get anything to work rightGrits
See BooleanW#? from Scalaz: scalaz.googlecode.com/svn/continuous/latest/browse.sxr/scalaz/…Floatation
O
16

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 |?.)

Orrin answered 24/4, 2010 at 20:44 Comment(6)
Doh, too slow. I was in the progress of typing something similar to this up, only to get the dreaded "2 new answers" messageParvis
@Jackson: Interesting alternatives are still worthwhile!Orrin
Nice! Only thing I'll change is ? to |? to get the lowest operator priority.Grits
@Alex: Just keep in mind that this has a lot more overhead than an if-statement. Performance-critical code should still use ifs (which are ~40x faster in my microbenchmarks--but it's ~40 ns vs. ~1 ns on a modern 3GHzish machine, so you'd really need to be doing this many millions of times per second before you'd care).Orrin
Did you enable optimizations when benchmarking? Shouldn't inlining get rid of the overhead? Possibly making some classes/methods final might help compile-time inlining (which the Scala compiler does).Akkerman
BTW, you begin by mentioning a problem with order of evaluation, but I don't understand your remark. Your code should perform the evaluation in the right order. BTW, you don't need to pass the boolean around by-name, since it must be evaluated anyway.Akkerman
C
13

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.

Caterer answered 7/8, 2011 at 19:27 Comment(2)
This is my favourite answer. Your explanation is as simple as the solution.Frampton
This solves a different problem, not the one stated in the question.Grits
D
5

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.

Downswing answered 24/4, 2010 at 20:45 Comment(0)
C
2

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
Calceiform answered 14/11, 2014 at 9:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.