【问题标题】:Seq[A] extends Ordered[Seq[A]]Seq[A] 扩展 Ordered[Seq[A]]
【发布时间】:2011-03-11 15:42:40
【问题描述】:

我正在尝试使一个序列(例如,其他集合类型也可以)与其他序列具有可比性。

class RichSeq[A](val seq: Seq[A]) extends Ordered[RichSeq[A]]

当然在引用的包对象中有隐式转换:

implicit def seq2RichSeq[A](s: Seq[A]) = new RichSeq(s)

比较意味着,第一个尺寸比每个元素都重要。代码很清楚:

class RichSeq[A](val seq: Seq[A]) extends Ordered[RichSeq[A]] { 
  def compare(s: RichSeq[A]) = {
    seq.size compare s.seq.size match {
      case 0 => seq.view.zip(s.seq).map { case (x,y) => ord.compare(x,y) }.dropWhile(_ == 0).headOption.getOrElse(0)
      case x => x
    }
  }
}

但这并不能编译(当然),因为需要一个排序来比较元素,所以我尝试了:

class RichSeq[A](val seq: Seq[A]) extends Ordered[RichSeq[A]] { 
  def compare(s: RichSeq[A])(implicit ord: Ordering[A]) = {
    // ...
  }
}

现在 compare 方法的签名不合适,所以我将隐式 ord 移到类签名中(并适应了隐式转换):

implicit def seq2RichSeq[A](s: Seq[A])(implicit ord: Ordering[A]) = new RichSeq(s)
class RichSeq[A](val seq: Seq[A])(implicit ord: Ordering[A]) extends Ordered[RichSeq[A]] { 
  def compare(s: RichSeq[A]) = {
      // ...
    }
  }

但现在我有一个问题,我想通过implicitSeq[A] 上使用RichSeq 中的所有其他方法也需要一个隐式Ordering[A],我不能总是提供一个。有时我使用我的RichSeq by 方法而不使用 Ordering,有时使用 compare 方法。

例如,有时我会调用

def distinctBy[B](f: A => B): Seq[A] = {
  seq.foldLeft { (Buffer[A](),MutMap[B,A]()) } {
    case ((b,m),x) if m contains f(x) => (b,m)
    case ((b,m),x) => 
      m += f(x) -> x
      b += x
      (b,m)
  }._1
}

同时我无法定义Ordering[A]

我看到一个解决方案是拥有两个不同的类(有两个隐式转换):

class RichSeqOrderable[A](val seq: Seq[A])(implicit ord: Ordering[A]) extends Ordered[RichSeqOrderable[A]]

class RichSeq[A](val seq: Seq[A])

但我认为这打破了将所有东西放在一起的想法?!?

【问题讨论】:

标签: scala


【解决方案1】:

我通常的前言是我不一定会这样做,而是以这个问题为借口来阐明一些鲜为人知的功能:这里如果有任何隐式排序可用,它将使用它,否则它将对它们进行排序通过哈希码。

package object foo {
  implicit def seq2RichSeq[A](s: Seq[A])(implicit ord: Ordering[A] = Ordering[Int].on((_: A).##)) = new RichSeq(s)
}
package foo {
  class RichSeq[A](val seq: Seq[A])(implicit ord: Ordering[A]) extends Ordered[RichSeq[A]] { 
    def compare(s: RichSeq[A]) = {
      seq.size compare s.seq.size match {
        case 0 => seq.view.zip(s.seq).map { case (x,y) => ord.compare(x,y) }.dropWhile(_ == 0).headOption.getOrElse(0)
        case x => x
      }
    }
  }
}

【讨论】:

  • 不错。另一个想法是默认比较字符串表示,希望它们能以某种方式紧密地表示有问题的对象。不过,哈希码通常是一种更简洁的解决方案,但含义较少。特别是,它可能会将具有相同内容的两个对象放在结果中相距很远,因为具有相同内容的不同对象的哈希码不必相等。
  • 不错!哈希码的默认排序!我真的很愿意接受你的回答,但丹尼尔斯的回答对我更有吸引力。
【解决方案2】:

我选择了类似于paulp 的建议:

class RichSeq[A](val seq: Seq[A])(implicit optionalOrd: Option[Ordering[A]] = None) extends Ordered[RichSeq[A]] {
  def compare(s: RichSeq[A]) = {
    seq.size compare s.seq.size match {
      case 0 => seq.view.zip(s.seq).map { case (x,y) => optionalOrd.map(_.compare(x,y)).getOrElse(0) }.dropWhile(_ == 0).headOption.getOrElse(0)
      case x => x
    }
  }
}

object RichSeq {
  implicit def orderingToSome[A](implicit ord: Ordering[A] = null) = Option(ord)
  implicit def seq2RichSeq[A](s: Seq[A])(implicit ord: Option[Ordering[A]]) = new RichSeq(s)
}

隐含太多不是一件好事,尤其是标准库中的类型。不过,我认为Ordering[A] => Option[Ordering[A]] 已经尽可能安全了。

关于隐式链接

Scala 对添加方法的自动转换有一个限制,即它不会在尝试查找方法时应用多个转换。例如:

class A(val n: Int)
class B(val m: Int, val n: Int)
class C(val m: Int, val n: Int, val o: Int) {
  def total = m + n + o
}

// This demonstrates implicit conversion chaining restrictions
object T1 { // to make it easy to test on REPL
  implicit def toA(n: Int) = new A(n)
  implicit def aToB(a: A) = new B(a.n, a.n)
  implicit def bToC(b: B) = new C(b.m, b.n, b.m + b.n)

  // won't work
  println(5.total)
  println(new A(5).total)

  // works
  println(new B(5, 5).total)
  println(new C(5, 5, 10).total)
}

然而,如果一个隐式定义本身需要一个隐式参数,Scala 根据需要寻找其他隐式值。继续上一个例子:

// def m[A <% B](m: A) is the same thing as
// def m[A](m: A)(implicit ev: A => B)

object T2 {
  implicit def toA(n: Int) = new A(n)
  implicit def aToB[A1 <% A](a: A1) = new B(a.n, a.n)
  implicit def bToC[B1 <% B](b: B1) = new C(b.m, b.n, b.m + b.n)

  // works
  println(5.total)
  println(new A(5).total)
  println(new B(5, 5).total)
  println(new C(5, 5, 10).total)
}

“魔法!”,你可能会说。不是这样。以下是编译器如何翻译每一个:

object T1Translated {
  implicit def toA(n: Int) = new A(n)
  implicit def aToB(a: A) = new B(a.n, a.n)
  implicit def bToC(b: B) = new C(b.m, b.n, b.m + b.n)

  // Scala won't do this
  println(bToC(aToB(toA(5))).total)
  println(bToC(aToB(new A(5))).total)

  // Just this
  println(bToC(new B(5, 5)).total)

  // No implicits required
  println(new C(5, 5, 10).total)
}

object T2Translated {
  implicit def toA(n: Int) = new A(n)
  implicit def aToB[A1 <% A](a: A1) = new B(a.n, a.n)
  implicit def bToC[B1 <% B](b: B1) = new C(b.m, b.n, b.m + b.n)

  // Scala does this
  println(bToC(5)(x => aToB(x)(y => toA(y))).total)
  println(bToC(new A(5))(x => aTo(B(x)(identity _)).total)
  println(bToC(new B(5, 5))(identity _).total)

  // no implicits required
  println(new C(5, 5, 10).total)
}

因此,虽然 bToC 被用作隐式转换,aToBtoA 被作为隐式参数传递,而不是作为隐式转换链接。

【讨论】:

  • 由于隐式没有链接,您必须将 Ordering[.] 显式传递给 seq2RichSeq 才能将其隐式转换为 Option[Ordering[.]],不是吗?但是如何将某些东西显式传递给隐式转换呢?
  • 不能链接的是允许方法的隐式转换。例如,如果我从A =&gt; BB =&gt; C 转换,但是我不能在A 上调用C 方法。然而,这里只进行了一次隐式转换:seq2RichSeq。另一方面,隐式参数的链接是允许的。所以它需要一个隐式Option[Ordering[A]],它由orderingToSome 提供,它需要一个隐式Ordering[A],它本身是隐式提供的,甚至可能通过一个隐式A =&gt; Ordered[A] 提供。
  • 嗯。这是否意味着我不能链接隐式方法,但 可以 链接隐式函数? 挠他的头 啊,不,等等:“链接”在这里用错词了。你是嵌套,而不是链接,对吧?我想知道是否可以通过使用嵌套来规避链接限制,即在某种意义上通过嵌套其他几个来构建一个隐式转换。
  • @Raphael 这对 cme​​ts 来说太混乱了,所以我扩展了我的答案来讨论它。
  • @Raphael 确实如此。我想我会这样做的。
【解决方案3】:

我还没有完全编码,但你为什么不这样做:

class RichSeq[A <: Ordered[A]](val seq: Seq[A]) extends Ordered[RichSeq[A]] {
  import Ordering.ordered 

  ...
}

导入的隐式转换会在必要时给你Ordering[A]

【讨论】:

  • 然后,我遇到了同样的问题:A 必须是Ordered[A],这并不总是可能的。有时我既没有Ordering[A] 也没有Ordered[A]
  • 那么创建该类型的RichSeq 是没有用的,至少和RichSeq &lt;: Ordered[RichSeq] 一样长。
猜你喜欢
  • 2014-08-25
  • 2019-03-03
  • 2021-08-31
  • 2012-10-11
  • 1970-01-01
  • 2011-11-06
  • 1970-01-01
  • 1970-01-01
  • 2019-09-23
相关资源
最近更新 更多