问题 Scala中的链式比较


Python支持“链式比较”的优雅语法,例如:

0 <= n < 256

含义,

0 <= n and n < 256

在语法上知道它是一种相当灵活的语言,是否可以在Scala中模拟这个功能?


6035
2017-07-22 11:36


起源

马特,我建议你让亚历山大成为公认的答案。他提出了一些更优越的东西。 - Daniel C. Sobral
完成了。感谢您的回答,很高兴您可以在Scala中获得相当接近的内容,尽管通过两次比较和“&&”来显示版本可能更加清晰。 - Matt R


答案:


丹尼尔的答案是全面的,实际上很难添加任何东西。由于他的答案提出了他提到的一个选择,我只想加上我的2美分,并以另一种方式提出一个非常短的解决方案。丹尼尔的描述:

从理论上讲,你可以提供   其中一种方法的结果   另一种方法。我能想到两个   这样做的方式:

  • <=返回一个对象,它既包含接收到的参数又包含比较结果,并且<使用这两个值。

CmpChain 将作为已经与最右边的自由对象进行比较的累加器,以便我们可以将它与下一个对象进行比较:

class CmpChain[T <% Ordered[T]](val left: Boolean, x: T) {
  def <(y: T) = new CmpChain(left && x < y, y)
  def <=(y: T) = new CmpChain(left && x <= y, y)
  // > and >= are analogous

  def asBoolean = left
}

implicit def ordToCmpChain[T <% Ordered[T]](x: T) = new AnyRef {
  def cmp = new CmpChain(true, x)
}
implicit def rToBoolean[T](cc: CmpChain[T]): Boolean = cc.asBoolean

您可以将它用于任何有序类型,例如 Ints或 DoubleS:

scala> (1.cmp < 2 < 3 <= 3 < 5).asBoolean                          
res0: Boolean = true

scala> (1.0.cmp < 2).asBoolean
res1: Boolean = true

scala> (2.0.cmp < 2).asBoolean
res2: Boolean = false

隐性转换会产生 Boolean 它应该是:

scala> val b: Boolean = 1.cmp < 2 < 3 < 3 <= 10  
b: Boolean = false

8
2017-07-22 15:42



这是我提到的第一个选项的实现,它非常聪明。继续并复制我的解释,以便您的答案可以完成。我比你所做的更喜欢你想出的东西,如果你完成答案,我赞成优先考虑你的答案。但选择比“r”更好的东西。 :-) - Daniel C. Sobral
谢谢!我已经更新了答案。 - Alexander Azarov


不是真的。需要记住的是,除了几个关键字之外,Scala中的所有内容都是对象的方法调用。

所以我们需要在对象上调用“<=”和“<”方法,并且每个这样的方法都接收一个参数。那么,你需要四个对象,有三个显式对象,两个隐含对象 - 每个方法的结果。

理论上,您可以将其中一个方法的结果提供给另一个方法。我可以想到两种方法:

  • <=返回一个对象,它既包含接收到的参数又包含比较结果,并且<使用这两个值。

  • <=返回false或接收到的参数,并且<失败或与其他参数进行比较。这可以使用Either类或基于它的东西来完成。

事实上,这两种解决方案非常相似。

一个问题是这种比较运算符的消费者期望布尔结果。这实际上是最容易解决的问题,因为你可以定义一个隐式从Either [Boolean,T]到Boolean。

所以,从理论上讲,这是可能的。你可以用自己的一类来做。但是,您将如何改变已定义的现有方法?着名的Pimp My Class模式用于添加行为,而不是更改行为。

这是第二个选项的实现:

object ChainedBooleans {

  case class MyBoolean(flag: Either[Boolean, MyInt]) {
    def &&(other: MyBoolean): Either[Boolean, MyInt] = 
      if (flag.isRight || flag.left.get) other.flag else Left(false)

    def <(other: MyInt): Either[Boolean, MyInt] = 
      if (flag.isRight || flag.left.get) flag.right.get < other else Left(false) 
    def >(other: MyInt): Either[Boolean, MyInt] = 
      if (flag.isRight || flag.left.get) flag.right.get > other else Left(false) 
    def ==(other: MyInt): Either[Boolean, MyInt] = 
      if (flag.isRight || flag.left.get) flag.right.get == other else Left(false) 
    def !=(other: MyInt): Either[Boolean, MyInt] = 
      if (flag.isRight || flag.left.get) flag.right.get != other else Left(false) 
    def <=(other: MyInt): Either[Boolean, MyInt] = 
      if (flag.isRight || flag.left.get) flag.right.get <= other else Left(false) 
    def >=(other: MyInt): Either[Boolean, MyInt] = 
      if (flag.isRight || flag.left.get) flag.right.get >= other else Left(false) 
  }

  implicit def toMyBoolean(flag: Either[Boolean, MyInt]) = new MyBoolean(flag)
  implicit def toBoolean(flag: Either[Boolean, MyInt]) = 
    flag.isRight || flag.left.get 

  case class MyInt(n: Int) {
    def <(other: MyInt): Either[Boolean, MyInt] =
      if (n < other.n) Right(other) else Left(false)

    def ==(other: MyInt): Either[Boolean, MyInt] =
      if (n == other.n) Right(other) else Left(false)

    def !=(other: MyInt): Either[Boolean, MyInt] =
      if (n != other.n) Right(other) else Left(false)

    def <=(other: MyInt): Either[Boolean, MyInt] = 
      if (this < other || this == other) Right(other) else Left(false)

    def >(other: MyInt): Either[Boolean, MyInt] =
      if (n > other.n) Right(other) else Left(false)

    def >=(other: MyInt): Either[Boolean, MyInt] = 
      if (this > other || this == other) Right(other) else Left(false)
  }

  implicit def toMyInt(n: Int) = MyInt(n)
}

这是一个使用它的会话,显示可以做什么和不能做什么:

scala> import ChainedBooleans._
import ChainedBooleans._

scala> 2 < 5 < 7
<console>:14: error: no implicit argument matching parameter type Ordering[Any] was found.
       2 < 5 < 7
         ^

scala> 2 < MyInt(5) < 7
res15: Either[Boolean,ChainedBooleans.MyInt] = Right(MyInt(7))

scala> 2 <= MyInt(5) < 7
res16: Either[Boolean,ChainedBooleans.MyInt] = Right(MyInt(7))

scala> 2 <= 5 < MyInt(7)
<console>:14: error: no implicit argument matching parameter type Ordering[ScalaObject] was found.
       2 <= 5 < MyInt(7)
         ^

scala> MyInt(2) < 5 < 7
res18: Either[Boolean,ChainedBooleans.MyInt] = Right(MyInt(7))

scala> MyInt(2) <= 5 < 7
res19: Either[Boolean,ChainedBooleans.MyInt] = Right(MyInt(7))

scala> MyInt(2) <= 1 < 7
res20: Either[Boolean,ChainedBooleans.MyInt] = Left(false)

scala> MyInt(2) <= 7 < 7
res21: Either[Boolean,ChainedBooleans.MyInt] = Left(false)

scala> if (2 <= MyInt(5) < 7) println("It works!") else println("Ow, shucks!")
It works!

5
2017-07-22 13:11