DEV Community

Discussion on: Implementing a Ternary Operator in Scala

Collapse
 
dividedbynil profile image
Kane Ong • Edited

I found the same inconvenience when working with Scala, the ternary operator just does not exist. Thanks for sharing your interesting implementations.

My opinion is, I tend to separate Either and ternary operator since it loses the conciseness when used together.

My preference:

object Implicits {
  implicit class Ternable (condition: Boolean) {
    def ? [T](ifTrue: => T): Option[T] = {
      if (condition) Some(ifTrue) else None
    }
  }
  implicit class Colonable [T](ifFalse: => T) {
    def |: (intermediate: Option[T]): T =
      intermediate match {
        case Some(ifTrue) => ifTrue
        case None => ifFalse
      }
  }
}

import Implicits._

object Main {
  def main(args: Array[String]): Unit = {
    val a = (3 > 2) ? "true" |: "false"
    val b = (3 < 2) ? "true" |: "false"

    val c = (1 > 0) ? 1 |: 0
    val d = (1 < 0) ? 1 |: 0

    println(s"a: $a\nb: $b\nc: $c\nd: $d")
  }
}

Collapse
 
awwsmm profile image
Andrew (he/him) • Edited

This is nicer, but it only works if ifTrue and ifFalse have the same type. You could do something like

object Implicits {
  implicit class Ternable (condition: Boolean) {
    def ? [T](ifTrue: => T): Option[T] = {
      if (condition) Some(ifTrue) else None
    }
  }
  implicit class Colonable [T, F](ifFalse: => F) {
    def |: (intermediate: Option[T]): Either[T, F] =
      intermediate match {
        case Some(ifTrue) => Left(ifTrue)
        case None => Right(ifFalse)
      }
    def &:(intermediate: Option[F]): F =
      intermediate match {
        case Some(ifTrue) => ifTrue
        case None => ifFalse
      }
  }
}

import Implicits._

val diff1 = (3 > 2) ? 42 |: 42L    // Either[Int,Long] = Left(42)
val diff2 = (3 < 2) ? 42 |: 42L    // Either[Int,Long] = Right(42)

val same1 = (3 > 2) ? "gt" &: "lt" // String = gt
val same2 = (3 < 2) ? "lt" &: "gt" // String = gt

This notation has the benefit that | is often used for type unions (in match statements), which is like an OR for types, while & could be used for type intersections, a type AND.

Collapse
 
dividedbynil profile image
Kane Ong • Edited

I don't oppose the idea of using sum type in ternary operator, you just delegate it to the next procedure to do the pattern matching and there will be no benefit than just using if then else.

Thread Thread
 
dividedbynil profile image
Kane Ong • Edited

Just for comparison, compare this

object Main {
  def main(args: Array[String]): Unit = {

    val n = 0

    if (n == 0) 
      println(s"$n == 0 is true")
    else 
      println(s"${3/n}") 

  }
}

with this

import Implicits._

object Main {
  def main(args: Array[String]): Unit = {

    val n = 0

    ((n == 0) ? true |: n) match {
      case Left(v) => println(s"$n == 0 is $v")
      case Right(v) => println(s"${3/v}") 
    } 

  }
}

The conciseness is obvious.

Thread Thread
 
awwsmm profile image
Andrew (he/him) • Edited

Fair enough! So what you're saying is you'd have the ifTrue and ifFalse themselves return Eithers, if necessary, like

(3 < 2) ? Left(42) |: Right("forty-two")

Is that right?

I wonder if there's an implicit conversion that would let us do away with the Left() and Right()...

Thread Thread
 
dividedbynil profile image
Kane Ong • Edited

That is a good example to ensure type consistency with minimal effort, you don't need |: and &: at the same time and achieve the same result (Option[Either[T, F]]).

One of my main point here is do not over obsess with data type generalization and construct unnecessary structures.