【问题标题】:In this parameterized Scala function, why do I need the cast?在这个参数化的 Scala 函数中,为什么我需要强制转换?
【发布时间】:2017-03-10 07:17:18
【问题描述】:

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

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

请解释一下!

【问题讨论】:

  • 使用CanBuildFrom 解决问题的类似问题:Function which generically takes a type and returns the same type。我无法解决这个问题,其他人?
  • 我让 CanBuildFrom 来解决我的问题,并将解决方案放入答案中。如果您好奇,请参阅下面的答案。
  • 不错的答案!顺便说一句,您可以接受自己的答案...

标签: scala


【解决方案1】:

根据 cmets 中的一些建议,我研究了 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的序列,但输出是(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
}

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

【讨论】:

    【解决方案2】:

    在某些情况下,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)
    

    【讨论】:

    • 啊,当然。愚蠢的范围!是否还有其他一些可以使用的特征来排除这种行为不端的序列?还是我应该和演员一起生活?
    • 我想正确的方法是使用集合 API 中使用的 CanBuildFrom 魔法?
    • 我让 CanBuildFrom 来解决我的问题,并将解决方案放在这个答案旁边。
    【解决方案3】:

    这里的问题是,通过在 xs 上调用 collect 可以将其转换为 Iterable[B]A &lt;% 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] =&gt; A添加一个视图

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

    【讨论】:

    • 用类型绑定替换视图绑定并不能消除这里对强制转换的需要,所以我不太清楚你在断言什么。
    • 对不起,你是对的。从 Iterable[B] => A 添加一个视图将是这里唯一的解决方案。
    猜你喜欢
    • 1970-01-01
    • 2014-08-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多