Как настроить неявное преобразование для разрешения арифметики между числовыми типами?

Я хотел бы реализовать класс 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 является следующим в меню...).

Возможно, у кого-то есть другой способ достигнуть всего этого в целом? Чувствует, что существует что-то простое, которое я пропускаю.

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

9
задан ostolop 21 June 2010 в 22:37
поделиться

2 ответа

Хорошо, Дэниел!

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

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

Во-первых, нам нужно класс типа, представляющий слабую наименьшую верхнюю границу всех пар типов 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
6
ответ дан 4 December 2019 в 21:47
поделиться

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

3
ответ дан 4 December 2019 в 21:47
поделиться
Другие вопросы по тегам:

Похожие вопросы: