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

时间:2012-08-04 05:03:06

标签: 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]

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

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)

请解释一下!

3 个答案:

答案 0 :(得分: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     
}

是的,它有效!!!

还有 NO 强制转换。因此,它甚至适用于Ranges。

然而,不得不从一个空的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
}

这是另一个:

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

我仍然没有对“喜欢”类型和CanBuildFrom有任何深刻的理解,但我得到了要点。在大多数情况下,将通用函数的转换版本编写为第一遍是很容易的,然后添加CanBuildFromIterableLike样板以使函数更通用且完全类型安全。< / p>

答案 1 :(得分:3)

在某些情况下,collect不会返回与调用Iterable相同的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) 的情况下:

{{1}}

答案 2 :(得分:1)

问题在于,通过在xs上调用collect,您可以将其转换为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

添加视图

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