部分应用的泛型函数"不能被强制转换为Nothing"

时间:2015-11-16 02:00:40

标签: 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。复数将表示为已定义的任何数字类型的2元素数组(即所有基本类型加上BigInt和BigDecimal)。

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

你会注意到"关注A"体现在默认,而关注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。什么都没有。

在没有成功的情况下挣扎,在网上徒劳地搜索之后,我希望在这里找到一个有用的建议。可能我会让它变得更难,但是使用Scala强大的类型系统应该有一个解决方案。问题是如何告诉编译器在评估这个函数时使用 this 类型。

1 个答案:

答案 0 :(得分:0)

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

考虑以下简化方案,

 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编译器推断出有一个可添加的int。

然而,对于你的复杂类型呢?因为我们基本上要说存在一个可添加的任何复杂类型,它由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中可以看出它是类型类模式。