2015-09-06 2 views
2

У меня есть простая черта, которая требует, чтобы реализация имела метод quality(x:A), который я хочу вернуть Ordered[B]. Другими словами, quality преобразует A в Ordered[B]. Я могу сравнить с B.Возврат заказа [T] из метода в Scala

У меня есть следующий базовый код:

trait Foo[A] { 
    def quality[B](x:A):Ordered[B] 

    def baz(x1:A, x2:A) = { 
     // some algorithm work here, and then 
     if (quality(x1) > quality(x2)) { 
      // some stuff 
     } 
} 

который я хочу реализовать, как следует:

class FooImpl extends Foo[Bar] { 
    def quality(x:Bar):Double = { 
     someDifficultQualityCalculationWhichReturnsADouble 
    } 
} 

Я полагал, что это может работать, потому что Double неявно преобразуется в RichDouble, который реализует Ordered[Double], если я Верно.
Но в > в baz методы признака он дает мне ошибку quality(x2) заявляя: Type mismatch, expected Nothing, actual Ordered[Nothing]

Я не это понять, потому что, исходя из C#, я считаю, что сравнимо с возвращением что-то вроде IEnumerable<A>, а затем используя все хорошие методы расширения для IEnumerable.

Что мне здесь не хватает? То, что я хочу с признаком, - это определить сложный алгоритм внутри признака, но ключевые функции должны определяться классом, реализующим этот признак. Для этих функций необходимо рассчитать коэффициент качества. Это может быть Double, Int или что-то еще, но это может быть и нечто более сложное. Я мог бы переписать его так, чтобы он всегда возвращал Double, и это, безусловно, возможно, но я хочу, чтобы этот признак был как можно более общим, потому что я хочу, чтобы он описывал поведение, а не реализацию. Я думал о классе A, реализующем Ordered[A], но это также кажется странным, потому что это не «цель» этого класса для сравнения.

+0

Вместо расширения 'Ordered [A]', вы можете убедиться, что существует 'Ordering [A]'. – Kolmar

+0

Я думаю, что я не совсем понимаю :) Как я могу убедиться, что существует 'Ordering [A]'? – avanwieringen

ответ

4

В дополнение к ответу Питера: в 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 - не что иное, как применение синтаксического сахара. Таким образом, все версии являются точно такими же, и компилятор будет обессыщать каждую из версий одним и тем же байт-кодом.


Чтобы резюмировать:

  1. извлечь логику A заказа от quantity функции к Ordering[A] класса;
  2. введите экземпляр Ordering[A] в неявный объем или выполните заказ явно в зависимости от ваших потребностей;
  3. выбрать «ваш аромат» синтаксического сахара для 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 хорошие и общие для сравнения и заказа.
+0

Спасибо за ваш очень четкий ответ! Остается один вопрос для меня без ответа, потому что я все еще не могу понять, почему я не могу сравнивать объект, который возвращает 'Ordered [A]' в моем примере, потому что он возвращает 'Ordered [Nothing]'. Это означает, что я совершенно неправильно понимаю конкретную концепцию Scala. Я думал, что когда метод возвращает 'Ordered [A]', это означает, что я могу делать сравнения на нем, так же как возврат «List [A]» означает, что я могу сделать «map». Почему это не так? – avanwieringen

+0

Обновлен комментарий. Надеюсь, на этот раз он покроет ваш оригинальный вопрос. –

+0

Благодарю вас за подробный ответ! Спасибо, что нашли время ответить на мои вопросы. – avanwieringen

5

С помощью Ordering[A] вы можете сравнить A с, не требуя A для реализации Ordered[A].

Мы можем потребовать, чтобы Ordering[A] существует в baz путем добавления implicit параметра:

trait Foo[A] { 
    def baz(x1:A, x2:A)(implicit ord: Ordering[A]) = 
    if (ord.gt(x1, x2)) "first is bigger" 
    else "first is smaller or equal" 
} 

Позволяет создать Person случай класса с Ordering в его объекте компаньон.

case class Person(name: String, age: Int) 
object Person { 
    implicit val orderByAge = Ordering.by[Person, Int](_.age) 
} 

Теперь мы можем использовать Foo[Person].baz потому что Ordering[Person] существует:

val (alice, bob) = (Person("Alice", 50), Person("Bob", 40)) 

val foo = new Foo[Person] {} 
foo.baz(alice, bob) 
// String = first is bigger 

// using an explicit ordering 
foor.baz(alice, bob)(Ordering.by[Person, String](_.name)) 
// String = first is smaller or equal 

Точно так же, как я сравнил Persons по возрасту, вы можете создать Ordering[A] сравнить ваш A по вашей функции качества.