2011-12-31 37 views
10

我正在尋找一種方法來獲得類似案例類的行爲,但這些行爲自動地是hash consed。實現這一整數列表自動哈希Consed案例類

一種方法是:

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 

現在我在尋找什麼因爲是一個通用方式來實現這個(或非常相似的東西)。理想情況下,我不想輸入太多:

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方法劫持

我們必須定義一個特徵(或一個抽象類)以確保您的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) } 
    } 
} 

類的護理對象將不得不實施ApplyableNInternableN的具體類的混入。它不適用於直接定義在您的伴侶對象中。這個

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

也許有點哈克,但你可以嘗試定義自己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