Me gustaría implementar una clase C
para almacenar valores de varios tipos numéricos, así como booleanos. Además, me gustaría ser capaz de operar en instancias de esta clase, entre los tipos, la conversión en caso necesario Int --> Double
y Boolean -> Int
, es decir, para ser capaz de añadir Boolean + Boolean
, Int + Boolean
, Boolean + Int
, Int + Double
, Double + Double
etc., devolviendo el más pequeño posible escriba (Int
o Double
) siempre que sea posible.¿Cómo configurar la conversión implícita para permitir la aritmética entre los tipos numéricos?
Hasta el momento se me ocurrió esto:
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
}
Esto funciona para algunos casos (1, 2, 3), pero no hace por (4, 5). La razón es que hay un ensanchamiento implícito del tipo de menor a mayor, pero no a la inversa. En cierto modo, el método
def +[T <% A](that:C[T]) = s.add(this.n, that.n)
alguna manera tiene que tener un método socio que sería algo como:
def +[T, A <% T](that:C[T]):T = that.s.add(this.n, that.n)
pero eso no compila por dos razones, en primer lugar, que el compilador no puede convertir this.n
a tipo T
(aunque especificamos view bound A <% T
), y, en segundo lugar, que incluso si fuera capaz de convertir this.n
, después de escribir el borrado, los dos métodos +
se vuelven ambiguos.
Disculpa, esto es muy largo. ¡Cualquier ayuda sería muy apreciada! De lo contrario, parece que tengo que escribir todas las operaciones entre todos los tipos explícitamente. Y se pondría peludo si tuviera que agregar tipos adicionales (Complex
es el siguiente en el menú ...).
¿Quizás alguien tenga otra manera de lograr todo esto por completo? Siente que hay algo simple que estoy pasando por alto.
¡Gracias de antemano!
Ahha! Veo cómo funciona esto, ¡gracias! Agregar un 'Boolean' resultó ser fácil, de hecho, y el LUB de' Numérico' no será demasiado difícil de cambiar para 'Complex'. Tengo curiosidad: parece que esta solución te ha sido muy útil, ¿en qué contexto te encontraste con este problema? También traté de probar el rendimiento de esta solución y resumir un millón 'C [Int]' s parece ser unas cinco veces más lento que un millón 'Int' ... Cualquier idea sobre cómo comenzar a optimizar esto? – ostolop
Jugué alrededor de esto durante una discusión con @extempore en IRC, no estaba resolviendo un problema particular mío. La sobrecarga de 5x no suena tan mal teniendo en cuenta la indirección. Puede llamar a 'wc.a2b' y' wc.a2c' directamente en lugar de usar el método 'unify'. Actualmente las entradas y salidas de 'NumeriC# plus' están encasilladas, con suerte una versión futura de Scala encontrará la manera de @specializar ese problema. – retronym
@retronym En realidad ... inicié esa discusión. :-) –