Я хотел бы реализовать класс 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
является следующим в меню...).
Возможно, у кого-то есть другой способ достигнуть всего этого в целом? Чувствует, что существует что-то простое, которое я пропускаю.
Заранее спасибо!
Хорошо, Дэниел!
Я ограничил решение игнорировать логические значения и работал только с 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
Есть способ сделать это, но я оставлю его ретрониму , чтобы объяснить это, поскольку он написал это решение. : -)