scala - 部分应用的通用函数 "cannot be cast to Nothing"

标签 scala classcastexception typeclass generic-programming separation-of-concerns

标题描述了我在尝试解决一个更普遍的问题时遇到的一个具体问题:如何将类型转换关注点与计算关注点分开。如果我可以通过部分应用函数之外的其他方法解决这个更大的问题,那就太好了!

我正在使用类型类 NumberOps 来表示对数字的操作。此代码已配对,但仍然显示问题并表达了我的意图。第一部分简单地定义了类型类和几个实现。

trait NumberOps[T] {  // the type class (simplified for debugging)
  def neg(x: T): T    // negate x
  def abs(x: T): T    // absolute value of x
  // ... over 50 more operations
  def toFloating(x:T):AnyVal    // convert from native to Float or Double, preserving precision
  def fromFloating(f:AnyVal):T  // convert from Float or Double to native
  // ... also to/from Integral and to/from Big
}

object NumberOps {    // Implements NumberOps for each type
  import language.implicitConversions

  implicit object FloatOps extends NumberOps[Float] {
    def neg(x: Float): Float = -x
    def abs(x: Float): Float = x.abs
    def toFloating(f:Float):Float = f
    def fromFloating(x:AnyVal):Float = {
      x match {
        case f:Float => f
        case d:Double => d.toFloat
      }
    }
  }

  implicit object DoubleOps extends NumberOps[Double] {
    def neg(x: Double): Double = -x
    def abs(x: Double): Double = x.abs
    def toFloating(d:Double):Double = d
    def fromFloating(x:AnyVal):Double = {
      x match {
        case f:Float => f.toDouble
        case d:Double => d
      }
    }
  }

// ... other implicits defined for all primitive types, plus BigInt, BigDec

}    // NumberOps object

一切都很好。但现在我想为复数实现 NumberOps。复数将表示为已定义的任何数字类型(即所有原始类型加上 BigInt 和 BigDecimal)的 2 元素数组。

此代码的目的是避免数字类型与数字运算的组合爆炸。我曾希望通过将关注点 A(类型转换)与关注点 B(通用计算)分开来实现这一点。

您会注意到“关注点 A”体现在 def eval 中,而“关注点 B”被定义为泛型方法 f,然后作为偏应用函数 (f _) 传递给方法 eval。这段代码依赖于之前的代码。

object ImaginaryOps {    // Implements NumberOps for complex numbers, as 2-element arrays of any numeric type
  import language.implicitConversions
  import reflect.ClassTag
  import NumberOps._

  implicit def ComplexOps[U: NumberOps : ClassTag]: NumberOps[Array[U]] = {    // NumberOps[T] :: NumberOps[Array[U]]

    val numOps = implicitly[NumberOps[U]]

    type OpF2[V] = (V,V) => NumberOps[V] => (V,V)    // equivalent to curried function: f[V](V,V)(NumberOps[V]):(V,V)

    // Concern A: widen x,y from native type U to type V, evaluate function f, then convert the result back to native type U
    def eval[V](x:U, y:U)(f:OpF2[V]):(U,U) = {
      (numOps.toFloating(x), numOps.toFloating(y), f) match {
        case (xf:Float, yf:Float, _:OpF2[Float] @unchecked) =>    // _:opF @unchecked permits compiler type inference on f
          val (xv,yv) = f(xf.asInstanceOf[V], yf.asInstanceOf[V])(FloatOps.asInstanceOf[NumberOps[V]])
          (numOps.fromFloating(xv.asInstanceOf[Float]), numOps.fromFloating(yv.asInstanceOf[Float]))
        case (xd:Double, yd:Double, _:OpF2[Double] @unchecked) =>    // _:opF @unchecked permits compiler type inference on f
          val (xv,yv) = f(xd.asInstanceOf[V], yd.asInstanceOf[V])(DoubleOps.asInstanceOf[NumberOps[V]])
          (numOps.fromFloating(xv.asInstanceOf[Double]), numOps.fromFloating(yv.asInstanceOf[Double]))
      }
    }    // eval

    new NumberOps[Array[U]]{    // implement NumberOps for complex numbers of any type U
      def neg(a: Array[U]): Array[U] = a match { case (Array(ax, ay)) =>
        def f[V](xv:V, yv:V)(no:NumberOps[V]):(V,V) = (no.neg(xv), no.neg(yv))  // Concern B: the complex calculation
        val (xu,yu) = eval(a(0), a(1))(f _)    // combine Concern A (widening conversion) with Concern B (calculation)
        a(0) = xu; a(1) = yu; a
      }
      def abs(a: Array[U]): Array[U] = a match { case (Array(ax, ay)) =>
        def f[V](xv:V, yv:V)(no:NumberOps[V]):(V,V) = (no.abs(xv), no.abs(yv))  // Concern B: the complex calculation
        val (xu,yu) = eval(a(0), a(1))(f _)    // combine Concern A (widening conversion) with Concern B (calculation)
        a(0) = xu; a(1) = yu; a
      }
      def toFloating(a:Array[U]):AnyVal = numOps.toFloating( a(0) )
      def fromFloating(x:AnyVal):Array[U] = Array(numOps.fromFloating(x), numOps.fromFloating(x))
    }
  }    // implicit def ComplexOps

}    // ImaginaryOps object

object TestNumberOps {

  def cxStr(a:Any) = { a match { case ad: Array[Double] => s"${ad(0)} + ${ad(1)}i" } }

  def cxUnary[T:NumberOps](v: T)(unaryOp:T => T): T = {
    val ops = implicitly[NumberOps[T]]
    unaryOp(v)
  }

  def main(args:Array[String]) {
    println("TestNo4")
    import ImaginaryOps._
    val complexDoubleOps = implicitly[NumberOps[Array[Double]]]
    val complex1 = Array(1.0,1.0)
    val neg1 = cxUnary(complex1)(complexDoubleOps.neg _)
    val abs1 = cxUnary(neg1)(complexDoubleOps.abs _)
    println(s"adz1 = ${cxStr(complex1)}, neg1 = ${cxStr(neg1)}, abs1 = ${cxStr(abs1)}, ")
  }

}    // TestNumberOps

现在这段代码可以编译,但在运行时我得到一个类转换异常:

Exception in thread "main" java.lang.ClassCastException: java.lang.Double cannot be cast to scala.runtime.Nothing$
  at ImaginaryOps$$anon$1$$anonfun$1.apply(Experiment4.scala:68)
  at ImaginaryOps$.ImaginaryOps$$eval$1(Experiment4.scala:60)
  at ImaginaryOps$$anon$1.neg(Experiment4.scala:68)
  at TestNumberOps$$anonfun$3.apply(Experiment4.scala:97)
  at TestNumberOps$$anonfun$3.apply(Experiment4.scala:97)
  at TestNumberOps$.cxUnary(Experiment4.scala:89)
  at TestNumberOps$.main(Experiment4.scala:97)
  at TestNumberOps.main(Experiment4.scala)

我明白为什么会出现这个异常。这是因为编译器无法解析 def f[V] 的类型 V,所以当它作为 (f _) 传递给方法 eval 时,它的泛型类型 V 已更改为 scala.runtime.Nothing。

苦苦挣扎,无果而终,在网上搜索无果后,我希望能在这里找到有用的建议。可能我使它变得比实际更难,但是使用 Scala 的强类型系统应该有一个解决方案。问题是如何告诉编译器在计算 this 函数时使用 this 类型。

最佳答案

您要做的是为您的复数使用派生类型类。

考虑以下简化场景,

 trait Addable[A] {
   def apply(a: A, b: A): A
 }

 implicit val intAddable: Addable[Int] = new Addable[Int] {
    def apply(a: Int, b: Int): Float = a + b
 }

 implicit val floatAddable: Addable[Float] = new Addable[Float] {
   def apply(a: Float, b: Float): Float = a + b
 }

 implicit final class AddOps[A](a: A) {
   def add(b: A)(implicit addable: Addable[A]): A = addable(a, b)
 }

这基本上允许我们调用,1.add(2) 允许 scala 编译器推断存在一个 addable for ints。

但是你的复杂类型呢?因为我们基本上想说对于任何复杂类型都存在一个可加项,它由 2 种类型组成,遵循可加法则,我们基本上是这样定义它的,

 implicit def complexAddable[A](implicit addable: Addable[A]): Addable[Array[A]] = {
     new Addable[Array[A]] {
         def apply(a: Array[A], b: Array[A]): Array[A] = {
            Array(a(0).add(b(0)), a(1).add(b(1)))
         }
     }
}

之所以有效,是因为范围内有一个 Addable[A]。请注意,如果 A 的可添加项不存在,则当然无法创建隐式,因此您拥有可爱的编译时安全性。

您可以在 scalaz、cats、scodec 等优秀的函数库中找到这种模式的用法,并且在 haskell 中被称为类型类模式。

关于scala - 部分应用的通用函数 "cannot be cast to Nothing",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33727449/

相关文章:

java - Scala "pimpers"的良好命名约定

java - ClassCastException 与 "cannot cast"编译错误

java - 在 WebSphere 6.1 上使用 ant/jenkins 生成 EAR 的类转换异常

android.app.Fragment$InstantiationException : Trying to instantiate a class ScannerFragment that is not a Fragment

haskell - 具有多态类型的 STUArray

haskell - 从上下文 `Show a` 获取 `Show (a,b)`

haskell - 模棱两可的情况

python - 如何在Spark中使用ElasticSearch在脚本文档中更新或部分更新?

scala - 根据 scala 中参数的类型选择函数列表的子集

Scala 隐式类限制