Is it possible to have an array that lazily evaluates its elements?

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 is that accessing one element of the array forces all elements to be evaluated:

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

How can i solve this?

Edit: Looking at the proposed solutions, I really wonder if it would be more efficient to simply distribute them without further complication. What do you think?

+6
source share
2 answers

Editing my answer because I thought it was a toy example of what you want to do and that your real objects were so expensive to build this laziness by buying you something. If the question shows something more similar to the real code, then laziness does not make sense. A lazy object is bigger and more expensive than strict ones. However, I save the following code because it shows how to create a lazy shell and that it "works" (in the sense that it is functionally fixed), even if it does not "work" in the sense of 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 
+4
source

Create your own lazy wrapper, possibly with an implicit conversion from it, so you don't notice that you are using it:

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

Then:

 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))) ) 

although if I were you, I would probably use a map.

+3
source

Source: https://habr.com/ru/post/891838/


All Articles