2011-12-31 19 views
10

Estoy buscando una manera de tener clases que se comporten como las clases de casos, pero que son automáticamente hash consed.Clases de casos Consed de hash automáticamente

Una forma de lograr esto para las listas de números enteros sería:

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

Y, por ejemplo, mientras

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

obtenemos

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

Ahora lo que estoy buscando for es un generic forma de lograr esto (o algo muy similar). Idealmente, no quiero tener que escribir mucho más que:

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

(o similar). ¿Alguien puede venir con algún tipo de sistema vudú para ayudarme, o tendré que esperar a que el macro lenguaje esté disponible?

Respuesta

3

Se pueden definir unos InternableN[Arg1, Arg2, ..., ResultType] rasgos para N es el número de argumentos para apply(): Internable1[A,Z], Internable2[A,B,Z], etc. Estos rasgos definen la caché en sí misma, el método intern() y el método apply que queremos secuestro.

Vamos a tener que definir un rasgo (o una clase abstracta) para asegurar que sus rasgos InternableN que hay de hecho un método de aplicar que se debe anular, llamémoslo 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) } 
    } 
} 

El objeto compañera de su clase tiene que ser un mixin de una clase concreta implementación de ApplyableN con InternableN. No funcionaría tener una aplicación definida directamente en su objeto compañero.

// 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 buena acerca de esto es que la aplicación original no necesita ser modificada para atender el internado. Solo crea instancias y solo se llama cuando deben crearse.

Todo el asunto puede (y debe) también definirse para las clases con más de un argumento. Para el caso de dos argumentos:

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] 

La interacción muestra que la Internables' aplicar método ejecuta antes de la original de apply() que es ejecutado sólo si es necesario.

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] 

yo decidimos utilizar un WeakHashMap para que el caché de internar no impide la recolección de basura de los casos internados vez que ya no están referenciados en cualquier lugar.

Código claramente disponible as a Github gist.

1

Tal vez un poco hacky, pero usted podría intentar definir su propio método intern(), como Java de String ha:

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 
Cuestiones relacionadas