Возможно ли иметь массив, который лениво оценивает свои элементы? - PullRequest
6 голосов
/ 01 июля 2011

Рассмотрим этот класс BigInt, который должен кэшировать некоторые общие значения в smallValues:

object BigInt {
  lazy val smallValues = Array(Zero, One, Two)
  lazy val Zero = new BigInt(0, Array[Long]())
  lazy val One = new BigInt(1, Array[Long](1))
  lazy val Two = new BigInt(1, Array[Long](2)) 

  private lazy val cacheSize = smallValues.length


  def apply(num: Long): BigInt = {
    // Is the number cached?
    if (0 <= num && num < cacheSize) smallValues(num.toInt)
    // Figure out the sign and make the number positive after that
    else {
      val (sign, value) = if (num < 0) (-1, num * -1) else (1, num)
      new BigInt(sign, Array(value))
    }
  }
}

class BigInt private(val sign: Int, val num: Array[Long]) extends Ordered[BigInt] {
  println("Constructing BigInt")
  ...
}

Проблема здесь в том, что доступ к одному элементу массива вызывает оценку всех элементов:

scala> BigInt.smallValues(0)
Constructing BigInt
Constructing BigInt
Constructing BigInt
res0: BigInt = BigInt@2c176570

Как я могу решить это?

Редактировать: Глядя на предлагаемые решения, я действительно задаюсь вопросом, не будет ли более эффективным просто распределить их без дальнейших осложнений.Что ты думаешь?

Ответы [ 2 ]

4 голосов
/ 01 июля 2011

Редактирование моего ответа, потому что я думал, что это был игрушечный пример того, что вы хотите сделать, и что ваши реальные объекты были настолько дорогими, что лень купила вам что-то. Если вопрос показывает нечто похожее на реальный код, то лень не имеет смысла. Ленивый объект крупнее и дороже в создании, чем строгий. Тем не менее, я сохраняю следующий код, потому что он показывает, как создать ленивую оболочку и что она «работает» (в том смысле, что она функционально правильна), даже если она не «работает» в смысле того, чтобы быть хорошим Идея для вашего варианта использования.

class Lazy[T] (expr : => T) {lazy val ! = expr}
object Lazy{def apply[T](expr : => T) = new Lazy({expr})}

class BigInt (val sign: Int, val num: Array[Long]) {
  println("Constructing BigInt")
}

object BigInt {
  val smallValues = Array(
    Lazy(new BigInt(0, Array[Long]())),
    Lazy(new BigInt(1, Array[Long](1))),
    Lazy(new BigInt(1, Array[Long](2)))
  )

  private val cacheSize = smallValues.length.toLong

   def apply(num: Long): BigInt = {
    // Is the number cached?
    if (0 <= num && num < cacheSize) smallValues(num.toInt)!
    // Figure out the sign and make the number positive after that
    else {
      val (sign, value) = if (num < 0) (-1, num * -1) else (1, num)
      new BigInt(sign, Array(value))
    }
  }
}


scala> BigInt(1)
Constructing BigInt
res0: BigInt = BigInt@c0dd841

scala> BigInt(1)
res1: BigInt = BigInt@c0dd841

scala> BigInt(2)
Constructing BigInt
res2: BigInt = BigInt@4a6a00ca

scala> BigInt(2)
res3: BigInt = BigInt@4a6a00ca
3 голосов
/ 01 июля 2011

Создайте свою собственную ленивую оболочку, возможно, с неявным преобразованием из нее, чтобы вы не заметили, что используете ее:

class Lazy[A](a0: => A) { lazy val value = a0 }
implicit def lazy_to_actual[a](lz: Lazy[A]) = lz.value

Тогда:

lazy val smallValues = Array(
  new Lazy(new BigInt(0, new Array[Long]())),
  new Lazy(new BigInt(1, new Array[Long](1))),
  new Lazy(new BigInt(2, new Array[Long](2)))
)

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

...