3

В названии описывается конкретная проблема, с которой я столкнулся при попытке решить более общую проблему: как отделить проблему преобразования типов от проблемы расчета. Если я смогу решить эту большую проблему другим способом, чем частично примененные функции, отлично!Частично примененная общая функция «не может быть применена к 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 для сложных чисел. Комплексное число будет представлено как 2-элементный массив любого уже определенного числового типа (т. Е. Все примитивные типы плюс BigInt и BigDecimal).

Целью этого кода является предотвращение комбинаторного взрыва числовых типов с числовыми операциями. Я надеялся добиться этого, отделив Концерн А (преобразование типов) от Концерна Б (общий расчет).

Вы заметите, что «Концерн А» воплощен в 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 as (f_), его общий тип V был изменен на scala.runtime.Nothing.

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

ответ

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 компилятор сделать вывод, что существует addable для Интс.

Однако как насчет вашего сложного типа? Так как мы хотим, чтобы по существу сказать, существует addable для любого сложного типа, который скомпонованный из 2-х типов, которые следуют за addable закону мы по существу определить его, как это,

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 не существует, и, следовательно, у вас прекрасная безопасность времени компиляции.

Вы можете найти способы использования этого шаблона в превосходных функциональных библиотеках, таких как скалаз, кошки, scodec и т. Д., И известно из haskell как шаблон типа.

Смежные вопросы