Как настроить неявное преобразование, чтобы разрешить арифметику между числовыми типами? - PullRequest
9 голосов
/ 22 июня 2010

Я бы хотел реализовать класс C для хранения значений различных числовых типов, а также логических значений. Кроме того, я хотел бы иметь возможность работать с экземплярами этого класса, между типами, преобразовывая при необходимости Int --> Double и Boolean -> Int, т. Е. Чтобы иметь возможность добавлять Boolean + Boolean, Int + Boolean, Boolean + Int, Int + Double, Double + Double и т. Д., Возвращая наименьший возможный тип (Int или Double), когда это возможно.

Пока я придумал это:

abstract class SemiGroup[A] { def add(x:A, y:A):A }

class C[A] (val n:A) (implicit val s:SemiGroup[A]) {
  def +[T <% A](that:C[T]) = s.add(this.n, that.n)
}

object Test extends Application {
  implicit object IntSemiGroup extends SemiGroup[Int] { 
    def add(x: Int, y: Int):Int = x + y 
  }

  implicit object DoubleSemiGroup extends SemiGroup[Double] { 
    def add(x: Double, y: Double):Double = x + y 
  }

  implicit object BooleanSemiGroup extends SemiGroup[Boolean] { 
    def add(x: Boolean, y: Boolean):Boolean = true;
  }

  implicit def bool2int(b:Boolean):Int = if(b) 1 else 0

  val n = new C[Int](10)
  val d = new C[Double](10.5)
  val b = new C[Boolean](true)

  println(d + n)    // [1]
  println(n + n)    // [2]
  println(n + b)    // [3]
  // println(n + d)    [4] XXX - no implicit conversion of Double to Int exists
  // println(b + n)    [5] XXX - no implicit conversion of Int to Boolean exists
}

Это работает для некоторых случаев (1, 2, 3), но не для (4, 5). Причина в том, что существует неявное расширение типа от низшего к высшему, но не наоборот. В некотором смысле, метод

def +[T <% A](that:C[T]) = s.add(this.n, that.n)

каким-то образом нужен метод партнера, который будет выглядеть примерно так:

def +[T, A <% T](that:C[T]):T = that.s.add(this.n, that.n)

, но это не компилируется по двум причинам: во-первых, компилятор не может преобразовать this.n в тип T (даже если мы указываем границы вида A <% T), и, во-вторых, даже если он был способен конвертировать this.n, после стирания типа два + метода становятся неоднозначными.

Извините, это так долго. Любая помощь приветствуется! В противном случае кажется, что я должен явно записать все операции между всеми типами. И было бы сложно, если бы мне пришлось добавить дополнительные типы (Complex следующий в меню ...).

Может быть, у кого-то есть другой способ достичь всего этого? Кажется, что-то простое я пропускаю.

Заранее спасибо!

Ответы [ 2 ]

6 голосов
/ 22 июня 2010

Хорошо, Даниэль!

Я ограничил решение игнорированием логического значения и работаю только с AnyVals со слабой наименьшей верхней границей, которая имеет экземпляр Numeric.Эти ограничения являются произвольными, вы можете удалить их и закодировать ваши собственные слабые соответствия между типами - реализация a2b и a2c может выполнить некоторое преобразование.

Интересно рассмотреть, как неявные параметры могут симулироватьнаследование (передача неявных параметров типа (Derived => Base) или Weak Conformance. Они действительно мощные, особенно когда помогает выводчик типов.

Во-первых, нам нужен класс типа для представления Weak Least UpperСвязывается со всеми интересующими нас парами типов A и B.

sealed trait WeakConformance[A <: AnyVal, B <: AnyVal, C] {
  implicit def aToC(a: A): C

  implicit def bToC(b: B): C
}

object WeakConformance {
  implicit def SameSame[T <: AnyVal]: WeakConformance[T, T, T] = new WeakConformance[T, T, T] {
    implicit def aToC(a: T): T = a

    implicit def bToC(b: T): T = b
  }

  implicit def IntDouble: WeakConformance[Int, Double, Double] = new WeakConformance[Int, Double, Double] {
    implicit def aToC(a: Int) = a

    implicit def bToC(b: Double) = b
  }

  implicit def DoubleInt: WeakConformance[Double, Int, Double] = new WeakConformance[Double, Int, Double] {
    implicit def aToC(a: Double) = a

    implicit def bToC(b: Int) = b
  }

  // More instances go here!


  def unify[A <: AnyVal, B <: AnyVal, C](a: A, b: B)(implicit ev: WeakConformance[A, B, C]): (C, C) = {
    import ev._
    (a: C, b: C)
  }
}

Метод unify возвращает тип C, который определяется механизмом вывода типов в зависимости от доступности.неявных значений для предоставления в качестве неявного аргумента ev.

Мы можем подключить это к вашему классу оболочки C следующим образом, также требуя Numeric[WeakLub], чтобы мы могли добавить значения.

case class C[A <: AnyVal](val value:A) {
  import WeakConformance.unify
  def +[B <: AnyVal, WeakLub <: AnyVal](that:C[B])(implicit wc: WeakConformance[A, B, WeakLub], num: Numeric[WeakLub]): C[WeakLub] = { 
    val w = unify(value, that.value) match { case (x, y) => num.plus(x, y)}; 
    new C[WeakLub](w)
  }
}

И, наконец, все вместе:

object Test extends Application {
  val n = new C[Int](10)
  val d = new C[Double](10.5)

  // The type ascriptions aren't necessary, they are just here to 
  // prove the static type is the Weak LUB of the two sides.
  println(d + n: C[Double]) // C(20.5)
  println(n + n: C[Int])    // C(20)
  println(n + d: C[Double]) // C(20.5)
}

Test
3 голосов
/ 22 июня 2010

Для этого есть способ , но я оставлю это для retronym , чтобы объяснить это, так как он написал это решение. : -)

...