5

在这个参数化函数中,为什么我需要强制转换?我怎样才能摆脱它?

/** 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]

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

type mismatch; found : Iterable[B] required: A

这个函数(带有强制转换)适用于我尝试过的所有情况,并且我通过在 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)

请解释!

4

3 回答 3

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     
}

耶,它工作!

而且没有演员表。因此,它甚至适用于 Ranges。

但是,必须从一个空的 retval 开始,然后使用 "++=" 来填充它似乎有点不雅,所以如果有人有更优雅的解决方案,我会全神贯注。

这是我实现的另一个泛型函数,它比上面的要复杂一些,因为返回类型与参数类型不同。即输入是一个A's序列,但输出是一个(A, A)'s序列:

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
}

这是另一个:

/** 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
}

我仍然对“Like”类型和 没有任何深入的了解CanBuildFrom,但我明白了要点。在大多数情况下,将泛型函数的强制转换版本编写为第一遍是很容易的,然后添加CanBuildFromIterableLike样板以使函数更通用和完全类型安全。

于 2012-08-07T00:49:20.360 回答
3

在某些情况下,collect它不会返回与Iterable调用相同的子类型,例如在 a 的情况下Range

scala> everyNth(1 to 10, 2)
java.lang.ClassCastException: scala.collection.immutable.Vector cannot be cast to scala.collection.immutable.Range$Inclusive
        at .<init>(<console>:9)
        at .<clinit>(<console>)
        at .<init>(<console>:11)
        at .<clinit>(<console>)
        at $print(<console>)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:616)
        at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:704)
        at scala.tools.nsc.interpreter.IMain$Request$$anonfun$14.apply(IMain.scala:920)
        at scala.tools.nsc.interpreter.Line$$anonfun$1.apply$mcV$sp(Line.scala:43)
        at scala.tools.nsc.io.package$$anon$2.run(package.scala:25)
        at java.lang.Thread.run(Thread.java:679)
于 2012-08-04T05:15:23.440 回答
1

这里的问题是,通过调用collectxs 将其转换为Iterable[B]. A <% Iterable[B]意味着,A可以被视为Iterable[B],这并不一定意味着,Iterable[B]也可以被视为A。这里实际发生的是

def everyNth[A, B](xs: A, n: Int, offset: Int = 0)(implicit view: (A => Iterable[B])): A =
  (view(xs).zipWithIndex collect {
    case (x, i) if (i + offset) % n == 0 => x
  }).asInstanceOf[A]

当我有例如这个:

class Foo
implicit def foo2Iterable(foo: Foo) = List(foo)

并打电话

everyNth(new Foo, 2)

我明白了

java.lang.ClassCastException: scala.collection.immutable.$colon$colon cannot be cast to Foo

你应该避免在这里投射。要么你添加一个视图Iterable[B] => A

编辑:类型绑定在这里不起作用。

于 2012-08-04T10:02:46.123 回答