在这个参数化的Scala函数中,为什么需要强制转换?

Dou*_*las 5 scala

在这个参数化函数中,为什么我需要演员?我怎么能摆脱它呢?

/** Filters `xs` to have only every nth element.
  */
def everyNth[A <% Iterable[B], B](xs: A, n: Int, offset: Int = 0): A =
  (xs.zipWithIndex collect { case (x, i) if (i - offset) % n == 0 => x }).asInstanceOf[A]
Run Code Online (Sandbox Code Playgroud)

如果我最后没有演员,我会收到以下错误消息:

type mismatch; found : Iterable[B] required: A
Run Code Online (Sandbox Code Playgroud)

这个函数(使用强制转换)适用于我尝试过的所有情况,我知道在REPL中输入类似下面的内容,Scala能够在不在参数化函数的上下文中正确推断结果类型:

scala> val a: Stream[Int] = (Stream.from(0).zipWithIndex collect { case (x, i) if (i + 3) % 5 == 0 => x })
a: Stream[Int] = Stream(2, ?)

scala> a take 10 force
res20: scala.collection.immutable.Stream[Int] = Stream(2, 7, 12, 17, 22, 27, 32, 37, 42, 47)
Run Code Online (Sandbox Code Playgroud)

请解释!

Dou*_*las 4

根据评论中的一些建议,我研究了 CanBuildFrom,这就是我想到的:

import scala.collection.IterableLike
import scala.collection.generic.CanBuildFrom

/** Filters `xs` to have only every nth element.
  */
def everyNth[A, It <: Iterable[A]]
        (xs: It with IterableLike[A, It], n: Int, offset: Int = 0)
        (implicit bf: CanBuildFrom[It, A , It]): It = {
  val retval = bf()
  retval ++= xs.zipWithIndex collect { case (x, i) if (i - offset) % n == 0 => x }
  retval.result     
}
Run Code Online (Sandbox Code Playgroud)

是的,它有效!

而且没有演员阵容。因此,它甚至适用于范围。

然而,必须从一个空的 retval 开始,然后使用“++=”来填充它似乎有点不优雅,所以如果有人有更优雅的解决方案,我会洗耳恭听。

这是我实现的另一个通用函数,它比上面的函数有点棘手,因为返回类型与参数类型不同。即,输入是A' 的序列,但输出是(A, A)' 的序列:

def zipWithSelf[A, It[A] <: Iterable[A]]
        (xs: It[A] with IterableLike[A, It[A]])
        (implicit bf:  CanBuildFrom[It[A], (A, A), It[(A, A)]]): It[(A, A)] = {
    val retval = bf()
    if (xs.nonEmpty) {
      retval ++= xs zip xs.tail
      retval.result
  } else retval.result
}
Run Code Online (Sandbox Code Playgroud)

这是另一个:

/** Calls `f(x)` for all x in `xs` and returns an Iterable containing the indexes for
  * which `f(x)` is true.
  *
  * The type of the returned Iterable will match the type of `xs`. 
  */
def findAll[A, It[A] <: Iterable[A]]
        (xs: It[A] with IterableLike[A, It[A]])
        (f: A => Boolean)
        (implicit bf:  CanBuildFrom[It[A], Int, It[Int]]): It[Int] = {
    val retval = bf()
    retval ++= xs.zipWithIndex filter { p => f(p._1) } map { _._2 }
    retval.result
}
Run Code Online (Sandbox Code Playgroud)

我对“喜欢”类型和 仍然没有任何深入的了解CanBuildFrom,但我明白了要点。在大多数情况下,首先编写泛型函数的转换版本是很容易的,然后添加CanBuildFromIterableLike样板文件以使函数更通用且完全类型安全。