自动哈希一致案例类

时间:2011-12-31 13:57:00

标签: scala case-class

我正在寻找一种方法,让类的行为与case类一样,但这些类会自动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]

(或类似)。有人可以提出一些类型系统伏都教来帮助我,还是我必须等待宏语言可用?

2 个答案:

答案 0 :(得分:3)

您可以定义一些InternableN[Arg1, Arg2, ..., ResultType]特征,其中N是apply()Internable1[A,Z]Internable2[A,B,Z]等参数的数量。这些特征定义了缓存本身, intern()方法和我们想要劫持apply方法。

我们必须定义一个特征(或一个抽象类)以确保你的InternableN特征确实存在一个覆盖的应用方法,让我们称之为{{1} }。

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

互动表明了Internables' apply方法在原始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] 之前执行,仅在需要时执行。

apply()

我选择使用WeakHashMap,这样一旦实习缓存不再在其他地方被引用,实习缓存不会阻止对其进行垃圾收集。

代码整齐可用as a Github gist

答案 1 :(得分: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