Chapter 2. Cross-Platform Language Semantics
Language Specification [
48
] to the letter. On the other hand, we can observe how Scala/JVM
behaves and define the semantics of Scala as the observed behavior. In theory, the former is
the obvious good choice, because it is a principled approach, and because the specification
leaves the semantics of some constructs as implementation-defined, potentially increasing
our freedom as the designer of a dialect for another platform. Unfortunately, in practice
we must account for an external agent: the ecosystem of existing libraries. As we saw in
the previous chapter, when designing a cross-platform language, it is not sufficient for the
language itself to be portable; so must the libraries. The trouble is that, being exposed only
to Scala/JVM, library developers have (perhaps unknowingly) relied on some behavior of
Scala/JVM that is technically implementation-defined according to the specification. These
behaviors are de facto semantics, and must be taken into account when considering portability
of the language. As our implementation of Scala.js reached more libraries, we progressively
realized that virtually every corner of the specification that was left to the implementation
had been turned into de facto semantics in more or less critical ways. Therefore, we had no
choice but to consider de facto semantics as an integral part of the Scala specification, and we
sometimes call the combination of both as the Scala/JVM specification.
In this section, we discuss some parts of the Scala/JVM specification, including de facto
semantics, that have a non-trivial impact on Scala.js, notably on interoperability. Beyond
the case of Scala.js, illustrating these aspects can also serve as a check-list of things to look
out for when designing a cross-platform language from scratch. Throughout this section,
we justify decisions based on arguments such as “the ecosystem did/did not rely on some
specific behavior”. Those observations came from experience, trying to implement alternative
behaviors and watching the ecosystem survive them, or breaking down. We did not actually
record empirical data as we progressed, therefore those arguments unfortunately remain
anectodal.
The “ecosystem” itself is difficult to define. For Scala.js, it even progressed over time, as the
development of the language matured. At first, approximately until Scala.js 0.4.x, i.e., one year
of development, it only consisted of early adopters: users who were willing to play with an
immature technology and report immediate limitations. The feedback from early users was
crucial to make the language usable at all. Once Scala.js addressed immediate needs, the pool
of early adopters grew into a small community of enthusiasts. That community developed
initial libraries, or tried to port existing libraries, exhibiting new requirements. So far, only
users specifically interested in the JavaScript platform tried to use Scala.js. The last critical
step was to make the language portable enough for large existing libraries to cross-compile
as-is, and to provide the necessary tooling to make it as easy as possible to do so, which
was the main focus of the transition from 0.5.x to 0.6.x, approximately two years into the
development. At that point, the ecosystem suddenly exploded, as more and more established
libraries from the Scala/JVM ecosystem started cross-compiling for Scala.js. In the process,
the test suites of these libraries started exercising all the possible obscure de facto semantics
of Scala/JVM. Unlike the two first phases of the development, where requirements and bugs
were only elicited by isolated experiments done by users, this third phase creates many more
6