我有一个简单的特性,要求实现方法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的类来定义。需要使用这些函数来计算品质因数。这可以是Double
,Int
或其他什么,但它也可以是更复杂的东西。我可以重写它总是返回Double
,这当然是可能的,但我希望特性尽可能通用,因为我希望它描述行为而不是实现。我想到实现A
的课程Ordered[A]
,但这看起来也很奇怪,因为这不是比较这个课程的“目的”。
答案 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
现在通过排序进行参数化,因此您可以根据具体情况动态选择如何订购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
函数的所有这些转换只不过是语法糖的应用。所以所有的版本都是完全相同的,编译器会将每个版本的desginarize为相同的字节码。
回顾一下:
A
函数中提取quantity
排序逻辑到Ordering[A]
类; Ordering[A]
的实例放入隐式范围,或根据您的需要明确传递排序; baz
的语法糖:无糖/嵌套函数,多参数列表或上下文绑定。<强> 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
不对称。
底线:
quality(x1) > x2
Ordering[T]
的内容与其他Scala魔法执行质量混为quality(x: A): Double
。 A
具有良好的通用性,可以进行比较和排序。