design patterns - ¿Es posible agregar un método a un tipo incorporado en Scala?
design-patterns infix-notation (2)
Me gustaría agregar un método a un tipo incorporado (por ejemplo, Doble), para que pueda usar un operador infix
. ¿Es eso posible?
Esta característica fue útil para implementar una clase que realiza la estimación de errores:
object errorEstimation {
class Estimate(val x: Double, val e: Double) {
def + (that: Estimate) =
new Estimate(this.x + that.x, this.e + that.e)
def - (that: Estimate) =
new Estimate(this.x - that.x, this.e + that.e)
def * (that: Estimate) =
new Estimate(this.x * that.x,
this.x.abs*that.e+that.x.abs*this.e+this.e*that.e)
def / (that: Estimate) =
new Estimate(this.x/that.x,
(this.x.abs*that.e+that.x.abs*this.e)/(that.x.abs*(that.x.abs-that.e)))
def +- (e2: Double) =
new Estimate(x,e+e2)
override def toString =
x + " +- " + e
}
implicit def double2estimate(x: Double): Estimate = new Estimate(x,0)
implicit def int2estimate(x: Int): Estimate = new Estimate(x,0)
def main(args: Array[String]) = {
println(((x: Estimate) => x+2*x+3*x*x)(1 +- 0.1))
// 6.0 +- 0.93
println(((x: Estimate) => (((y: Estimate) => y*y + 2)(x+x)))(1 +- 0.1))
// 6.0 +- 0.84
def poly(x: Estimate) = x+2*x+3/(x*x)
println(poly(3.0 +- 0.1))
// 9.33333 +- 0.3242352
println(poly(30271.3 +- 0.0001))
// 90813.9 +- 0.0003
println(((x: Estimate) => poly(x*x))(3 +- 1.0))
// 27.037 +- 20.931
}
}
Si y no. Sí, puede hacer que parezca que ha agregado un método para double
. Por ejemplo:
class MyRichDouble(d: Double) {
def <>(other: Double) = d != other
}
implicit def doubleToSyntax(d: Double) = new MyRichDouble(d)
Este código agrega el operador <>
previamente no disponible a cualquier objeto de tipo Double
. Siempre que el método doubleToSyntax
esté dentro del alcance para que pueda invocarse sin calificación, lo siguiente funcionará:
3.1415 <> 2.68 // => true
La parte "no" de la respuesta proviene del hecho de que no está realmente agregando nada a la clase Double
. En cambio, está creando una conversión de Double
a un nuevo tipo que define el método que desea. Esta puede ser una técnica mucho más poderosa que las clases abiertas que ofrecen muchos lenguajes dinámicos. También pasa a ser completamente seguro para tipos. :-)
Algunas limitaciones que debe tener en cuenta:
- Esta técnica no te permite eliminar o redefinir los métodos existentes, simplemente agrega nuevos
- El método de conversión implícita (en este caso,
doubleToSyntax
) debe estar dentro del alcance para que el método de extensión deseado esté disponible
Idiomáticamente, las conversiones implícitas se colocan dentro de objetos singleton y se importan (por ejemplo, import Predef._
) o dentro de los rasgos y se heredan (por ejemplo, la class MyStuff extends PredefTrait
).
Levemente a un lado: los "operadores de infijo" en Scala son en realidad métodos. No hay magia asociada con el método <>
que le permita ser infijo, el analizador simplemente lo acepta de esa manera. También puede usar "métodos regulares" como operadores de infijo si lo desea. Por ejemplo, la clase Stream
define un método take
que toma un único parámetro Int
y devuelve un nuevo Stream
. Esto se puede usar de la siguiente manera:
val str: Stream[Int] = ...
val subStream = str take 5
La expresión str take 5
es literalmente idéntica a str.take(5)
.