2011-12-31 2 views
10

Я ищу способ иметь классы, которые ведут себя так же, как классы case, но это автоматически hash consed.Automatically Hash Consed Case Classes

Одним из способов достижения этой цели для целых списков будет:

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

И, например, в то время как

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

мы получаем

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

Теперь то, что я ищу for is generic способ достичь этого (или что-то очень похожее). В идеале, я не хочу печатать гораздо больше:

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

(или аналогичный). Может кто-нибудь придумать какую-то систему вуду, чтобы помочь мне, или мне придется ждать, пока макроязык будет доступен?

ответ

3

Вы можете определить несколько InternableN[Arg1, Arg2, ..., ResultType] признаков для N-число аргументов apply(): Internable1[A,Z], Internable2[A,B,Z] и т. Д. Эти черты определяют сам кеш, метод intern() и метод apply, который мы хотим получить hijack.

Нам нужно определить признак (или абстрактный класс), чтобы гарантировать ваши черты InternableN, что действительно существует метод приложения, который можно переопределить, назовем его 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) } 
    } 
} 

компаньон объект вашего класса должны быть Mixin конкретного класса, реализующего ApplyableN с InternableN. Было бы нецелесообразно применять прямое определение в вашем сопутствующем объекте.

// 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] 

Одна хорошая вещь об этом заключается в том, что первоначальное применение не нужно модифицировать, чтобы обслуживать интернирование. Он создает только экземпляры и вызывается только тогда, когда их нужно создать.

Все это может (и должно) также быть определено для классов с более чем одним аргументом. Для случая с двумя аргументами:

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] 

взаимодействие показывает, что Internables' применяется метод выполняется до исходного apply(), который запускается на выполнение только в случае необходимости.

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] 

Я решил использовать WeakHashMap так, что интернирование кэш не не препятствует сбору мусора в интернированных случаях, как только они больше не ссылаются в другом месте.

Код в порядке as a Github gist.

1

Может быть, немного Hacky, но вы можете попробовать определения свой собственный intern() метод, как и в Java 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