Search code examples
scalascala.jsscalajs-bundlerjsweet

Can JSweet viably port Java libraries for use in cross-built Scala.js projects?


In the search for ways to make Java libraries accessible to both the JavaScript and JVM sides of Scala.js cross-built projects, please consider the following experiment:

Imagine that a Scala.js project needs advanced matrix math capabilities such as Singular Value Decomposition. Although the JavaScript world has Numeric.js and the JVM world has many options, JAMA not least among them, no cross building Scala.js solution exists at the time of this question's formulation.

What options do we have?

  1. Write anew or port a matrix library for Scala.js.
  2. Wrap Numeric.js facades and JAMA into a common Scala.js interface.
  3. Write facades for Numeric.js, then compile it with Nashorn for JVM support.
  4. Transpile JAMA to JavaScript with JSweet and fashion appropriate Scala.js facades.

This question reflects option 4.

After reconditioning JAMA for the JSweet transpiler, publishing the transpiled JavaScript as a CommonJS module through npm, and writing Scala.js facades for the CommonJS module, Scala code can now access Jama on the JVM side, and a port of it on the JS side.

Unfortunately, the core data structure on the JVM side has type: double[][], Array[Array[Double]] in Scala syntax, but JSweet converts it to the JavaScript array type, js.Array[js.Array[Double]] in Scala.js syntax.

Now, from the Scala.js cross built perspective, two identically named, equivalently functional, but utterly distinct and separate libraries exist.

From Scala syntax, we can construct the 3D identity matrix on the JS side as so:

new Matrix(
  js.Array[js.Array[Double]](
    new js.Array[Double](1.0, 0.0, 0.0),
    new js.Array[Double](0.0, 1.0, 0.0),
    new js.Array[Double](0.0, 0.0, 1.0)
   )
 )

On the JVM side, we write:

new Matrix(
  Array[Array[Double]](
    new Array[Double](1.0, 0.0, 0.0),
    new Array[Double](0.0, 1.0, 0.0),
    new Array[Double](0.0, 0.0, 1.0)
  )
)

How could we unify these two implementations?

Is there a trick to equate js.Array and Array?

Would you suggest an entirely different approach to make Java libraries accessible to cross-built Scala.js projects?


Solution

  • Let me start with the easy question:

    Is there a trick to equate js.Array and Array?

    No, there is no such "trick". Those data structures are fundamentally different. js.Array's length is variable, and can be patched with additional properties. Array is Java's fixed-length array. If there was any way to equate them, Scala.js would have done that for you, like it does for Double and number.

    One relatively easy way to unify the APIs would be to rebuild the API of JAMA in Scala.js code, where every class is a wrapper for the facaded JS class coming from the JSweet-compiled library. This allows the Scala-level API of JAMA to be exactly equivalent between Scala/JVM and Scala.js. It does require some amount of code writing to replicate the APIs, but at least the body of the methods need not be rewritten.

    A completely different approach, very radical and requiring an insane amount of man·hours would be fork the JSweet compiler to emit Scala.js IR (.sjsir) files instead of .js files. That way, you could link the JSweet-generated .sjsir files for JAMA together with the Scala.js-generated .sjsir files for your application. This would give the maximum performance, since the Scala.js optimizer would be able to optimize across the app/library border.