В дополнение к ответу Питера: в Scala у нас есть две черты: Ordering[T] и Ordered[A]. Вы должны использовать их в разных ситуациях.
Ordered[A]
предназначен для случаев, когда класс, который вы реализуете, может быть заказан естественным образом, и этот порядок является единственным.
Пример:
class Fraction(val numerator: Int, val denominator: Int) extends Ordered[Fraction]
{
def compare(that: Fraction) = {
(this.numerator * that.denominator) compare (this.denominator * that.numerator)
}
}
Ordering[T]
для случаев, когда вы хотите иметь различные способы сделать заказ вещи. Таким образом, стратегия определения порядка может быть отделена от упорядоченного класса.
Для примера я буду брать Питер Person
:
case class Person(name: String, age: Int)
object PersonNameOrdering extends Ordering[Person]
{
def compare(x: Person, y: Person) = x.name compare y.name
}
отметить, что с PersonNameOrdering
не имеет какой-либо полого экземпляр, все это делает инкапсулирует логику определения порядка двух Person
-х гг. Таким образом, я сделал его object
, а не class
.
Чтобы сократить шаблонного вы можете использовать Ordering.on определить Ordering
:
val personAgeOrdering: Ordering[Person] = Ordering.on[Person](_.age)
Теперь самое интересное: как использовать все эти вещи.
В вашем исходном коде Foo[A].quantity
косвенно определяется способ заказа ваших A
. Теперь, чтобы сделать это идиоматическое Scala давайте использовать Ordering[A]
вместо этого, и переименовать quantity
в ord
:
trait Foo[A] {
def baz(x1: A, x2: A, ord: Ordering[A]) = {
import ord._
if (x1 > x2) "first is greater"
else "first is less or equal"
}
}
Несколько вещей отметить здесь:
import ord._
позволяет использовать инфиксную обозначения для сравнения, т.е. x1 > x2
против ord.gt(x1, x2)
baz
в настоящее время параметризован заказным путем, поэтому вы можете динамически выбирать, как заказать x1
и x2
на случай- на индивидуальной основе:
foo.baz(person1, person2, PersonNameOrdering)
foo.baz(person1, person2, personAgeOrdering)
Тот факт, что ord
теперь явный параметр иногда может быть неудобно, вы не можете передать его в явном виде все время, в то время как там могут быть некоторые случаи, когда вы хотите сделать так. Имплицирует на помощь!
def baz(x1: A, x2: A) = {
def inner(implicit ord: Ordering[A]) = {
import ord._
if (x1 > x2) "first is greater"
else "first is less or equal"
}
inner
}
Обратите внимание на ключевое слово implicit
. Он используется, чтобы сообщить компилятору сделать параметр из неявной области в случае, если вы не предоставите его в явном виде:
// put an Int value to the implicit scope
implicit val myInt: Int = 5
def printAnInt(implicit x: Int) = { println(x) }
// explicitly pass the parameter
printAnInt(10) // would print "10"
// let the compiler infer the parameter from the implicit scope
printAnInt // would print "5"
Вы можете узнать where does Scala look for implicits.
Еще одно замечание - необходимость вложенной функции. Вы не можете написать def baz(x1: A, x2: A, implicit ord: Ordering[A])
- это не будет компилироваться, потому что ключевое слово implicit
применяется ко всему списку параметров.
Чтобы справиться с этой маленькой проблемой, baz
был переписан таким неуклюжим способом.
Эта форма переписано оказался настолько распространенным, что хороший синтаксический сахар был введен для него - список несколько параметров:
def baz(x1: A, x2: A)(implicit ord: Ordering[A]) = {
import ord._
if (x1 > x2) "first is greater"
else "first is less or equal"
}
Необходимость неявное параметризовано типа также является довольно распространенным явлением поэтому приведенный выше код можно переписать с еще больше сахара - контекст связан:
def baz[A: Ordering](x1: A, x2: A) = {
val ord = implicitly[Ordering[A]]
import ord._
if (x1 > x2) "first is greater"
else "first is less or equal"
}
Пожалуйста, имейте в виду, что все эти преобразования baz
- не что иное, как применение синтаксического сахара. Таким образом, все версии являются точно такими же, и компилятор будет обессыщать каждую из версий одним и тем же байт-кодом.
Чтобы резюмировать:
- извлечь логику
A
заказа от quantity
функции к Ordering[A]
класса;
- введите экземпляр
Ordering[A]
в неявный объем или выполните заказ явно в зависимости от ваших потребностей;
- выбрать «ваш аромат» синтаксического сахара для
baz
: нет сахара/вложенных функций, нескольких списков параметров или ограничений контекста.
UPD
Чтобы ответить на исходный вопрос "почему он не компилировать?" позвольте мне начать с небольшого отступления от того, как работает оператор сравнения infix в Scala.
Учитывая следующий код:
val x: Int = 1
val y: Int = 2
val greater: Boolean = x > y
Вот что на самом деле происходит. У Scala нет инфиксных операторов как таковых, а инфиксные операторы - это просто синтаксический сахар для вызова метода с одним параметром. Таким образом, внутренне приведенный выше код преобразуется к следующему:
val greater: Boolean = x.>(y)
Теперь сложная часть: Int не имеет >
метод сам по себе. Выберите заказ по наследованию на странице ScalaDoc и убедитесь, что этот метод указан в группе под названием «Унаследовано от неявного преобразования intWrapper от Int до RichInt».
Так внутренне компилятор делает это (ну, за исключением того, что для повышения производительности, что не существует никакой фактической конкретизация дополнительного объекта на куче):
val greater: Boolean = (new RichInt(x)).>(y)
Если мы переходим к ScalaDoc из RichInt и снова заказать методы, с помощью Наследование получается, что метод > действительно исходит от Ordered!
Давайте перепишем весь блок, чтобы сделать его более ясным, что происходит на самом деле:
val x: Int = 1
val y: Int = 2
val richX: RichInt = new RichInt(x)
val xOrdered: Ordered[Int] = richX
val greater: Boolean = xOrdered.>(y)
Переписывание должны были выделены типы переменных, участвующих в сравнении: Ordered[Int]
слева и Int
справа. Обратитесь к документации >.
Теперь давайте вернемся к исходному коду и переписать его таким же образом, чтобы выделить типы:
trait Foo[A] {
def quality[B](x: A): Ordered[B]
def baz(x1: A, x2: A) = {
// some algorithm work here, and then
val x1Ordered: Ordered[B] = quality(x1)
val x2Ordered: Ordered[B] = quality(x2)
if (x1Ordered > x2Ordered) {
// some stuff
}
}
}
Как вы можете видеть типы не совпадают: они Ordered[B]
и Ordered[B]
, в то время как для >
сравнения для работы они должны были быть Ordered[B]
и B
соответственно.
Вопрос в том, где вы можете получить это B
, чтобы поставить его справа? Мне кажется, что B
фактически совпадает с A
в этом контексте. Вот что я придумал:
trait Foo[A] {
def quality(x: A): Ordered[A]
def baz(x1: A, x2: A) = {
// some algorithm work here, and then
if (quality(x1) > x2) {
"x1 is greater"
} else {
"x1 is less or equal"
}
}
}
case class Cargo(weight: Int)
class CargoFooImpl extends Foo[Cargo] {
override def quality(x: Cargo): Ordered[Cargo] = new Ordered[Cargo] {
override def compare(that: Cargo): Int = x.weight compare that.weight
}
}
Недостатком этого подхода является то, что это не очевидно: реализация quality
слишком многословным и quality(x1) > x2
не является симметричным.
В нижней строке:
- , если вы хотите, чтобы код был идиоматическим Scala идти
Ordering[T]
- , если вы не хотите возиться с implicits и другой магией Scala реализовать качество как
quality(x: A): Double
для всех A
s; Double
s хорошие и общие для сравнения и заказа.
Вместо расширения 'Ordered [A]', вы можете убедиться, что существует 'Ordering [A]'. – Kolmar
Я думаю, что я не совсем понимаю :) Как я могу убедиться, что существует 'Ordering [A]'? – avanwieringen