Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to have an Array which evaluates its elements lazily?

Consider this BigInt class, which should cache some common values in 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")
  ...
}

The problem here is that accessing one element of the array forces the evaluation of all elements:

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

How could I solve that?

Edit: Looking at the proposed solutions I really wonder if it wouldn't be more efficient to just allocate them without further complication. What do you think?

like image 256
soc Avatar asked Jul 01 '11 17:07

soc


1 Answers

Editing my answer because I thought this was a toy example of what you want to do and that your real objects were so expensive to build that laziness bought you something. If the question shows something more like real code then laziness makes no sense. The lazy object are bigger and more expensive to create than the strict ones are. Still, I'm keeping the following code because it does show how create a lazy wrapper and that it does "work" (in the sense that it's functionally correct) even if it doesn't "work" in the sense of being a good idea for your use case.

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
like image 183
James Iry Avatar answered Sep 27 '22 21:09

James Iry