10

我正在寻找一种方法来让类的行为就像案例类一样,但会自动散列 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]

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

4

2 回答 2

3

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

我们必须定义一个特征(或抽象类)以确保您的InternableN特征确实有一个 apply 方法要被覆盖,我们称之为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。在伴生对象中直接定义 apply 是行不通的。

// 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 方法在原始方法之前执行,原始方法apply()仅在需要时才执行。

scala> import SomeClass._
import SomeClass._

scala> SomeClass(1)
Internable1: hijacking apply
original apply
res0: SomeClass = SomeClass@2e239525

scala> import AnotherClass._
import AnotherClass._

scala> AnotherClass("earthling", "greetings")
Internable2: hijacking apply
original apply
res1: AnotherClass = AnotherClass@329b5c95

scala> AnotherClass("earthling", "greetings")
Internable2: hijacking apply
res2: AnotherClass = AnotherClass@329b5c95

我选择使用 Wea​​kHashMap 以便在其他地方不再引用它们时,实习缓存不会阻止对实习实例进行垃圾收集。

代码整齐地作为 Github gist提供。

于 2012-09-20T17:54:48.497 回答
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
于 2012-01-03T15:24:58.473 回答