从Scala中的方法返回Ordered [T]

时间:2015-09-06 15:49:19

标签: scala sorting types

我有一个简单的特性,要求实现方法quality(x:A)我想要返回Ordered[B]。换句话说,quality会将A转换为Ordered[B]。这样我可以与B进行比较。

我有以下基本代码:

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的好的扩展方法。

我在这里缺少什么?我想要的特性是在trait中定义一个复杂的算法,但是关键函数需要由实现trait的类来定义。需要使用这些函数来计算品质因数。这可以是DoubleInt或其他什么,但它也可以是更复杂的东西。我可以重写它总是返回Double,这当然是可能的,但我希望特性尽可能通用,因为我希望它描述行为而不是实现。我想到实现A的课程Ordered[A],但这看起来也很奇怪,因为这不是比较这个课程的“目的”。

2 个答案:

答案 0 :(得分: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质量函数。

答案 1 :(得分: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 vs ord.gt(x1, x2)
  • baz现在通过排序进行参数化,因此您可以根据具体情况动态选择如何订购x1x2

    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函数的所有这些转换只不过是语法糖的应用。所以所有的版本都是完全相同的,编译器会将每个版本的desginarize为相同的字节码。

回顾一下:

  1. A函数中提取quantity排序逻辑到Ordering[A]类;
  2. Ordering[A]的实例放入隐式范围,或根据您的需要明确传递排序;
  3. 选择&#34;你的味道&#34; baz的语法糖:无糖/嵌套函数,多参数列表或上下文绑定。
  4. <强> UPD

    回答原来的问题&#34;为什么不编译?&#34;让我从关于中缀比较运算符如何在Scala中工作开始。

    给出以下代码:

    val x: Int = 1
    val y: Int = 2
    val greater: Boolean = x > y
    

    这是实际发生的事情。 Scala本身没有中缀运算符,而中缀运算符只是单参数方法调用的语法糖。因此,上面的代码在内部转换为:

    val greater: Boolean = x.>(y)
    

    现在棘手的部分:Int自身没有>方法。在ScalaDoc页面上选择继承进行排序,并检查此方法是否在标题为&#34;由Int到RichInt&#34;的隐式转换intWrapper继承的组中列出。

    所以内部编译器会这样做(好吧,除了出于性能原因,堆上没有额外对象的实际实例化):

    val greater: Boolean = (new RichInt(x)).>(y)
    

    如果我们继续使用RichInt的ScalaDoc并再次通过继承对方法进行排序,则>方法实际上来自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]和{{1分别。

    问题是你在哪里得到这个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不对称。

    底线:

    • 如果您希望代码是惯用的,则Scala会转到quality(x1) > x2
    • 如果您不想将所有Ordering[T]的内容与其他Scala魔法执行质量混为quality(x: A): DoubleA具有良好的通用性,可以进行比较和排序。