I implemented a ternary operator like Java's <condition> ? <if true> : <if false>
, substituting /
for :
, since :
is not a valid identifier:
case class Ternary[T](val o: Option[T]) {
def / (f: => T) = o getOrElse f
}
implicit def boolToTernary(cond: Boolean) = new {
def ? [T](f: => T) = if(cond) Ternary(Some(f))
else Ternary[T](None)
}
It works fine in general, e.g.
scala> (1 > 2) ? "hi" / "abc"
res9: java.lang.String = abc
but falls down in the following case:
scala> (1 > 2) ? 5 / 6.0
<console>:33: error: type mismatch;
found : Double(6.0)
required: Int
(1 > 2) ? 5 / 6.0
^
Is there any tweaking I can do to the types in order to get this to work like the built-in if (1 > 2) 5 else 6.0
does? I googled for similar solutions and the implementations I found all exhibited the same behaviour.
One thing you can do is change your definition of /
to this:
def /[U >: T](f: => U) = o getOrElse f
It doesn't work like a regular if
(where the inferred type would be Double
) — you get AnyVal
, but that's already an improvement with a very small modification to your code.
Update: I think I've found a (slightly more complicated) way to make it behave more like a normal if (e.g., in this case, inferring a Double
). Try this code:
implicit def boolToTernary(cond: Boolean) = new {
def ?[T](f: => T) = if (cond) Ternary(Some(f))
else Ternary[T](None)
}
case class Ternary[T](val o: Option[T]) {
def /[U, That](f: => U)(implicit conv: BiConverter[T, U, That]): That = o map conv.toThat1 getOrElse (conv toThat2 f)
}
class BiConverter[T, U, That](val toThat1: T => That, val toThat2: U => That)
trait LowPriorityBiConverterImplicits {
implicit def subtype[A, T <: A, U <: A]: BiConverter[T, U, A] = new BiConverter[T, U, A](identity[T], identity[U])
}
object BiConverter extends LowPriorityBiConverterImplicits {
implicit def identityConverter[T]: BiConverter[T, T, T] = new BiConverter[T, T, T](identity, identity)
implicit def firstAsSecond[T, U](implicit conv: T => U): BiConverter[T, U, U] = new BiConverter[T, U, U](conv, identity)
implicit def secondAsFirst[T, U](implicit conv: U => T): BiConverter[T, U, T] = new BiConverter[T, U, T](identity, conv)
}
Then (some sample code):
abstract class Fruit
class Apple extends Fruit
class Banana extends Fruit
def main(args: Array[String]) {
val int = (1 > 2) ? 5 / 6 // Int is inferred
val fruit = (1 > 2) ? new Apple / new Banana // Fruit is inferred
val double1 = (1 > 2) ? 5 / 5.5 // Double is inferred
val double2 = (1 > 2) ? 5.5 / 5 // Double is inferred
}
Here is my version just cause I'm curious. I wouldn't really use this...
I chose low priority operators. ^
will bind first, then |?
. I use the fact that tuples are covariant to infer the type of the result.
case class TernClause[T](t: T) {
def ^[U](u: U) = (t, u)
}
case class Tern(b: Boolean) {
def |?[U](tuple: (U,U)) = if (b) tuple._1 else tuple._2
}
implicit def toTern(b: Boolean): Tern = Tern(b)
implicit def toTernClause[T](t: T): TernClause[T] = TernClause(t)
(1 > 2) |? "hi" ^ "abc"
// java.lang.String = abc
(1 > 2) |? 5 ^ 6.0
// AnyVal{def getClass(): java.lang.Class[_ >: Double with Int <: AnyVal]} = 6.0
Another example showing how operator precedence works together:
3 > 2 |? 5 - 1 ^ 6.0 + 1
// AnyVal{def getClass(): java.lang.Class[_ >: Double with Int <: AnyVal]} = 4
Probably some work needed to ensure the unused branch does not get evaluated.
Just food for thoughts: note that type inference does the right thing when calling a function. Would a less flexible syntax work for you? It's really a lot simpler...
class BooleanEx(b: Boolean) {
def ?[U](onTrue: => U, onFalse: => U) = if (b) onTrue else onFalse
}
implicit def toBooleanEx(b: Boolean): BooleanEx = new BooleanEx(b)
class A
class B extends A
(1 > 2) ? ("hi", "abc")
//res0: java.lang.String = abc
(1 > 2) ? (5, 6.0)
//res1: Double = 6.0
(1 > 2) ? (5, 6)
//res2: Int = 6
(1 > 2) ? (new A, new B)
//res3: A = B@1e21540
Also, this is available in scalaz but they name it fold
:
import scalaz._
import Scalaz._
(1 > 2) fold (5, 6.0)
//res0: Double = 6.0
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With