95

I am trying to avoid constructs like this:

val result = this.getClass.getSimpleName
if (result.endsWith("$")) result.init else result

Ok, in this example the then and else branch are simple, but you can image complex ones. I built the following:

object TernaryOp {
  class Ternary[T](t: T) {
    def is[R](bte: BranchThenElse[T,R]) = if (bte.branch(t)) bte.then(t) else bte.elze(t)
  }
  class Branch[T](branch: T => Boolean) {
    def ?[R] (then: T => R) = new BranchThen(branch,then)
  }
  class BranchThen[T,R](val branch: T => Boolean, val then: T => R)
  class Elze[T,R](elze: T => R) {
    def :: (bt: BranchThen[T,R]) = new BranchThenElse(bt.branch,bt.then,elze)
  }
  class BranchThenElse[T,R](val branch: T => Boolean, val then: T => R, val elze: T => R)
  implicit def any2Ternary[T](t: T) = new Ternary(t)
  implicit def fct2Branch[T](branch: T => Boolean) = new Branch(branch)
  implicit def fct2Elze[T,R](elze: T => R) = new Elze(elze)
}

Defined that, I can replace the above simple example with:

this.getClass.getSimpleName is {s: String => s.endsWith("$")} ? {s: String => s.init} :: {s: String => s}

But how can I get rid of the s: String =>? I want something like that:

this.getClass.getSimpleName is {_.endsWith("$")} ? {_.init} :: {identity}

I guess the compiler needs the extra stuff to infer types.

Peter Schmitz
  • 5,824
  • 4
  • 26
  • 48
  • Since I didn't actually have this in my answer--the reason you're having trouble is that type inference works best from left to right, but you're binding your tokens together from right to left because of operator precedence. If you make all of your statements words (with the same precedence) and change the way things group together, you'll get the inference that you want. (I.e. you would have `HasIs`, `IsWithCondition`, `ConditionAndTrueCase` classes that would build up parts of the expression from left to right.) – Rex Kerr Feb 09 '11 at 19:05
  • I unconsciously presumed the way of type inference from left to right, but stucked with operator precedence and associativity of method names, especially starting with `?` before any other alphanum char as a method name first char and a `:` for left associativity. So I have to rethink of new method names to get type inference working from left to right. thanks! – Peter Schmitz Feb 09 '11 at 22:39

5 Answers5

126

From Tony Morris' Lambda Blog:

I hear this question a lot. Yes it does. Instead of c ? p : q, it is written if(c) p else q.

This may not be preferable. Perhaps you’d like to write it using the same syntax as Java. Sadly, you can’t. This is because : is not a valid identifier. Fear not, | is! Would you settle for this?

c ? p | q

Then you’ll need the following code. Notice the call-by-name (=>) annotations on the arguments. This evaluation strategy is required to correctly rewrite Java’s ternary operator. This cannot be done in Java itself.

case class Bool(b: Boolean) {   
  def ?[X](t: => X) = new {
    def |(f: => X) = if(b) t else f   
  } 
}

object Bool {   
  implicit def BooleanBool(b: Boolean) = Bool(b) 
}

Here is an example using the new operator that we just defined:

object T {   val condition = true

  import Bool._

  // yay!   
  val x = condition ? "yes" | "no"
}

Have fun ;)

Cedric Reichenbach
  • 8,970
  • 6
  • 54
  • 89
Landei
  • 54,104
  • 13
  • 100
  • 195
  • yes, I have seen this before, but the difference is, that a I have the (evaluated) value of my first expression as an argument in the `then` and `else` clause. – Peter Schmitz Feb 09 '11 at 22:32
  • 5
    I took the `if(c) p else q` approach... the lack of braces makes me a touch uncomfortable but that's just a style thing – rjohnston Jul 11 '13 at 14:27
28

We can combine How to define a ternary operator in Scala which preserves leading tokens? with the answer to Is Option wrapping a value a good pattern? to get

scala>   "Hi".getClass.getSimpleName |> {x => x.endsWith("$") ? x.init | x}
res0: String = String

scala> List.getClass.getSimpleName |> {x => x.endsWith("$") ? x.init | x}
res1: String = List

Is this adequate for your needs?

Community
  • 1
  • 1
Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
  • Thats very close to what I have in mind. nice approach. I´ll think about that. My reason to avoid the very first code was to be more concise in not having a temporary `val` for a following `if`-statement: Do it intelligible in one line, just like one have it in mind. – Peter Schmitz Feb 09 '11 at 22:48
17

Rex Kerr’s answer expressed in basic Scala:

"Hi".getClass.getSimpleName match {
  case x if x.endsWith("$") => x.init
  case x => x
}

although I’m not sure what part of the if–else construct you want to optimise.

Community
  • 1
  • 1
Debilski
  • 66,976
  • 12
  • 110
  • 133
  • very straight way. sometimes one forgets about the daily-use match/case statements. I just sticked to the one line ternary `if then else` idiom, but it´s indeed a intelligible way to solve. – Peter Schmitz Feb 09 '11 at 22:44
  • 1
    Pattern Matching scales easily to more than two branches. – Raphael Feb 10 '11 at 13:13
11

Since if-else constructions in Scala return a value, you can use this

val a = if (1 < 0) 1 else 2

More info: https://alvinalexander.com/scala/scala-if-then-ternary-operator-cookbook-examples

Wouter
  • 1,829
  • 3
  • 28
  • 34
0

Since : by itself won't be a valid operator unless you are ok with always escaping it with back ticks :, you could go with another character, e.g. "|" as in one of the answers above. But how about elvis with a goatee ?::

implicit class Question[T](predicate: => Boolean) {
  def ?(left: => T) = predicate -> left
}
implicit class Colon[R](right: => R) {
  def ::[L <% R](pair: (Boolean, L)): R = if (q._1) q._2 else right
}
val x = (5 % 2 == 0) ? 5 :: 4.5

Of course this again won't work if you values are lists, since they have :: operator themselves.

Ustaman Sangat
  • 1,505
  • 1
  • 14
  • 26