Частично примененная универсальная функция не может быть преобразована в Nothing

В заголовке описывается конкретная проблема, с которой я столкнулся при попытке решить более общую проблему: как отделить проблему преобразования типа от проблемы вычисления. Если я смогу решить эту большую проблему другим способом, кроме частично применяемых функций, отлично!

Я использую класс типа 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).

Цель этого кода - избежать комбинаторного взрыва числовых типов с числовыми операциями. Я надеялся достичь этого, отделив проблему 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)

Я понимаю, почему возникает это исключение. Это потому, что компилятор не смог разрешить тип V def f [V], поэтому, когда он передается методу eval как (f _), его общий тип V был изменен на scala.runtime.Nothing.

После безуспешных попыток и безуспешных поисков в Интернете, я надеюсь найти здесь полезное предложение. Возможно, я делаю это сложнее, чем есть на самом деле, но с сильной системой типов Scala должно быть решение. Проблема в том, как указать компилятору использовать тип this для оценки функции this.




Ответы (1)


Что вы хотите сделать, так это использовать класс производного типа для вашего комплексного числа.

Рассмотрим следующий упрощенный сценарий,

 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 сделать вывод о том, что есть добавляемый для 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 как шаблон класса типов.

person yw3410    schedule 08.01.2016