2011-03-11 55 views
2

我正在尝试制作一个可与其他序列比较的序列(例如,其他集合类型也是可行的)。Seq [A]扩展顺序[Seq [A]]

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

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

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]) = { 
    // ... 
    } 
} 

现在的签名比较方法是不适合的,所以我提出的隐式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]使用也需要一个隐含的Ordering[A],我不能总是提供一个。有时候我使用我的RichSeq通过没有排序的方法和有时使用比较方法。

例如,有时我叫

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]) 

但我认为,打破让所有的东西在一起的念头?!?

+0

我会去两个班,但在同一个文件。 – Anonymous 2011-03-11 18:00:17

+1

http://stackoverflow.com/questions/4493242/why-dont-scala-lists-have-an-ordering – 2011-03-11 21:20:38

回答

1

我去类似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) 
} 

这不是一件好事,有太多implicits,特别是在标准库类型。不过,我认为Ordering[A] => Option[Ordering[A]]大概是安全的。

在implicits

斯卡拉的链接对自动转换的限制添加的方法,这是它不会试图找到方法应用于超过一次的转换。例如:

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

但是,如果一个隐含的定义需要本身就是一个隐含参数,斯卡拉找,只要需要额外的隐含价值。继续从最后一个例子:

// 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正在传递的隐含参数,而不是被链隐式转换。

+0

因为implicits没有链接,所以你必须显式地将'Ordering [。]'传递给'seq2RichSeq',它被隐式转换为'Option [Ordering [。]]',对吗?但是,如何明确地将某些内容传递给隐式转换? – Raphael 2011-03-16 11:42:23

+0

无法链接的内容是隐式转换以允许使用方法。例如,如果我有从'A => B'和'B => C'的转换,但是我不能在'A'上调用'C'方法。然而,在这里,只有一个隐式转换正在发生:'seq2RichSeq'。另一方面,允许隐含参数的链接。所以它需要一个隐含的'Option [Ordering [A]]',它是由'orderedToSome'提供的,它带有一个隐式的Ordering [A],它本身是隐式提供的,甚至可以通过隐式的'A =>排序[A]'。 – 2011-03-16 12:51:06

+0

呵呵。这是否意味着我不能链式隐式方法,但*可以链式隐式函数? *划伤他的头*啊,不,等等:“链接”是在这里使用的错误词。你在嵌套,而不是链接,对吗?我想知道是否可以通过使用嵌套来规避链接限制,从某种意义上说,通过嵌套其他几个嵌套来构建一个隐式转换。 – Raphael 2011-03-16 13:15:04

0

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

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

    ... 
} 

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

+0

然后,我有同样的问题:'A'必须是'有序[A]',那并不总是可能的。有时候,我既没有“订购[A]”也没有订购[A]'。 – 2011-03-11 19:26:55

+0

然后,在创建该类型的RichSeq时没有用处,至少与RichSeq Raphael 2011-03-11 22:09:04

3

我通常的前言说我不一定会用这种方式做事,而是用这个问题作为借口来说明一些鲜为人知的特征:在这里,如果有任何隐式排序可用,它将使用它,否则它会命令他们通过hashcode。

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 
     } 
    } 
    } 
} 
+1

不错。另一个想法是默认比较字符串表示,希望它们以某种方式紧密地代表所讨论的对象。尽管如此,哈希代码通常是一个更干净的解决方案,但具有较少的*含义*。特别是,它可能会将两个具有相同内容的对象放在相距很远的地方,因为对于具有相同内容的不同对象,哈希代码不必相同。 – Raphael 2011-03-13 10:22:12

+0

不错!哈希码的默认排序!我非常喜欢接受你的回答,但丹尼尔斯对我的回答非常有吸引力。 – 2011-04-27 17:07:51