2011-12-31 16 views
10

Sto cercando un modo per avere classi che si comportano proprio come le classi dei casi, ma che sono automaticamente hash consed.Classi case Hash Consed automaticamente

Un modo per raggiungere questo obiettivo per gli elenchi interi sarebbe:

import scala.collection.mutable.{Map=>MutableMap} 

sealed abstract class List 
class Cons(val head: Int, val tail: List) extends List 
case object Nil extends List 

object Cons { 
    val cache : MutableMap[(Int,List),Cons] = MutableMap.empty 
    def apply(head : Int, tail : List) = cache.getOrElse((head,tail), { 
    val newCons = new Cons(head, tail) 
    cache((head,tail)) = newCons 
    newCons 
    }) 
    def unapply(lst : List) : Option[(Int,List)] = { 
    if (lst != null && lst.isInstanceOf[Cons]) { 
     val asCons = lst.asInstanceOf[Cons] 
     Some((asCons.head, asCons.tail)) 
    } else None 
    } 
} 

E, per esempio, mentre

scala> (5 :: 4 :: scala.Nil) eq (5 :: 4 :: scala.Nil) 
resN: Boolean = false 

otteniamo

scala> Cons(5, Cons(4, Nil)) eq Cons(5, Cons(4, Nil)) 
resN: Boolean = true 

Ora quello che sto cercando per è un generico modo per raggiungere questo (o qualcosa di molto simile). Idealmente, non voglio dover digitare molto più di:

class Cons(val head : Int, val tail : List) extends List with HashConsed2[Int,List] 

(o simile). Qualcuno può inventare qualche sistema tipo voodoo per aiutarmi, o dovrò aspettare che il linguaggio macro sia disponibile?

risposta

3

È possibile definire alcune InternableN[Arg1, Arg2, ..., ResultType] tratti per N è il numero di argomenti per apply(): Internable1[A,Z], Internable2[A,B,Z], ecc. Questi tratti definiscono la cache stessa, il metodo intern() e il metodo apply che vogliamo dirottare .

Dovremo definire un tratto (o una classe astratta) per assicurare i tratti InternableN che esiste effettivamente un metodo apply da sovrascrivere, chiamiamolo Applyable.

trait Applyable1[A, Z] { 
    def apply(a: A): Z 
} 
trait Internable1[A, Z] extends Applyable1[A, Z] { 
    private[this] val cache = WeakHashMap[(A), Z]() 
    private[this] def intern(args: (A))(builder: => Z) = { 
    cache.getOrElse(args, { 
     val newObj = builder 
     cache(args) = newObj 
     newObj 
    }) 
    } 
    abstract override def apply(arg: A) = { 
    println("Internable1: hijacking apply") 
    intern(arg) { super.apply(arg) } 
    } 
} 

L'oggetto associato della classe dovrà essere un mixin di una classe concreta attuazione ApplyableN con InternableN. Non avrebbe funzionato applicare direttamente definito nel tuo oggetto compagno.

// class with one apply arg 
abstract class SomeClassCompanion extends Applyable1[Int, SomeClass] { 
    def apply(value: Int): SomeClass = { 
    println("original apply") 
    new SomeClass(value) 
    } 
} 
class SomeClass(val value: Int) 
object SomeClass extends SomeClassCompanion with Internable1[Int, SomeClass] 

Una cosa buona di questo è che l'applicazione originale non deve essere modificata per provvedere all'internamento. Crea solo istanze e viene chiamato solo quando devono essere creati.

L'intera operazione può (e dovrebbe) essere definita anche per le classi con più di un argomento. Per il caso a due argomenti:

trait Applyable2[A, B, Z] { 
    def apply(a: A, b: B): Z 
} 
trait Internable2[A, B, Z] extends Applyable2[A, B, Z] { 
    private[this] val cache = WeakHashMap[(A, B), Z]() 
    private[this] def intern(args: (A, B))(builder: => Z) = { 
    cache.getOrElse(args, { 
     val newObj = builder 
     cache(args) = newObj 
     newObj 
    }) 
    } 
    abstract override def apply(a: A, b: B) = { 
    println("Internable2: hijacking apply") 
    intern((a, b)) { super.apply(a, b) } 
    } 
} 

// class with two apply arg 
abstract class AnotherClassCompanion extends Applyable2[String, String, AnotherClass] { 
    def apply(one: String, two: String): AnotherClass = { 
    println("original apply") 
    new AnotherClass(one, two) 
    } 
} 
class AnotherClass(val one: String, val two: String) 
object AnotherClass extends AnotherClassCompanion with Internable2[String, String, AnotherClass] 

L'interazione dimostra che Internables' diffusa metodo esegue prima all'originale apply() che viene eseguita solo se necessario.

scala> import SomeClass._ 
import SomeClass._ 

scala> SomeClass(1) 
Internable1: hijacking apply 
original apply 
res0: SomeClass = [email protected] 

scala> import AnotherClass._ 
import AnotherClass._ 

scala> AnotherClass("earthling", "greetings") 
Internable2: hijacking apply 
original apply 
res1: AnotherClass = [email protected] 

scala> AnotherClass("earthling", "greetings") 
Internable2: hijacking apply 
res2: AnotherClass = [email protected] 

ho scelto di usare un WeakHashMap in modo che la cache internato non impedisce garbage collection di istanze internati una volta non sono più riferimento altrove.

Codice ordinabile as a Github gist.

1

Forse un po 'hacky, ma si potrebbe provare a definire un proprio metodo intern(), come Java di String è:

import scala.collection.mutable.{Map=>MutableMap} 

object HashConsed { 
    val cache: MutableMap[(Class[_],Int), HashConsed] = MutableMap.empty 
} 

trait HashConsed { 
    def intern(): HashConsed = 
    HashConsed.cache.getOrElse((getClass, hashCode), { 
     HashConsed.cache((getClass, hashCode)) = this 
     this 
    }) 
} 

case class Foo(bar: Int, baz: String) extends HashConsed 

val foo1 = Foo(1, "one").intern() 
val foo2 = Foo(1, "one").intern() 

println(foo1 == foo2) // true 
println(foo1 eq foo2) // true 
Problemi correlati