Python поддерживает элегантный синтаксис для "цепных сравнений", например:
0 <= n < 256
значение
0 <= n and n < 256
Зная, что это довольно гибкий язык синтаксически, можно ли эмулировать эту функцию в Scala?
Python поддерживает элегантный синтаксис для "цепных сравнений", например:
0 <= n < 256
значение
0 <= n and n < 256
Зная, что это довольно гибкий язык синтаксически, можно ли эмулировать эту функцию в Scala?
Ответ Дэниела всеобъемлющий, на самом деле трудно добавить что-либо к нему. Поскольку в его ответе представлен один из вариантов, о которых он упомянул, я хотел бы просто добавить свои 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
Не совсем. Нужно помнить, что, помимо нескольких ключевых слов, все в 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!