2009-08-02 16 views
9

yo estaba trabajando a través de la "Programación en Scala" libro, y llamó la atención un poco de un problema en la implementación de la clase Rational en el Capítulo 6.Evitar pérdidas de memoria Scala - Scala constructores

Ésta es mi primera versión de la clase Rational (basado en el libro)

class Rational(numerator: Int, denominator: Int) { 
    require(denominator != 0) 

    private val g = gcd(numerator.abs, denominator.abs) 

    val numer = numerator/g 
    val denom = denominator/g 

    override def toString = numer + "/" + denom 

    private def gcd(a: Int, b: Int): Int = 
    if(b == 0) a else gcd(b, a % b) 

    // other methods go here, neither access g 
} 

el problema aquí es que el campo g permanece durante toda la vida de la clase, incluso si nunca se accede de nuevo. Este problema se puede ver mediante la ejecución del siguiente programa de simulacro:

object Test extends Application { 

    val a = new Rational(1, 2) 
    val fields = a.getClass.getDeclaredFields 

    for(field <- fields) { 
    println("Field name: " + field.getName) 
    field.setAccessible(true) 
    println(field.get(a) + "\n") 
    } 

} 

Su producción va a ser:

Field: denom 
2 

Field: numer 
1 

Field: g 
1 

Una solución que encontré en el Scala Wiki consiste en lo siguiente:

class Rational(numerator: Int, denominator: Int) { 
    require(denominator != 0) 

    val (numer, denom) = { 
    val g = gcd(numerator.abs, denominator.abs) 
    (numerator/g, denominator/g) 
    } 

    override def toString = numer + "/" + denom 

    private def gcd(a: Int, b: Int): Int = 
    if(b == 0) a else gcd(b, a % b) 

    // other methods go here 
} 

Aquí, el campo g es solo local para su bloque, pero, al ejecutar la pequeña aplicación de prueba, encontré otro campo x$1 que conserva una copia de la tupla que consiste en (numer, denom)!

Field: denom 
2 

Field: numer 
1 

Field: x$1 
(1,2) 

¿Hay alguna manera de construir un racional en Scala con el algoritmo anterior, sin causar ninguna pérdida de memoria?

Gracias,

Flaviu Cipcigan

+1

La misma pregunta: http://stackoverflow.com/questions/1118669/h ow-do-you-define-a-local-var-val-in-the-primary-constructor-in-scala –

+0

Gracias, y lo siento por hacer la pregunta de nuevo :). Las respuestas en la publicación vinculada aclararon mi pregunta. –

+0

¿Has confirmado que 'denom' y' numer' son realmente valores? No me sorprendería si fueran "solo" métodos de acceso de la forma 'def denom = x $ 1._2'. – Raphael

Respuesta

6

Usted puede hacer esto:

val numer = numerator/gcd(numerator.abs, denominator.abs) 
val denom = denominator/gcd(numerator.abs, denominator.abs) 

Por supuesto que tendría que hacer el cálculo en dos ocasiones. Pero luego las optimizaciones son a menudo una compensación entre memoria/espacio y tiempo de ejecución.

Quizás haya otras maneras también, pero entonces el programa puede volverse demasiado complejo, y si hay un lugar donde la optimización rara vez es prematura, es la optimización del poder del cerebro :). Por ejemplo, probablemente podría hacer esto:

val numer = numerator/gcd(numerator.abs, denominator.abs) 
val denom = denominator/(numerator/numer) 

Pero no necesariamente hace que el código sea más comprensible.

(Nota: Yo en realidad no intento esto, a fin de utilizar a su propio riesgo.)

+0

Gracias, su segunda solución funciona (aunque no he hecho ninguna prueba rigurosa) y se deshace de cualquier campo superfluo con una sobrecarga insignificante. –

12

Un objeto acompañante puede proporcionar la flexibilidad que necesita. Puede definir un método de fábrica "estático" que reemplace al constructor.

object Rational{ 

    def apply(numerator: Int, denominator: Int) = { 
     def gcd(a: Int, b: Int): Int = if(b == 0) a else gcd(b, a % b) 
     val g = gcd(numerator, denominator) 
     new Rational(numerator/g, denominator/g) 
    } 
} 

class Rational(numerator: Int, denominator: Int) { 
    require(denominator != 0) 

    override def toString = numerator + "/" + denominator 
    // other methods go here, neither access g 
} 

val r = Rational(10,200) 

En el alcance del método de fábrica g puede calcularse y utilizarse para derivar los dos valores de constructor.

+1

Gracias por la respuesta, estaba pensando en una fábrica también, pero eso agregaría un par de complicaciones. Por ejemplo, un usuario podría invocar el constructor del objeto (por ejemplo, Rational nuevo (10,20)) y en el proceso crear un racional no válido. Se podría agregar un requerimiento (gcd (numerador, denominador) == 1) al constructor o hacer que el constructor de la clase sea privado y obligar a los usuarios a usar la fábrica. No estoy seguro de qué sería lo mejor ... una fábrica parece un poco exagerada para un Rational :) –

+2

Tenga en cuenta que dado que el nombre del método de fábrica es 'apply', se puede llamar así:' Rational (10, 20) '. –

6

Hay un pequeño problema con el ejemplo de Thomas Jung; todavía le permite crear un objeto racional con un término común en el numerador y el denominador - si se crea el objeto adecuado y con 'nuevo' a sí mismo, en vez de a través del objeto compañera:

val r = new Rational(10, 200) // Oops! Creating a Rational with a common term 

Usted puede evitar esto que requiere código de cliente para utilizar siempre el objeto compañero para crear un objeto racional, haciendo que el constructor privado implícita:

class Rational private (numerator: Int, denominator: Int) { 
    // ... 
} 
13

usted puede hacer esto:

object Rational { 
    def gcd(a: Int, b: Int): Int = 
     if(b == 0) a else gcd(b, a % b) 
} 

class Rational private (n: Int, d: Int, g: Int) { 
    require(d != 0) 

    def this(n: Int, d: Int) = this(n, d, Rational.gcd(n.abs, d.abs)) 

    val numer = n/g 

    val denom = d/g 

    override def toString = numer + "/" + denom 

} 
3

... en realidad, no veo cómo esto constituye una "pérdida de memoria".

Declara un campo final dentro del alcance de la instancia de la clase, y aparentemente se sorprende de que "se cuelga". ¿Qué comportamiento esperabas?

¿Falta algo aquí?

+3

El problema es que no hay una forma clara de definir una variable temporal que solo se usa durante la construcción del objeto, como se podría hacer con un constructor de Java. –

+3

Esto no es formalmente una pérdida de memoria, ya que todavía es accesible desde variables globales o locales (por lo que el GC no lo limpia). Sin duda es una "fuga" en el sentido informal de que son datos que siguen vivos aunque nunca los necesitará. – Malvolio

+0

Creo que la elección del título es un poco engañosa. – ziggystar

0

me encontré con este artículo que le puede resultar útil: http://daily-scala.blogspot.com/2010/02/temporary-variables-during-object.html

Parece que podría escribir esto:

class Rational(numerator: Int, denominator: Int) { 
    require(denominator != 0) 

    val (numer,denom) = { 
     val g = gcd(numerator.abs, denominator.abs) 
     (numerator/g, denominator/g) 
    } 

    override def toString = numer + "/" + denom 

    private def gcd(a: Int, b: Int): Int = 
    if(b == 0) a else gcd(b, a % b) 

    // other methods go here, neither access g 
} 
0

podría ser como:

def g = gcd(numerator.abs, denominator.abs) 

en lugar de val

Cuestiones relacionadas