È possibile aggiungere un metodo a un tipo incorporato in Scala?
-
08-07-2019 - |
Domanda
Vorrei aggiungere un metodo a un tipo incorporato (ad esempio Double), in modo da poter utilizzare un infix
operatore. È possibile?
Soluzione
Sì e no. Sì, puoi farlo sembrare come se avessi aggiunto un metodo a double
. Ad esempio:
class MyRichDouble(d: Double) {
def <>(other: Double) = d != other
}
implicit def doubleToSyntax(d: Double) = new MyRichDouble(d)
Questo codice aggiunge l'operatore <>
precedentemente non disponibile a qualsiasi oggetto di tipo Double
. Finché il metodo doubleToSyntax
rientra nell'ambito in modo che possa essere invocato senza qualifica, funzionerà quanto segue:
3.1415 <> 2.68 // => true
Il " no " parte della risposta deriva dal fatto che non stai davvero aggiungendo nulla alla classe import Predef._
. Invece, stai creando una conversione da class MyStuff extends PredefTrait
a un nuovo tipo che definisce il metodo desiderato. Questa può essere una tecnica molto più potente delle classi aperte offerte da molti linguaggi dinamici. Capita anche di essere completamente sicuro. : -)
Alcune limitazioni di cui dovresti essere a conoscenza:
- Questa tecnica non ti consente di rimuovere o ridefinire metodi esistenti, basta aggiungerne di nuovi
- Il metodo di conversione implicita (in questo caso,
Stream
) deve essere assolutamente nell'ambito affinché il metodo di estensione desiderato sia disponibile
Idiomaticamente, le conversioni implicite vengono collocate all'interno di oggetti singleton e importate (ad esempio take
) o all'interno di tratti e ereditate (ad esempio Int
).
Leggero a parte: " infix operatori " in Scala sono in realtà metodi. Non esiste alcuna magia associata al metodo str take 5
che gli consente di essere infettato, il parser semplicemente lo accetta in quel modo. Puoi anche usare & Quot; metodi regolari & Quot; come operatori infix, se lo desideri. Ad esempio, la classe str.take(5)
definisce un metodo <=> che accetta un singolo <=> parametro e restituisce un nuovo <=>. Questo può essere usato nel modo seguente:
val str: Stream[Int] = ...
val subStream = str take 5
L'espressione <=> è letteralmente identica a <=>.
Altri suggerimenti
Questa funzionalità è stata utile per implementare una classe che esegue una stima degli errori:
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
}
}