Reputation: 11881
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
Upvotes: 9
Views: 6612
Reputation: 6131
Ternary operator which adds my improvement to the best of Rex Kerr’s and Michel Krämer’s implementations:
.
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
Upvotes: 2
Reputation: 6084
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.
Upvotes: 13
Reputation: 14637
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.
Upvotes: 5
Reputation: 167871
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 |?
.)
Upvotes: 16