Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the neatest way to define circular lists with Scala?

Here is my attempt:

case class A(val a: A, val b: Int){
    override def toString() = b.toString
}

lazy val x: A = A(y, 0)
lazy val y: A = A(z, 1)
lazy val z: A = A(x, 2)

The problem comes when trying to do anything with x; causing x to be evaluated starts off a circular evaluation going through x, y, z and ends in a stack overflow. Is there a way of specifying that val a should be computed lazily?

like image 891
Henry Henrinson Avatar asked Feb 05 '14 13:02

Henry Henrinson


3 Answers

You could use Stream like this:

lazy val stream: Stream[Int] = 0 #:: 1 #:: 2 #:: stream

stream.take(10).toList
// List(0, 1, 2, 0, 1, 2, 0, 1, 2, 0)

In general you should use call-by-name parameters:

class A(_a: => A, val b: Int) {
    lazy val a = _a
    override def toString() = s"A($b)"
}

Usage:

scala> :paste
// Entering paste mode (ctrl-D to finish)

lazy val x: A = new A(y, 0)
lazy val y: A = new A(z, 1)
lazy val z: A = new A(x, 2)

// Exiting paste mode, now interpreting.

x: A = <lazy>
y: A = <lazy>
z: A = <lazy>

scala> z.a.a.a.a.a
res0: A = A(1)
like image 104
senia Avatar answered Oct 15 '22 21:10

senia


You need to make A.a itself lazy. You can do it by turning it into a by name parameter that is used to initialize a lazy field:

class A(a0: => A, val b: Int){
  lazy val a = a0
  override def toString() = b.toString
}
object A {
  def apply( a0: => A, b: Int ) = new A( a0, b )
}

You could also do the same using a helper class Lazy:

implicit class Lazy[T]( getValue: => T ) extends Proxy {
  def apply(): T = value
  lazy val value = getValue
  def self = value
}

It has the advantage that you code is pretty much unchanged except for changing a: A into a: Lazy[A]:

case class A(val a: Lazy[A], val b: Int){
 override def toString() = b.toString
}

Note that to access the actual value wrapped in Lazy, you can either use apply or value (as in x.a() or x.a.value)

like image 3
Régis Jean-Gilles Avatar answered Oct 15 '22 22:10

Régis Jean-Gilles


You can define a lazy circular list using the Stream data type:

lazy val circular: Stream[Int] = 1 #:: 2 #:: 3 #:: circular

You can do the same trick on your own with by-name parameters:

class A(head: Int, tail: => A)
lazy val x = new A(0, y)
lazy val y = new A(1, z)
lazy val z = new A(2, x)

Note that this does not work with case classes.

like image 3
Apocalisp Avatar answered Oct 15 '22 23:10

Apocalisp