2016-07-24 7 views
0

Von was ich verstanden habe, behandelt Scala Val-Definitionen als Werte. Also sollte jede Instanz einer Fallklasse mit gleichen Parametern gleich sein. AberWarum evaluiert jede neue Instanz von Fallklassen in Scala wieder Lazy Vals?

case class A(a: Int) { 
    lazy val k = { 
     println("k") 
     1 
    } 

val a1 = A(5) 
println(a1.k) 
Output: 
k 
res1: Int = 1 

println(a1.k) 
Output: 
res2: Int = 1 

val a2 = A(5) 
println(a1.k) 
Output: 
k 
res3: Int = 1 

Ich hatte erwartet, dass für println (A2.k), ist es nicht k gedruckt werden soll. Da dies nicht das erforderliche Verhalten ist, wie soll ich das implementieren , so dass für alle Instanzen einer Fallklasse mit denselben Parametern nur eine Lazy Val-Definition nur einmal ausgeführt werden soll. Benötige ich eine Memo-Technik oder kann Scala alleine damit umgehen?

Ich bin sehr neu zu Scala und funktionale Programmierung so bitte entschuldigen Sie mich, wenn Sie die Frage trivial finden.

+0

Sie sind verschiedene Klasseninstanzen im Speicher, natürlich werden sie zweimal initialisiert? Sie sollten das Companion-Objekt von 'A' verwenden, wenn Sie nur eine Instanz haben möchten ... –

Antwort

0

Ich glaube, case class es nur die Argumente zu ihrem Konstruktor (kein Hilfskonstruktor) zu sein Teil ihrer Gleichheit Konzept. Wenn Sie eine Case-Klasse in einer match-Anweisung verwenden, erhalten Sie mit unapply nur (standardmäßig) Zugriff auf die Konstruktorparameter.

Betrachten Sie alles im Körper von Fallklassen als "Extra" oder "Nebeneffekt". Ich halte es für eine gute Taktik, Fallklassen so nah wie möglich leer zu machen und jede benutzerdefinierte Logik in ein Begleitobjekt zu legen. ZB:

case class Foo(a:Int) 

object Foo { 
    def apply(s: String) = Foo(s.toInt) 
} 
2

Angenommen, Sie sind nicht zwingende equals oder schlecht beraten, etwas zu tun, wie der Konstruktor args var s machen, ist es der Fall, dass zwei Fall Klasse instantiations mit gleichen Konstruktorargumente gleich sein wird. Allerdings bedeutet dies nicht bedeuten, dass zwei Fallklasse instantiations mit gleichen Konstruktorargumente zum gleichen Objekt in Speichern zeigen wird:

case class A(a: Int) 
A(5) == A(5) // true, same as `A(5).equals(A(5))` 
A(5) eq A(5) // false 

Wenn Sie wollen, dass der Konstruktor immer gibt das gleiche Objekt im Speicher Dann musst du selbst damit umgehen. verwenden eine Art Fabrik Vielleicht:

case class A private (a: Int) { 
    lazy val k = { 
    println("k") 
    1 
    } 
} 

object A { 
    private[this] val cache = collection.mutable.Map[Int, A]() 
    def build(a: Int) = { 
    cache.getOrElseUpdate(a, A(a)) 
    } 
} 

val x = A.build(5) 
x.k     // prints k 
val y = A.build(5) 
y.k     // doesn't print anything 
x == y    // true 
x eq y    // true 

Wenn stattdessen Sie über den Konstruktor nicht darum das gleiche Objekt zurückkehrt, aber Sie nur Sorge um die Neubewertung von k, können Sie nur den Teil-Cache:

case class A(a: Int) { 
    lazy val k = A.kCache.getOrElseUpdate(a, { 
    println("k") 
    1 
    }) 
} 

object A { 
    private[A] val kCache = collection.mutable.Map[Int, Int]() 
} 

A(5).k  // prints k 
A(5).k  // doesn't print anything 
+0

Ich möchte darauf hinweisen, dass die 'Symbol'-Klasse der Standardbibliothek eine nette Referenzimplementierung dieses Musters ist. – HTNW

0

Neben DHG Antwort, würde ich sagen, ich bin mir nicht bewusst funktionalen Sprache, die standardmäßig voll Konstruktor memoizing tut. Sie sollten verstehen, dass eine solche Speicherung bedeutet, dass alle konstruierten Instanzen im Speicher bleiben sollten, was nicht immer wünschenswert ist.

Manuelles Caching ist nicht so schwer, sollten Sie diesen einfachen Code

import scala.collection.mutable 

class Doubler private(a: Int) { 
    lazy val double = { 
    println("calculated") 
    a * 2 
    } 
} 

object Doubler{ 
    val cache = mutable.WeakHashMap.empty[Int, Doubler] 
    def apply(a: Int): Doubler = cache.getOrElseUpdate(a, new Doubler(a)) 
} 

Doubler(1).double //calculated 

Doubler(5).double //calculated 

Doubler(1).double //most probably not calculated 
+1

Der Nachteil dieser Vorgehensweise besteht darin, dass Sie alle Vorteile der Verwendung von Fallklassen verlieren, es sei denn, Sie implementieren equals, hashCode, unapply, ... – dhg

1

Die triviale Antwort lautet: „Das ist, was die Sprache tut nach der Spezifikation“. Das ist die richtige, aber nicht sehr befriedigende Antwort. Es ist interessanter, warum es das tut.

Es könnte klarer sein, dass es dies mit einem anderen Beispiel zu tun hat:

case class A[B](b: B) { 
    lazy val k = { 
     println(b) 
     1 
    } 
} 

Wenn Sie konstruieren zwei A ist, können Sie nicht wissen, ob sie gleich sind, weil Sie habe nicht definiert, was es bedeutet, dass sie gleich sind (oder was es bedeutet, dass B gleich ist). Und Sie können auch nicht k statisch initialisieren, da es auf dem übergebenen B beruht.

Wenn dies zweimal gedruckt werden muss, wäre es völlig intuitiv, wenn das nur der Fall wäre, wenn k von b abhängt, aber nicht, wenn es nicht von b abhängt.

Wenn Sie fragen,

wie soll ich das implementieren, so dass für alle Instanzen einer Fallklasse mit denselben Parametern, es sollte nur einmal

eine faule val Definition ausführen, die ein heikler ist Frage, als es sich anhört. Sie machen "die gleichen Parameter" wie etwas klingen, das zur Kompilierzeit ohne weitere Informationen bekannt ist. Es ist nicht, Sie können es nur zur Laufzeit wissen.

Und wenn Sie nur wissen, dass zur Laufzeit, müssen Sie alle früheren Verwendungen der Instanz A[B] am Leben erhalten. Dies ist ein eingebautes Speicherleck - kein Wunder, dass Scala keine eingebaute Möglichkeit hat, dies zu tun.

Wenn Sie dies wirklich wollen - und lange über das Speicherleck nachdenken - konstruieren Sie eine Map[B, A[B]], und versuchen Sie, eine im Cache gespeicherte Instanz von dieser Karte zu bekommen, und wenn es nicht existiert, konstruieren Sie eine und legen Sie sie in die Karte.

Verwandte Themen