2009-08-02 13 views
9

stavo lavorando attraverso la "Programmazione in Scala" libro, e ha colpito un po 'un problema per l'attuazione della classe Rational nel Capitolo 6.Come evitare perdite di memoria Scala - costruttori Scala

Questo è il mio primo versione della classe Rational (basato sul 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 
} 

il problema qui è che il campo g rimane per tutta la durata della classe, anche se mai più accessibile. Questo problema può essere visto eseguendo il seguente programma finto:

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") 
    } 

} 

La sua uscita sarà:

Field: denom 
2 

Field: numer 
1 

Field: g 
1 

Una soluzione che ho trovato al Scala Wiki comporta la seguente:

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 
} 

Qui, il campo g è solo locale al suo blocco, ma, eseguendo la piccola applicazione di test, ho trovato un altro campo x$1 che conserva una copia della tupla composta da (numer, denom)!

Field: denom 
2 

Field: numer 
1 

Field: x$1 
(1,2) 

C'è un modo per costruire un razionale a Scala con l'algoritmo di cui sopra, senza causare perdite di memoria?

Grazie,

Flaviu Cipcigan

+1

stessa domanda: http://stackoverflow.com/questions/1118669/h OW-do-you-definire-a-local-var-val-in-the-primario-costruttore-in-scala –

+0

Grazie, e mi dispiace per chiedere ancora una volta la questione :). Le risposte nel post collegato hanno chiarito la mia domanda. –

+0

Hai confermato che 'denom' e' numer' sono veramente valori? Non sarei affatto sorpreso se fossero "solo" i metodi di accesso del modulo 'def denom = x $ 1._2'. – Raphael

risposta

6

Si potrebbe fare questo:

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

Naturalmente dovreste fare il calcolo due volte. Ma poi le ottimizzazioni sono spesso un compromesso tra memoria/spazio e tempo di esecuzione.

Forse ci sono anche altri modi, ma il programma potrebbe diventare eccessivamente complesso, e se c'è un posto dove l'ottimizzazione è raramente prematura, è l'ottimizzazione della potenza del cervello :). Ad esempio, è possibile eseguire questa operazione:

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

Ma questo non rende il codice più comprensibile.

(Nota: non ho fatto provare questo, in modo da utilizzare a proprio rischio.)

+0

Grazie, la tua seconda soluzione funziona (anche se non ho effettuato test rigorosi) e non ci sono più campi superflui con spese generali trascurabili. –

12

un oggetto associato può fornire la flessibilità necessaria. Può definire un metodo factory "statico" che sostituisce il costruttore.

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) 

Nell'ambito del metodo factory g può essere calcolato e utilizzato per ricavare i due valori del costruttore.

+1

Grazie per la risposta, stavo pensando anche a una fabbrica, ma questo avrebbe aggiunto un paio di complicazioni. Ad esempio, un utente può richiamare il costruttore dell'oggetto (ad esempio new Rational (10,20)) e nel processo creare un razionale non valido. Si potrebbe aggiungere un requisito (gcd (numeratore, denominatore) == 1) al costruttore o rendere privato il costruttore della classe e obbligare gli utenti a utilizzare la fabbrica. Non sono sicuro di quello che sarebbe stato meglio ... una fabbrica fa sembrare un po 'eccessivo per un razionale :) –

+2

Nota che, dal momento il nome del metodo di fabbrica è 'apply', può essere chiamato in questo modo:' razionale (10, 20) '. –

6

C'è un piccolo problema con l'esempio di Thomas Jung; permette ancora di creare un oggetto razionale con un termine comune nel numeratore e il denominatore - se si crea l'oggetto razionale, usando 'nuova' te stesso, invece che tramite l'oggetto compagna:

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

È possibile evitare questo che richiedono codice client di utilizzare sempre l'oggetto compagna per creare un oggetto razionale, rendendo l'implicito costruttore privato:

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

Si potrebbe fare questo:

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

... in realtà, non vedo come ciò costituisca una "perdita di memoria".

si dichiara una campo finale nell'ambito di applicazione della istanza di classe, e sono quindi a quanto pare sorpreso che "va in giro". Che comportamento ti aspettavi?

Mi manca qualcosa qui?

+3

Il problema è che non esiste un modo pulito per definire una variabile temporanea che viene utilizzata solo durante la costruzione dell'oggetto, come con un costruttore Java. –

+3

Questo non è formalmente una perdita di memoria, dal momento che è ancora raggiungibile dalle variabili globali o locali (che è il motivo per cui il GC non ripulirlo). È certamente una "perdita" nel senso informale che sono dati che vivono anche se non ne avrai mai bisogno. – Malvolio

+0

Penso che la scelta del titolo sia un po 'fuorviante. – ziggystar

0

mi sono imbattuto in questo articolo che potreste trovare utili: http://daily-scala.blogspot.com/2010/02/temporary-variables-during-object.html

Sembra si potrebbe scrivere questo:

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

potrebbe essere come:

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

invece di val