Allowing certain types of forward references

The following code does not compile, although its intention is clear.

case class Vertex(neighbors: Set[Vertex])

def simpleCircularGraph(): Vertex = {
  val a: Vertex = Vertex(Set(b))
  val b: Vertex = Vertex(Set(a))
  a
}

It can be translated into some code that does compile, at the cost of being less clear and more verbose:

class Vertex(neighborsArgument: Set[Vertex]) {
  private[this] var neighborsValue = neighborsArgument
  def neighbors: Set[Vertex] = neighborsArgument
  def setNeighbors$(neighborsArgument: Set[Vertex]): Unit = {
    neighborsValue = neighborsArgument
  }
  // <additional boilerplate to emulate a case class>
}
// <additional boilerplate to emulate a case class>

def simpleCircularGraph(): Vertex = {
  val a: Vertex = Vertex(Set())
  val b: Vertex = Vertex(Set(a))
  a.setNeighbors$(Set(b))
  a
}

Could support for such forward references somehow be added to Scala?

I would say that what you’re trying to achieve here requires something more than just forward references. You can already replace your vals with lazy vals to make this compile but calling simpleCircularGraph() would then crash at runtime.

The problem with the workaround suggested by you is that if Vertex exposes a public method mutating its state it basically looses the semantic properties of a case class

BTW I’ve just realized that in your particular use case you might not want Vertex to be a case class. E.g. if two vertices in a graph have the same neighbors this doesn’t mean they’re the same vertex (but the synthetic implementation of equals in case classes relies on the values of class parameters).

This works and doesn’t seem that bad syntactically

class Vertex(neighborsArgument: => Set[Vertex]):
  lazy val neighbors = neighborsArgument

def simpleCircularGraph(): Vertex = {
  lazy val a: Vertex = Vertex(Set(b))
  lazy val b = Vertex(Set(a))
  a
}

@main def run() = 
  val a = simpleCircularGraph()
  assert(a.neighbors.head.neighbors.head == a)

@prolativ Thank you! Yes, your code looks better. It’s true that for a graph, we wouldn’t want case classes, so I didn’t pick my example so well (states might have been better). Can your code be modified to work with case classes as well?

In my “workaround”, setNeighbors$ was meant to be generated by the compiler, or something, and not accessible in the code.

I feel that, ideally, the code I wrote would translate into the allocations being done for both objects first, and then their initialization. But that’s probably not possible with the JVM.

Anyways, it’s probably not worth adding support for this, as normal classes (potentially with some boilerplate and performance overhead) and lazy vals can be used, as you showed; plus, it’s not a common problem.

It depends on what you want to achieve. Could you give some proper use case for that?

In general by-name parameters cannot be used as “field parameters” (not sure if this is the right term). So you can have

class Foo(i: => Int)

but not

class Foo(val i: => Int)

If Foo was a case class val would be added implicitly so

case class Foo(i: => Int)

won’t work either.
However all the magic of case classes works only for the first parameters list of a constructor so theoretically you can have e.g.

case class Foo(s: String)(i: => Int)

but then people might get confused that

Foo("a")(1) == Foo("a")(2)

returns true because only the first parameter is taken into account when equals method is synthesized

1 Like

Why not something with a simpler immutable non-lazy design which allows using the normal Scala case class infrastructure?

case class Vertex(tag: String)

case class Edge(vertexA: Vertex, vertexB: Vertex)

case class Graph(vertexes: Set[Vertex], edges: Set[Edge]) {
  def neighbors(vertex: Vertex): Set[Vertex] =
    edges.map(
      edge =>
          if ((edge.vertexA == vertex) && (edge.vertexB != vertex)) Some(edge.vertexB)
          else
            if ((edge.vertexA != vertex) && (edge.vertexB == vertex)) Some(edge.vertexA)
            else None
    ).flatten
}

View and play within Scastie!

And this design is trivially enhanced to memoize the calls to the neighbors method.

2 Likes