9

我正在尝试为 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
4

2 回答 2

3

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

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

正如 Miles 指出的那样,拆分对于隐式搜索和类型推断来说似乎是必不可少的。

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

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
于 2013-01-06T09:51:42.940 回答
0

如果AIsTraversableLike.

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.

于 2016-06-29T14:59:44.943 回答