Связанные сравнения в Scala

Python поддерживает элегантный синтаксис для "цепных сравнений", например:

0 <= n < 256

значение

0 <= n and n < 256

Зная, что это довольно гибкий язык синтаксически, можно ли эмулировать эту функцию в Scala?

Ответ 1

Ответ Дэниела всеобъемлющий, на самом деле трудно добавить что-либо к нему. Поскольку в его ответе представлен один из вариантов, о которых он упомянул, я хотел бы просто добавить свои 2 цента и представить очень короткое решение проблемы по-другому. Описание Daniel:

Теоретически вы могли бы кормить результат одного из методов другой метод. Я могу думать о двух способы сделать это:

  • Наличие <= возвращает объект, который имеет как полученный параметр, так и результат сравнения, и имеющий < используйте оба эти значения.

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

Вы можете использовать его для любых упорядоченных типов, например Int или Double s:

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

Ответ 2

Не совсем. Нужно помнить, что, помимо нескольких ключевых слов, все в Scala является вызовом метода для объекта.

Итак, нам нужно вызвать "< =" и "< методы для объекта, и каждый такой метод получает параметр. Вам нужно четыре объекта, и есть три явных и два неявных - результаты каждого метода.

Теоретически вы могли бы передать результат одного из методов другому методу. Я могу думать о двух способах этого:

  • Имея <= возвращает объект, который имеет как полученный параметр, так и результат сравнения, и имеющий < используйте оба эти значения, если это необходимо.

  • Имея <= возврат либо false, либо полученный параметр, и имеющий < либо сбой, либо сравнение с другим параметром. Это можно сделать с помощью класса "Либо" или чего-то на его основе.

Эти два решения очень похожи.

Одна из проблем заключается в том, что потребители таких операторов сравнения ожидают, в результате, булевых. Это на самом деле самая простая задача для решения, так как вы могли бы определить неявный из Либо [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!