É possível adicionar um método para um built-in tipo em Scala?
-
08-07-2019 - |
Pergunta
Eu gostaria de adicionar um método para uma (por exemplo Duplo) built-in tipo, para que eu possa usar um operador infix
. Isso é possível?
Solução
Sim e não. Sim, você pode fazê-lo parecem como você adicionou um método para double
. Por exemplo:
class MyRichDouble(d: Double) {
def <>(other: Double) = d != other
}
implicit def doubleToSyntax(d: Double) = new MyRichDouble(d)
Este código adiciona o operador <>
anteriormente indisponíveis para qualquer objeto do tipo Double
. Enquanto o método doubleToSyntax
está no escopo para que pudesse ser invocado sem qualificação, o seguinte irá funcionar:
3.1415 <> 2.68 // => true
O "não" parte da resposta vem do fato de que você não está realmente acrescentar nada para a classe Double
. Em vez disso, você está criando uma conversão de Double
a um novo tipo que não definem o método que você deseja. Esta pode ser uma técnica muito mais poderoso do que as classes abertas oferecidos por muitas linguagens dinâmicas. Também acontece de ser completamente tipo seguro. : -)
Algumas limitações que você deve estar ciente de:
- Esta técnica não permitem que você remover ou Redefinir métodos existentes, basta adicionar novos
- O método de conversão implícita (neste caso,
doubleToSyntax
) tem absolutamente de ser dentro do escopo do método de extensão desejado para estar disponível
Idiomaticamente, conversões implícitas ou são colocados dentro de objectos únicos e importadas (por exemplo import Predef._
) ou dentro de traços e herdadas (por exemplo class MyStuff extends PredefTrait
).
Leve de lado: "operadores infix" em Scala são realmente métodos. Não há mágica associada com o método <>
que permite que ele seja infix, o analisador simplesmente aceita-lo dessa maneira. Você também pode usar "métodos regulares" como operadores infixas se quiser. Por exemplo, a classe Stream
define um método take
que leva um único parâmetro Int
e retorna uma nova Stream
. Isso pode ser usado da seguinte maneira:
val str: Stream[Int] = ...
val subStream = str take 5
A expressão str take 5
é literalmente idêntico ao str.take(5)
.
Outras dicas
Este recurso veio a calhar para implementar uma classe realizar estimativa de erro:
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
}
}