10

ケースクラスと同じように動作するクラスを持つ方法を探していますが、それは自動的にconsed をハッシュします

整数リストでこれを実現する 1 つの方法は次のとおりです。

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したいメソッドを定義します。

オーバーライドする 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) }
  }
}

ApplyableNクラスのコンパニオン オブジェクトは、 を実装する具象クラスの mixin である必要があります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]

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

複数の引数を持つクラスに対しても、全体を定義することができます (また、定義する必要があります)。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 メソッドが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

WeakHashMap を使用することにしたのは、インターン キャッシュが、インターンされたインスタンスが他の場所で参照されなくなった後のガベージ コレクションを妨げないようにするためです。

コードは Github gist としてきちんと利用できます。

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