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

标签 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的方法(如`tail)时,我已经能够保留Repr类型。

不幸的是,我似乎无法使用此技巧来保留集合元素的类型。我无法调用返回元素的方法。
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

最佳答案

部分答案。您可能已经从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

正如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

关于scala - 如何在IsTraversableLike中使用类型成员A?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14180533/

相关文章:

scala - 如何避免: "blockingToByteString is a blocking and unsafe operation"

javascript - 在 play 框架中将 javascript 变量转换为 scala

scala - SBT Scaladoc 配置

scala - HList参数为HNil时如何对Json进行编码?

没有 "log.xxxx"的 Scala 调用记录方法

Scala 和转发引用

scala - 模式匹配依赖类型 - 如何避免 asInstanceOf?

java - 在 Scala 中对 Java 列表进行平面映射

java - 脂肪 jar 的动态负载

scala - 如何使用 Spray-client 设置非标准用户代理?