Автоматически хэшированные классы - PullRequest
10 голосов
/ 31 декабря 2011

Я ищу способ иметь классы, которые ведут себя так же, как классы case, но которые автоматически consh 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]

(или аналогичный). Может кто-нибудь придумать какую-нибудь систему типов voodoo, чтобы помочь мне, или мне придется ждать, пока язык макросов станет доступным?

Ответы [ 2 ]

3 голосов
/ 20 сентября 2012

Вы можете определить несколько 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) }
  }
}

Сопутствующий объект вашего класса должен быть смесью конкретного класса, реализующего 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]

Одна хорошая вещь об этом - то, что оригинальное заявление не должно быть изменено, чтобы обслужить интернирование. Он создает только экземпляры и вызывается только тогда, когда их нужно создать.

Все это можно (и нужно) также определять для классов с более чем одним аргументом. Для случая с двумя аргументами:

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 Internables выполняется до исходного 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 .

1 голос
/ 03 января 2012

Может быть, немного хакерский, но вы можете попытаться определить свой собственный метод 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...