如何在IsTraversableLike中使用类型成员A?

时间:2013-01-06 08:24:28

标签: scala

我正在尝试为Scala集合编写一些扩展方法,并遇到麻烦,使它们完全一致。

第一次尝试使用tailOption会产生类似的结果:

implicit class TailOption[A, Repr <: GenTraversableLike[A, Repr]](val repr: Repr) {
  def tailOption: Option[Repr] = 
    if (repr.isEmpty) None 
    else Some(repr.tail)
}

不幸的是,这不起作用:

scala> List(1,2,3).tailOption
<console>:19: error: value tailOption is not a member of List[Int]
              List(1,2,3).tailOption

Scala 2.10提供了IsTraversableLike类型类,以帮助适应所有集合(包括奇数,如字符串)这类事物。

有了这个,我可以很容易地实现tailOption:

implicit class TailOption[Repr](val r: Repr)(implicit fr: IsTraversableLike[Repr]) {
  def tailOption: Option[Repr] = { 
    val repr = fr.conversion(r)
    if (repr.isEmpty) None 
    else Some(repr.tail)
  }
}

scala> List(1,2,3).tailOption
res12: Option[List[Int]] = Some(List(2, 3))

scala> "one".tailOption
res13: Option[String] = Some(ne)

结果的类型正确:Option[<input-type>]。具体来说,我在调用返回Repr的方法时能够保留Repr类型,如`tail。

不幸的是,我似乎无法使用此技巧来保留集合元素的类型。我无法调用返回元素的方法。

IsTraversableLike确实有成员A,但似乎没有用处。特别是我无法重建我的原始元素类型,并且该成员在类型上不相同。例如,没有进一步的工作,headTailOption看起来像这样:

implicit class HeadTailOption[Repr](val r: Repr)(implicit val fr: IsTraversableLike[Repr]) { 
  def headTailOption: Option[(fr.A, Repr)] = { 
    val repr = fr.conversion(r)
    if (repr.isEmpty) None 
    else Some(repr.head -> repr.tail)
  }
}

scala> val Some((c, _)) = "one".headTailOption
c: _1.fr.A forSome { val _1: HeadTailOption[String] } = o

正如我们所看到的,c有一种奇妙的巴洛克式。但是,这种类型等同于Char:

scala> val fr = implicitly[IsTraversableLike[String]]
fr: scala.collection.generic.IsTraversableLike[String] = scala.collection.generic.IsTraversableLike$$anon$1@60ab6a84

scala> implicitly[fr.A <:< Char]
<console>:25: error: Cannot prove that fr.A <:< Char.
              implicitly[fr.A <:< Char]

我尝试了各种各样的技巧,包括Repr[A] <: GenTraversableLike[A, Repr[A]]没有任何帮助。任何人都可以制定神奇的酱汁,使headTailOption返回正确的类型:

val headTailString: Option[(Char, String)] = "one".headTailOption
val headTailList: Option[(Int, List[Int])] = List(1,2,3).headTailOption

2 个答案:

答案 0 :(得分:3)

部分答案。您可能已从IsTraversableLike的scaladoc示例开始。它仍然使用分离隐式转换和实例化包装类的“旧方法”,而不是通过隐式类进行一步。事实证明,“旧方法”确实有效:

import collection.GenTraversableLike
import collection.generic.IsTraversableLike

final class HeadTailOptionImpl[A, Repr](repr: GenTraversableLike[A, Repr]) { 
  def headTailOption: Option[(A, Repr)] = { 
    if (repr.isEmpty) None 
    else Some(repr.head -> repr.tail)
  }
}

implicit def headTailOption[Repr](r: Repr)(implicit fr: IsTraversableLike[Repr]):
  HeadTailOptionImpl[fr.A,Repr] = new HeadTailOptionImpl(fr.conversion(r))

// `c` looks still weird: `scala.collection.generic.IsTraversableLike.stringRepr.A`
val Some((c, _)) = "one".headTailOption
val d: Char = c  // ...but it really is a `Char`!

val headTailString: Option[(Char, String)] = "one".headTailOption
val headTailList: Option[(Int, List[Int])] = List(1,2,3).headTailOption

正如迈尔斯指出的那样,分裂似乎对于使用隐式搜索和类型推断至关重要。

另一种解决方案,虽然当然不那么优雅,但是放弃了字符串和集合的统一:

trait HeadTailOptionLike[A, Repr] {
  def headTailOption: Option[(A, Repr)]
}

implicit class GenHeadTailOption[A, Repr](repr: GenTraversableLike[A, Repr])
extends HeadTailOptionLike[A, Repr] {
  def headTailOption = 
    if (repr.isEmpty) None 
    else Some(repr.head -> repr.tail)
}

implicit class StringHeadTailOption(repr: String)
extends HeadTailOptionLike[Char, String] {
  def headTailOption = 
    if (repr.isEmpty) None 
    else Some(repr.head -> repr.tail) // could use repr.charAt(0) -> repr.substring(1)
}

List(1,2,3).headTailOption
"one".headTailOption

答案 1 :(得分:0)

如果从A中提取类型IsTraversableLike,则可以将其编写为隐式类。

import collection.generic.IsTraversableLike

implicit class HeadTailOption[Repr,A0](val r: Repr)(implicit val fr: IsTraversableLike[Repr]{ type A = A0 }) { 
  def headTailOption: Option[(A0, Repr)] = { 
    val repr = fr.conversion(r)
    if (repr.isEmpty) None 
    else Some(repr.head -> repr.tail)
  }
}

或等效地:

import collection.generic.IsTraversableLike

type IsTraversableLikeAux[Repr,A0] = IsTraversableLike[Repr]{ type A = A0 }

implicit class HeadTailOption[Repr,A](val r: Repr)(implicit val fr: IsTraversableLikeAux[Repr,A]) { 
  def headTailOption: Option[(A, Repr)] = { 
    val repr = fr.conversion(r)
    if (repr.isEmpty) None 
    else Some(repr.head -> repr.tail)
  }
}

然后一切正常。

scala> val Some((c, _)) = "one".headTailOption
c: scala.collection.generic.IsTraversableLike.stringRepr.A = o

scala> c.isSpaceChar
res0: Boolean = false

编译器知道scala.collection.generic.IsTraversableLike.stringRepr.AChar相同。