【问题标题】:Ternary operator typing三元运算符类型
【发布时间】:2011-12-17 15:08:34
【问题描述】:

我实现了一个像 Java 的 <condition> ? <if true> : <if false> 这样的三元运算符,用 / 代替 :,因为 : 不是一个有效的标识符:

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)
}

一般来说效果很好,例如

scala> (1 > 2) ? "hi" / "abc"
res9: java.lang.String = abc

但在以下情况下会摔倒:

scala> (1 > 2) ? 5 / 6.0
<console>:33: error: type mismatch;
 found   : Double(6.0)
 required: Int
       (1 > 2) ? 5 / 6.0
                     ^

我可以对类型进行任何调整,以使其像内置的if (1 &gt; 2) 5 else 6.0 那样工作吗?我搜索了类似的解决方案,发现所有实现都表现出相同的行为。

【问题讨论】:

    标签: scala types ternary-operator


    【解决方案1】:

    这是我的版本只是因为我很好奇。我不会真的用这个...

    我选择了低优先级的运算符。 ^ 将首先绑定,然后是 |?。我使用元组是协变的这一事实来推断结果的类型。

    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
    

    另一个展示运算符优先级如何协同工作的示例:

    3 > 2 |? 5 - 1 ^ 6.0 + 1
    // AnyVal{def getClass(): java.lang.Class[_ >: Double with Int <: AnyVal]} = 4
    

    可能需要做一些工作来确保未使用的分支不会被评估。

    提神醒脑:请注意,类型推断在调用函数时是正确的。不太灵活的语法对您有用吗?真的简单多了……

    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
    

    此外,这在 scalaz 中可用,但他们将其命名为 fold

    import scalaz._
    import Scalaz._
    (1 > 2) fold (5, 6.0)
    //res0: Double = 6.0
    

    【讨论】:

    • 很好,但推断的返回类型仍然是 AnyVal 而不是 Double,因为它会在常规 if 中......
    • 对于这个更简单的版本,(1 &gt; 2) ? (BigInt(3), 6) 返回 Any = 6,而 Jean-Philippe 的版本返回 BigInt = 6
    • @LuigiPlinge 是的,我认为额外的复杂性不值得,但我得表扬一下,我没有意识到 JPP 的解决方案能够利用像 BigInt.int2bigInt 这样的隐式转换。 +1。
    【解决方案2】:

    您可以做的一件事是将/ 的定义更改为:

    def /[U >: T](f: => U) = o getOrElse f
    

    它不像普通的if(推断的类型是Double)那样工作——你会得到AnyVal,但这已经是一个改进,只需对你的代码进行非常小的修改。

    更新:我想我找到了一种(稍微复杂一点)让它表现得更像正常 if 的方法(例如,在这种情况下,推断出 Double)。试试这个代码:

    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)
    }
    

    然后(一些示例代码):

    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
    }
    

    【讨论】:

      【解决方案3】:

      我一直在玩@Jean-Philippe 的解决方案,并添加了一些内容以允许链接运算符。 (好吧,我本可以保持原样并使用括号,但这样做的乐趣在哪里?)可能有更好的方法来做到这一点,因此欢迎提出改进建议。

      implicit def boolToTernary(cond: Boolean) = new {
        // operator name changed
        def |? [T](f: => T) = if (cond) Ternary(Some(f)) else Ternary[T](None)
      }
      
      case class Ternary[T](o: Option[T]) {
        def or [U, That](f: => U)      (implicit conv: BiConverter[T, U, That]): That = o map conv.toThat1 getOrElse (conv toThat2 f)
        // overload added
        def or [U, That](t: Ternary[U])(implicit conv: BiConverter[T, U, That]): Ternary[That] = o match {
          case x: Some[_] => Ternary(o map conv.toThat1)
          case None       => Ternary(t.o map conv.toThat2)
        }
      }
      

      我更改了运算符名称:Ternary 类中的运算符需要低优先级,但隐式 def 中的运算符也需要低优先级,| 除了字母数字以外的优先级最低。

      我还添加了一个重载,以便我们可以采用另一个三元子句。

      因此

      1 > 2 |? 4 or 4 > 6 |? 8.0 or 10  //look, no parentheses!
      // Double = 10.0
      
      1 + 1 < 3 |?
        4L or
        4 > 6 |?
          8 or BigInt(10)
      // scala.math.BigInt = 4
      

      酷还是什么? :) 谁需要if / else

      【讨论】:

        猜你喜欢
        • 2017-01-20
        • 1970-01-01
        • 2017-08-24
        • 2012-01-22
        • 2014-10-03
        相关资源
        最近更新 更多