我正在尝试为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
答案 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.A
与Char
相同。