2015-09-06 32 views
2

我有一个简单的特征,需要实现有一个方法quality(x:A),我想返回一个Ordered[B]。换句话说,qualityA转换为Ordered[B]。这样我可以比较B的。从Scala中的方法返回一个有序的[T]

我有以下的基本代码:

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,这当然是可能的,但我希望特征尽可能通用,因为我希望它描述行为而不是实现。我曾想过实施Ordered[A]的班级A,但这似乎也很奇怪,因为它不是这个班的“目的”。

+0

不是扩展'Ordered [A]',你可以确保存在'Ordering [A]'。 – Kolmar

+0

我想我不完全理解:)我怎样才能确保存在'订购[A]'? – avanwieringen

回答

4

为了补充Peter的回答:在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的。现在,使其惯用斯卡拉让我们使用Ordering[A]代替,并重新命名quantityord

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被重写得如此笨拙。

rewritting这种形式竟然是如此普遍,一个漂亮的语法糖中引入了它 - 多个参数列表

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" 
} 

请谨记的所有这些转换功能不过是句法糖的应用。所以所有版本都是完全一样的,编译器会将每个版本解除为相同的字节码。


总括:

  1. quantity功能到Ordering[A]类提取A排序逻辑;
  2. Ordering[A]的实例置于隐式范围或根据您的需要明确传递排序;
  3. 挑选语法糖的“风味”baz:无糖/嵌套函数,多参数列表或上下文绑定。

UPD

要回答原来的问题: “为什么不把它编译?”让我从关于中缀比较运算符如何在Scala中工作的一点点偏离开始。

考虑下面的代码:

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

这里到底发生了什么。斯卡拉本身没有中缀运算符,而中缀运算符只是单参数方法调用的语法糖。所以内部上面的代码转换到这一点:

val greater: Boolean = x.>(y) 

现在棘手的问题:Int没有对自己的>方法。在ScalaDoc页面上选择,继承,并检查此方法是否列在标题为“由隐式转换intWrapper从Int到RichInt继承”的组中。

所以内部编译器这个(当然,除了性能方面的原因,有上堆额外的对象没有实际实例):

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) 

重写应该强调的各类参与对比参数:在右边的左边和IntOrdered[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和其他斯卡拉魔术实施质量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 s,而不需要A来执行Ordered[A]

我们可以请求Ordering[A]通过添加implicit参数baz存在:

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" 
} 

让我们创建与它的同伴对象的Ordering一个Person案例类。

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你的质量功能。

相关问题