2011-12-31 13 views
10

ケースクラスのように振る舞いますが、自動的にhash 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

あなたはapply()にNは、引数の数であるために、いくつかのInternableN[Arg1, Arg2, ..., ResultType]特性を定義することができます。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) } 
    } 
} 

クラスのコンパニオンオブジェクトは、InternableNApplyableNを実装する具象クラスのミックスインする必要があります。あなたのコンパニオンオブジェクトに直接定義されたアプリケーションを適用することはできません。

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

これについての良い点は、元のアプリケーションをインターンに対応するために変更する必要がないことです。インスタンスを作成するだけで、作成する必要がある場合にのみ呼び出されます。

複数の引数を持つクラスに対して、すべてのことを定義することができます。 2つの引数の場合について:

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

たぶん少しハックが、JavaのStringが持っているようにあなたは、あなた自身のintern()メソッドを定義してみてください:

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