0

Suppose that you want to traverse an object graph in a navigational way, similar to the way we traverse file systems.

For example, imagine you have an object graph that supports this expression:

var x = objName.foo.bar.baz.fieldName

We can encode this data access expression as a path as follows:

"objName/foo/bar/baz/fieldName"

By breaking this path into segments, we can easily traverse an object graph in JavaScript because in addition to the traditional dot notation, it also supports the array access notation: objName["foo"]["bar"]["baz"]["fieldName"].

In Java or JVM Scala, we can use reflection to traverse object graphs, but how would you follow these kinds of paths to traverse object graphs of Scala objects in the Scala.js environment?

In other words, given a path similar in form to URIs, how would you walk through Scala.js objects, and fields?

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
Ben McKenneby
  • 481
  • 4
  • 15
  • Scala-defined objects, or JS-defined ones? I think that you currently wind up with different answers, depending. (Although I think in Scala 3 it might work the same either way.) I'm honestly unsure how to do it for ordinary Scala objects in the Scala.js environment... – Justin du Coeur Mar 03 '19 at 14:13
  • Hi, Justin. This is about ordinary Scala objects in the Scala.js environment, but if it can't work for scala objects in general, can you think of a way to design Scala classes with this capability in mind? – Ben McKenneby Mar 04 '19 at 04:11

1 Answers1

1

You could surely use a macro to convert a constant String representation to an accessor at compile-time, but I guess you want to be able to do this at run-time for arbitrary strings.

If the goal is to be able to pass around partially constructed paths, then this is just a matter of passing accessor functions around. You could use something like this to make it prettier:

class Path[+A](private val value: () => A) {
  def resolve: A = value()
  def /[B](f: A => B): Path[B] = new Path(() => f(resolve))
}

implicit class PathSyntax[A](val a: A) extends AnyVal {
  def /[B](f: A => B): Path[B] = new Path(() => a) / f
}


object Foo {
  val bar = Bar
}

object Bar {
  val baz = Baz
}

object Baz

The syntax is not exactly as pretty, but this is now typesafe and not too bad looking:

val barPath: Path[Bar.type] = Foo / (_.bar)
val bazPath: Path[Baz.type] = barPath / (_.baz)

Against, this would need more work, eg. there is no proper equality/comparison between Paths.

If you want to stick with your approach, I'm not aware of a direct solution. However, I would argue that the whole point of using ScalaJS is to keep strong types and avoid any pattern that could lead to runtime errors that the compiler could have prevented had you let it do its job.

Ben McKenneby
  • 481
  • 4
  • 15
francoisr
  • 4,407
  • 1
  • 28
  • 48