summaryrefslogtreecommitdiff
path: root/book/src/main/scalatex/book/indepth
diff options
context:
space:
mode:
Diffstat (limited to 'book/src/main/scalatex/book/indepth')
-rw-r--r--book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex16
-rw-r--r--book/src/main/scalatex/book/indepth/CompilationPipeline.scalatex80
-rw-r--r--book/src/main/scalatex/book/indepth/DesignSpace.scalatex4
-rw-r--r--book/src/main/scalatex/book/indepth/JavaAPIs.scalatex2
-rw-r--r--book/src/main/scalatex/book/indepth/SemanticDifferences.scalatex214
5 files changed, 180 insertions, 136 deletions
diff --git a/book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex b/book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex
index 098da08..0b84cfe 100644
--- a/book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex
+++ b/book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex
@@ -45,7 +45,7 @@
@p
@lnk("Scala.Rx", "https://github.com/lihaoyi/scala.rx") is a change-propagation library that implements the @b{Continuous} style of FRP. To begin with, we need to include it in our @code{build.sbt} dependencies:
- @hl.ref(wd/'examples/'demos/"build.sbt", "com.scalarx")
+ @hl.ref(wd/'examples/'demos/"build.sbt", "scalarx", "")
@p
Scala.Rx provides you with smart variables that automatically track dependencies with each other, such that if one smart variable changes, the rest re-compute immediately and automatically. The main primitives in Scala.Rx are:
@@ -85,7 +85,7 @@
@hl.ref(advanced/"BasicRx.scala", "val txt =")
@less
- @example(div, "BasicRx().main")
+ @example(div, "advanced.BasicRx().main")
@p
This snippet sets up a basic data-flow graph. We have our @hl.scala{txt} @hl.scala{Var}, and a bunch of @hl.scala{Rx}s (@hl.scala{numChars}, @hl.scala{numWords}, @hl.scala{avgWordLength}) that are computed based on @hl.scala{txt}.
@@ -109,7 +109,7 @@
@hl.ref(advanced/"BasicRx.scala", "val fruits =")
@less
- @example(div, "BasicRx().main2")
+ @example(div, "advanced.BasicRx().main2")
@p
This is a basic re-implementation of the autocomplete widget we created in the chapter @sect.ref{Interactive Web Pages}, except done using Scala.Rx. Note that unlike the original implementation, we don't need to manage the clearing of the output area via @hl.scala{innerHTML = ""} and the re-rendering via @hl.scala{appendChild(...)}. All this is handled by the same @hl.scala{rxFrag} code we wrote earlier.
@@ -182,7 +182,7 @@
@def exampleDiv = div(height:="200px")
@sect{Direct Use of XMLHttpRequest}
- @example(exampleDiv, "Futures().main0")
+ @example(exampleDiv, "advanced.Futures().main0")
@hl.ref(advanced/"Futures.scala", "def handle0", "main")
@p
@@ -194,7 +194,7 @@
This solution is basically equivalent to the initial code given in the @sect.ref{Raw Javascript} section of @sect.ref{Interactive Web Pages}, with the additional code necessary for aggregation. As described in @sect.ref{dom.extensions}, we can make use of the @hl.scala{Ajax} object to make it slightly tidier.
@sect{Using dom.extensions.Ajax}
- @example(exampleDiv, "Futures().main1")
+ @example(exampleDiv, "advanced.Futures().main1")
@hl.ref(advanced/"Futures.scala", "def handle1", "main")
@p
@@ -203,7 +203,7 @@
However, we still have the messiness inherent in the result aggregation: we don't actually want to perform our action (writing to the @hl.scala{output} div) when one @hl.scala{Future} is complete, but only when @i{all} the @hl.scala{Future}s are complete. Thus we still need to do some amount of manual book-keeping in the @hl.scala{results} buffer.
@sect{Future Combinators}
- @example(exampleDiv, "Futures().main2")
+ @example(exampleDiv, "advanced.Futures().main2")
@hl.ref(advanced/"Futures.scala", "def handle2", "main")
@p
@@ -250,7 +250,7 @@
@hl.ref(advanced/"Async.scala", "// traditional")
@less
- @example(canvas, "Async().main0")
+ @example(canvas, "advanced.Async().main0")
@p
This is a working implementation, and you can play with it on the right. We basically set the three listeners:
@@ -283,7 +283,7 @@
@hl.ref(advanced/"Async.scala", "// async")
@less
- @example(canvas, "Async().main")
+ @example(canvas, "advanced.Async().main")
@p
We have an @hl.scala{async} block, which contains a while loop. Each round around the loop, we wait for the @hl.scala{mousedown} channel to start the path, waiting for either @hl.scala{mousemove} or @hl.scala{mouseup} (which continues the path or ends it respectively), fill the shape, and then wait for another @hl.scala{mousedown} before clearing the canvas and going again.
diff --git a/book/src/main/scalatex/book/indepth/CompilationPipeline.scalatex b/book/src/main/scalatex/book/indepth/CompilationPipeline.scalatex
index cb55bf9..5d91b62 100644
--- a/book/src/main/scalatex/book/indepth/CompilationPipeline.scalatex
+++ b/book/src/main/scalatex/book/indepth/CompilationPipeline.scalatex
@@ -27,14 +27,14 @@
@li
@b{Initial Compilation}: @code{.scala} files to @code{.class} and @code{.sjsir} files
@li
- @b{Fast Optimization}: @code{.sjsir} files to smallish/fast @code{.js} files
+ @b{Fast Optimization}: @code{.sjsir} files to one smallish/fast @code{.js} file, or
@li
- @b{Full Optimization}: @code{.js} files to smaller/faster @code{.js} files
+ @b{Full Optimization}: @code{.sjsir} files to one smaller/faster @code{.js} file
@p
- @code{.scala} files are the source code you're familiar with. @code{.class} files are the JVM-targetted artifacts which aren't used, but we keep around: tools such as @lnk.misc.IntelliJ or @lnk.misc.Eclipse use these files to provide IDE support for Scala.js code, even if they take no part in compilation. @code{.js} files are the output Javascript, which we can execute in a web browser.
+ @code{.scala} files are the source code you're familiar with. @code{.class} files are the JVM-targetted artifacts which aren't used for actually producing @code{.js} files, but are kept around for pretty much everything else: the compiler uses them for separate compilation and macros, and tools such as @lnk.misc.IntelliJ or @lnk.misc.Eclipse use these files to provide IDE support for Scala.js code. @code{.js} files are the output Javascript, which we can execute in a web browser.
@p
- @code{.sjsir} files are worth calling out: the name stands for "ScalaJS Intermediate Representation", and these files contain compiled code half-way between Scala and Javascript: most Scala features have by this point been replaced their Java/Javascript equivalents, but it still contains Types (which have all been inferred) that can aid in analysis. Many Scala.js specific optimizations take place on this IR.
+ @code{.sjsir} files are worth calling out: the name stands for "ScalaJS Intermediate Representation", and these files contain compiled code half-way between Scala and Javascript: most Scala features have by this point been replaced by their Java/Javascript equivalents, but it still contains Types (which have all been inferred) that can aid in analysis. Many Scala.js specific optimizations take place on this IR.
@p
Each stage has a purpose, and together the stages do bring benefits to offset their cost in complexity. The original compilation pipeline was much more simple:
@@ -83,7 +83,7 @@
@ul
@li
- The original @code{.class} files, as if it were compiled on the JVM. For code that does not use any Javascript interop, these are perfectly valid Java @code{.class} files full of bytecode and can even be executed on the JVM.
+ The original @code{.class} files, @i{almost} as if they were compiled on the JVM, but not quite. They are sufficiently valid that the compiler can execute macros defined in them, but they should not be used to actually run.
@li
The @code{.sjsir} files, destined for further compilation in the Scala.js pipeline.
@@ -94,25 +94,25 @@
This is the only phase in the Scala.js compilation pipeline that separate compilation is possible: you can compile many different sets of Scala.js @code{.scala} files separately, only to combine them later. This is used e.g. for distributing Scala.js libraries as Maven Jars, which are compiled separately by library authors to be combined into a final executable later.
@sect{Fast Optimization}
+ @p
+ Without optimizations, the actual JavaScript code emitted for the above snippet would look like this:
@hl.javascript
- ScalaJS.c.LExample$.prototype.main__V = (function() {
+ ScalaJS.c.Lexample_ScalaJSExample$.prototype.main__V = (function() {
var x = 0;
while ((x < 999)) {
x = ((x + new ScalaJS.c.sci_StringOps().init___T(
- ScalaJS.m.s_Predef().augmentString__T__T("2")
- ).toInt__I()) | 0)
+ ScalaJS.m.s_Predef$().augmentString__T__T("2")).toInt__I()) | 0)
};
- ScalaJS.m.s_Predef().println__O__V(x)
+ ScalaJS.m.s_Predef$().println__O__V(x)
});
-
@p
- This phase is a whole-program optimization of the @code{.sjsir} files, and lives in the @lnk("tools/", "https://github.com/scala-js/scala-js/tree/master/tools") folder of the Scala.js repository. The end result is a rough translation of Scala into the equivalent Javascript (e.g. above):
+ This is a pretty straightforward translation from the intermediate reprensentation into vanilla JavaScript code:
@ul
@li
Scala-style method @hl.scala{def}s become Javascript-style prototype-function-assignment
@li
- Scala @hl.scala{var}s become Javascript @hl.scala{var}s
+ Scala @hl.scala{val}s and @hl.scala{var}s become Javascript @hl.scala{var}s
@li
Scala @hl.scala{while}s become Javascript @hl.scala{while}s
@li
@@ -126,32 +126,57 @@
This is an incomplete description of the translation, but it should give a good sense of how the translation from Scala to Javascript looks like. In general, the output is verbose but straightforward.
@p
- In addition to this superficial translation, the optimizer does a number of things which are more subtle and vary from case to case. The rough operations that get performed are:
+ In addition to this superficial translation, the optimizer does a number of things which are more subtle and vary from case to case. Without diving into too much detail, here are a few optimizations that are performed:
@ul
@li
- @b{Dead-code elimination}: entry-points to the program such as @hl.scala("@JSExport")ed methods/classes are kept, as are any methods/classes that these reference. All others are removed. This reduces the potentially 20mb of Javascript generated by a naive compilation to a more manageable 700kb-1mb for a typical application
+ @b{Dead-code elimination}: entry-points to the program such as @hl.scala("@JSExport")ed methods/classes are kept, as are any methods/classes that these reference. All others are removed. This reduces the potentially 20mb of Javascript generated by a naive compilation to a more manageable 400kb-1mb for a typical application
+ @li
+ @b{Inlining}: under some circumstances, the optimizer inlines the implementation of methods at call sites. For example, it does so for all "small enough" methods. This typically reduces the code size by a small amount, but offers a several-times speedup of the generated code by inlining away much of the overhead from the abstractions (implicit-conversions, higher-order-functions, etc.) in Scala's standard library.
+ @li
+ @b{Constant-folding}: due to inlining and other optimizations, some variables that could have arbitrary are known to contain a constant. These variables are replaced by their respective constants, which, in turn, can trigger more optimizations.
@li
- @b{Inlining}: in cases where a method is only called in a few places, the optimizer inlines the implementation of the method at those callsites. This typically reduces the code size by a small amount, but offers a several-times speedup of the generated code by inlining away much of the overhead from the abstractions (implicit-conversions, higher-order-functions, etc.) in Scala's standard library.
+ @b{Closure elimination}: probably one of the most important optimizations. When inlining a higher-order method such as @code{map}, the optimizer can in turn inline the anonymous function inside the body of the loop, effectively turning polymorphic dispatch with closures into bare-metal loops.
+ @p
+ Applying these optimizations on our examples results in the following JavaScript code instead, which is what you typically execute in fastOpt stage:
+
+ @hl.javascript
+ ScalaJS.c.Lexample_ScalaJSExample$.prototype.main__V = (function() {
+ var x = 0;
+ while ((x < 999)) {
+ var jsx$1 = x;
+ var this$2 = new ScalaJS.c.sci_StringOps().init___T("2");
+ var this$4 = ScalaJS.m.jl_Integer$();
+ var s = this$2.repr$1;
+ x = ((jsx$1 + this$4.parseInt__T__I__I(s, 10)) | 0)
+ };
+ var x$1 = x;
+ var this$6 = ScalaJS.m.s_Console$();
+ var this$7 = this$6.outVar$2;
+ ScalaJS.as.Ljava_io_PrintStream(this$7.tl$1.get__O()).println__O__V(x$1)
+ });
+
@p
As a whole-program optimization, it tightly ties together the code it is compiling and does not let you e.g. inject additional classes later. This does not mean you cannot interact with external code at all: you can, but it has to go through explicitly @hl.scala{@@JSExport}ed methods and classes via Javascript Interop, and not on ad-hoc classes/methods within the module. Thus it's entirely possible to have multiple "whole-programs" running in the same browser; they just will likely have duplicate copies of e.g. standard library classes inside of them, since they cannot share the code as it's not exported.
@p
- While the input for this phase is the aggregate @code{.sjsir} files from your project and all your dependencies, the output is executable Javascript. This phase usually runs in less than a second, outputs a Javascript blob in the 700kb-1mb range, and is suitable for repeated use during development. This corresponds to the @code{fastOptJS} command in SBT.
+ While the input for this phase is the aggregate @code{.sjsir} files from your project and all your dependencies, the output is executable Javascript. This phase usually runs in less than a second, outputs a Javascript blob in the 400kb-1mb range, and is suitable for repeated use during development. This corresponds to the @code{fastOptJS} command in SBT.
@sect{Full Optimization}
@hl.javascript
- be.prototype.main=function(){
- for(var a=0;999>a;)
- a=a+(new de).g(S(L(),"2")).ne()|0;
- ee(); L();
- var b=F(fe); ge();
- a=(new he).g(w(a)); b=bc(0,J(q(b,[a])));
- ie(bc(L(),J(q(F(fe),[je(ke(ge().Vg),b)]))))
- }
+ Fd.prototype.main = function() {
+ for(var a = 0;999 > a;) {
+ var b = (new D).j("2");
+ E();
+ a = a + Ja(0, b.R) | 0
+ }
+ b = Xa(ed().pc.Sb);
+ fd(b, gd(s(), a));
+ fd(b, "\n");
+ };
@p
- The @lnk("Google Closure Compiler", "https://developers.google.com/closure/compiler/") (GCC) is a set of tools that work with Javascript. It has multiple @lnk("levels of optimization", "https://developers.google.com/closure/compiler/docs/compilation_levels"), doing everything from basic whitespace-removal to heavy optimization. It is a old, relatively mature project that is relied on both inside and outside google to optimize the delivery of Javascript to the browser.
+ The @lnk("Google Closure Compiler", "https://developers.google.com/closure/compiler/") (GCC) is a set of tools that work with Javascript. It has multiple @lnk("levels of optimization", "https://developers.google.com/closure/compiler/docs/compilation_levels"), doing everything from basic whitespace-removal to heavy optimization. It is an old, relatively mature project that is relied on both inside and outside Google to optimize the delivery of Javascript to the browser.
@p
Scala.js uses GCC in its most aggressive mode: @lnk("Advanced Optimization", "https://developers.google.com/closure/compiler/docs/api-tutorial3"). GCC spits out a compressed, minified version of the Javascript (above) that @sect.ref{Fast Optimization} spits out: e.g. in the example above, all identifiers have been renamed to short strings, the @hl.javascript{while}-loop has been replaced by a @hl.javascript{for}-loop, and the @hl.scala{println} function has been inlined.
@@ -172,10 +197,9 @@
@p
Notably, GCC @i{does not preserve the semantics of arbitrary Javascript}! In particular, it only works for a subset of Javascript that it understands and can properly analyze. This is an issue when hand-writing Javascript for GCC since it's very easy to step outside that subset and have GCC break your code, but is not a worry when using Scala.js: the Scala.js optimizer (the previous phase in the pipeline) automatically outputs Javascript which GCC understands and can work with.
@p
- GCC duplicates a lot of functionality that the Scala.js optimizer already does, such as DCE and inlining. It is entirely possible to skip the optimization phase, output the naive 20mb Javascript blob, and run GCC on it to bring the size down. However, GCC is much slower than the Scala.js optimizer, taking 60 seconds where the optimizer takes less than 1.
-
+ There is some overlap between the optimizations performed by the Scala.js optimizer and GCC. For example, both apply DCE and inlining in some form. However, there are also a lot of optimizations specific to each tool. In general, the Scala.js optimizer is more concerned about producing very efficient JavaScript code, while GCC shines at making that JavaScript as small as possible (in terms of the number of characters).
@p
- Empirically, running GCC on the output of the optimizer produces the smallest output blobs: ~150-400kb, significantly smaller than the output of running either of them alone, and so that is what we do. This takes 5-10 seconds to run, which makes it somewhat slow for iterative development, so it's typically only run right before final testing and deployment. This corresponds to the @code{fullOptJS} command in SBT.
+ The combination of both these tools produces small and fast output blobs: ~100-400kb. This takes 5-10 seconds to run, which makes it somewhat slow for iterative development, so it's typically only run right before final testing and deployment. This corresponds to the @code{fullOptJS} command in SBT.
@hr
diff --git a/book/src/main/scalatex/book/indepth/DesignSpace.scalatex b/book/src/main/scalatex/book/indepth/DesignSpace.scalatex
index f167ea6..594d8c8 100644
--- a/book/src/main/scalatex/book/indepth/DesignSpace.scalatex
+++ b/book/src/main/scalatex/book/indepth/DesignSpace.scalatex
@@ -151,7 +151,7 @@
(15 / 4) | 0 // 3
@p
- This gives the correct result for most numbers, and is reasonably efficient. However, what about dividing-by-zero?
+ This gives the correct result for most numbers, and is reasonably efficient (actually, it tends to be @i{more} efficient on modern VMs). However, what about dividing-by-zero?
@hl.scala
/*JVM*/
@@ -185,7 +185,7 @@
@p
The decision to not support these exceptional cases comes down to a value judgement: how often do people actually depend on an exception being thrown as part of their program semantics, e.g. by catching it and performing actions? And how often are they just a way of indicating bugs? It turns out that very few @hl.scala{ArithmeticException}s, @hl.scala{ArrayIndexOutOfBoundsException}s, or similar are actually a necessary part of the program! They exist during debugging, but after that, these code paths are never relied upon "in production".
@p
- Thus Scala.js goes for a compromise: in the Fast Optimization mode, we run the code with all these checks in place, so as to catch cases where these errors occur close-to-the-source and make it easy for you to debug them. In Full Optimization mode, on the other hand, we remove these checks, assuming you've already ran through these cases and found any bugs during development.
+ Thus Scala.js goes for a compromise: in the Fast Optimization mode, we run the code with all these checks in place (this is work in progress; currently only @code{asInstanceOf}s are thus checked), so as to catch cases where these errors occur close-to-the-source and make it easy for you to debug them. In Full Optimization mode, on the other hand, we remove these checks, assuming you've already ran through these cases and found any bugs during development.
@p
This is a common pattern in situations where there's a tradeoff between debuggability and speed. In Scala.js' case, it allows us to get good debuggability in development, as well as good performance in production. There's some loss in debuggability in development, sacrificed in exchange for greater performance.
diff --git a/book/src/main/scalatex/book/indepth/JavaAPIs.scalatex b/book/src/main/scalatex/book/indepth/JavaAPIs.scalatex
index 89389dc..1a0afa5 100644
--- a/book/src/main/scalatex/book/indepth/JavaAPIs.scalatex
+++ b/book/src/main/scalatex/book/indepth/JavaAPIs.scalatex
@@ -21,7 +21,7 @@
@li
Write a clean-room implementation in Scala, without looking at the source code of @lnk("OpenJDK", "http://openjdk.java.net/"). This is due to legal-software-license incompatibility between OpenJDK and Scala.js. Reading the docs or specification are fine, as is looking at the source of alternate implementations such as @lnk("Harmony", "http://harmony.apache.org/")
@li
- Submit a pull-request to the @lnk("Scala.js repository", "https://github.com/scala-js/scala-js"), including your implementation, together with tests. See the @lnk("existing tests", "https://github.com/scala-js/scala-js/tree/master/test-suite/src/test/scala/scala/scalajs/testsuite/javalib") in the repository if you need examples of how to write your own.
+ Submit a pull-request to the @lnk("Scala.js repository", "https://github.com/scala-js/scala-js"), including your implementation, together with tests. See the @lnk("existing tests", "https://github.com/scala-js/scala-js/tree/master/test-suite/src/test/scala/org/scalajs/testsuite/javalib") in the repository if you need examples of how to write your own.
@p
In general, this is a simple process, for "pure-Java" classes which do not use any special JVM/Java-specific APIs. However, this will not be possible for classes which do! This means that classes that make use of Java-specific things like:
diff --git a/book/src/main/scalatex/book/indepth/SemanticDifferences.scalatex b/book/src/main/scalatex/book/indepth/SemanticDifferences.scalatex
index 96f3929..c87c60f 100644
--- a/book/src/main/scalatex/book/indepth/SemanticDifferences.scalatex
+++ b/book/src/main/scalatex/book/indepth/SemanticDifferences.scalatex
@@ -3,130 +3,150 @@
Although Scala.js tries very hard to maintain compatibility with Scala-JVM, there are some parts where the two platforms differs. This can be roughly grouped into two things: differences in the libraries available, and differences in the language itself. This chapter will cover both of these facets.
@sect{Language Differences}
- @sect{Floats are Doubles}
- @p
- Since JavaScript doesn't have a native float type, we sometimes represent Floats using doubles/numbers, rather than with lower-precision 32-bit floats.
- @p
- The choice of how to represent floats is up to the implementation. You may not rely on floats providing 64-bit floating point precision.
+
+ @sect{Primitive data types}
@p
- Float literals are truncated to their (binary) precision. However, output does not truncate to that precision. This can lead to the following behavior (this works as expected when using doubles):
+ All primitive data types work exactly as on the JVM, with the three following
+ exceptions.
- @split
- @half
+ @sect{Floats can behave as Doubles by default}
+ @p
+ Scala.js underspecifies the behavior of @code{Float}s by default. Any @code{Float} value can be stored as a @code{Double} instead, and any operation on @code{Float}s can be computed with double precision. The choice of whether or not to behave as such, when and where, is left to the
+ implementation.
+ @p
+ If exact single precision operations are important to your application, you can enable strict-floats semantics in Scala.js, with the following sbt setting:
@hl.scala
- // Scala-JVM
- > println(13.345f)
- 13.345
-
- @half
+ scalaJSSemantics ~= { _.withStrictFloats(true) }
+ @p
+ Note that this can have a major impact on performance of your application on JS interpreters that do not support @lnk("the Math.fround function", "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/fround").
+
+ @sect{toString of Float, Double and Unit}
+ @p
+ @code{x.toString()} returns slightly different results for floating point numbers and @code{()} (@code{Unit}).
+
+ @split
+ @half
+ @hl.scala
+ // Scala-JVM
+ > println(())
+ ()
+ > println(1.0)
+ 1.0
+ > println(1.4f)
+ 1.4
+
+ @half
+ @hl.scala
+ // Scala.js
+ > println(())
+ undefined
+ > println(1.0)
+ 1
+ > println(1.4f)
+ 1.399999976158142
+
+ @p
+ In general, a trailing @code{.0} is omitted. Floats print in a weird way because they are printed as if they were Doubles, which means their lack of precision shows up.
+ @p
+ To get sensible and portable string representation of floating point numbers, use @code{String.format()} or related methods.
+
+ @sect{Runtime type tests are based on values}
+ @p
+ Instance tests (and consequently pattern matching) on any of @code{Byte}, @code{Short}, @code{Int}, @code{Float}, @code{Double} are based on the value and not the type they were created with. The following are examples:
+ @ul
+ @li
+ 1 matches @code{Byte}, @code{Short}, @code{Int}, @code{Float}, @code{Double}
+ @li
+ 128 (@code{> Byte.MaxValue}) matches @code{Short}, @code{Int}, @code{Float}, @code{Double}
+ @li
+ 32768 (@code{> Short.MaxValue}) matches @code{Int}, @code{Float}, @code{Double}
+ @li
+ 2147483647 matches @code{Int}, @code{Double} if strict-floats are enabled, otherwise @code{Float} as well
+ @li
+ 2147483648 (@code{> Int.MaxValue}) matches @code{Float}, @code{Double}
+ @li
+ 1.5 matches @code{Float}, @code{Double}
+ @li
+ 1.4 matches @code{Double} only if strict-floats are enabled, otherwise @code{Float} and @code{Double}
+ @li
+ @code{NaN}, @code{Infinity}, @code{-Infinity} and @code{-0.0} match @code{Float}, @code{Double}
+ @p
+ As a consequence, the following apparent subtyping relationships hold:
@hl.scala
- // Scala.js
- > println(13.345f)
- 13.345000267028809
-
- @sect{Int division by 0 is undefined}
- @p
- Unlike the JVM where dividing an integer type by 0 throws an exception, in Scala.js integer division by 0 is undefined. This allows for efficient implementation of division. Dividing a Double or Float by 0 yields positive or negative infinity as expected.
-
- @split
- @half
- @hl.scala
- // Scala-JVM
- > 10 / 0
- java.lang.ArithmeticException: / by zero
- @half
- @hl.scala
- // Scala.js
- > 10 / 0
- 0
-
- @p
- This is a consequence of the eternal trade-off between performance and correctness, as described in the section @sect.ref{Why does error behavior differ?}
+ Byte <:< Short <:< Int <:< Double
+ <:< Float <:<
+ @p
+ if strict-floats are enabled, or
+ @hl.scala
+ Byte <:< Short <:< Int <:< Float =:= Double
+ @p
+ otherwise.
- @sect{Primitive isInstanceOf tests are based on value}
+ @sect{Undefined behaviors}
@p
- Instance tests (and consequently pattern matching) on any of @hl.scala{Byte}, @hl.scala{Short}, @hl.scala{Int}, @hl.scala{Float}, @hl.scala{Double} are based on the value and not the type they were created with. The following are examples:
-
+ The JVM is a very well specified environment, which even specifies how some bugs are reported as exceptions. Some examples are:
@ul
@li
- @hl.scala{1} matches @hl.scala{Byte}, @hl.scala{Short}, @hl.scala{Int}, @hl.scala{Float}, @hl.scala{Double}
+ @code{NullPointerException}
@li
- @hl.scala{128} (> @hl.scala{Byte.MaxValue}) matches @hl.scala{Short}, @hl.scala{Int}, @hl.scala{Float}, @hl.scala{Double}
+ @code{ArrayIndexOutOfBoundsException} and @code{StringIndexOutOfBoundsException}
@li
- @hl.scala{32768} (> @hl.scala{Short.MaxValue}) matches @hl.scala{Int}, @hl.scala{Float}, @hl.scala{Double}
+ @code{ClassCastException}
@li
- @hl.scala{2147483648} (> @hl.scala{Int.MaxValue}) matches @hl.scala{Float}, @hl.scala{Double}
+ @code{ArithmeticException} (such as integer division by 0)
@li
- @hl.scala{1.2} matches @hl.scala{Float}, @hl.scala{Double}
+ @code{StackOverflowError} and other @code{VirtualMachineError}s
@p
- As a consequence, the following apparent subtyping relationship holds:
-
- @hl.scala
- Byte <:< Short <:< Int <:< Float =:= Double
-
- @sect{toString for integral Floats and Doubles}
+ Because Scala.js does not receive VM support to detect such erroneous conditions, checking them is typically too expensive.
@p
- Calling toString on a Float or a Double that holds an integral value, will not append ".0" to that value:
-
- @split
- @half
- @hl.scala
- // Scala-JVM:
- > println(1.0)
- 1.0
- @half
- @hl.scala
- // Scala.js:
- > println(1.0)
- 1
+ Therefore, all of these are considered @lnk("undefined behavior", "http://en.wikipedia.org/wiki/Undefined_behavior").
@p
- This is due to how numeric values are represented at runtime in Scala.js: @hl.scala{Float}s and @hl.scala{Double}s are raw Javascript @hl.scala{Number}s, and their @hl.scala{toString} behavior follows from that.
-
+ Some of these, however, can be configured to be compliant with sbt settings. Currently, only @code{ClassCastException}s (thrown by invalid @code{asInstanceOf} calls) are configurable, but the list will probably expand in future versions.
@p
- Use a formatting interpolator if you always want to show decimals:
-
+ Every configurable undefined behavior has 3 possible modes:
+ @ul
+ @li
+ @b{Compliant}: behaves as specified on a JVM
+ @li
+ @b{Unchecked}: completely unchecked and undefined
+ @li
+ @b{Fatal}: checked, but throws @lnk("UndefinedBehaviorError", "http://www.scala-js.org/api/scalajs-library/0.6.0/#scala.scalajs.runtime.UndefinedBehaviorError")s instead of the specified exception.
+ @p
+ By default, undefined behaviors are in Fatal mode for fastOptJS and in Unchecked mode for fullOptJS. This is so that bugs can be detected more easily during development, with predictable exceptions and stack traces. In production code (fullOptJS), the checks are removed for maximum efficiency.
+ @p
+ @code{UndefinedBehaviorError}s are @i{fatal} in the sense that they are not matched by @code{case NonFatal(e)} handlers. This makes sure that they always crash your program as early as possible, so that you can detect and fix the bug. It is @i{never} OK to catch an @code{UndefinedBehaviorError} (other than in a testing framework), since that means your program will behave differently in fullOpt stage than in fastOpt.
+ @p
+ If you need a particular kind of exception to be thrown in compliance with the JVM semantics, you can do so with an sbt setting. For example, this setting enables compliant @code{asInstanceOf}s:
@hl.scala
- val x = 1.0
- println(f"$x%.1f")
- // Scala-JVM: 1.0
- // Scala.js: 1.0
-
- @sect{Unit}
+ scalaJSSemantics ~= { _.withAsInstanceOfs(
+ org.scalajs.core.tools.sem.CheckedBehavior.Compliant) }
@p
- @hl.scala{scala.Unit} is represented using JavaScript's undefined. Therefore, calling @hl.scala{toString()} on @hl.scala{Unit} will return @hl.scala{"undefined"} rather than @hl.scala{"()"}. In practice, this shouldn't matter for most use cases.
-
- @sect{Reflection}
+ Note that this will have (potentially major) performance impacts.
@p
- Java reflection and Scala reflection, are not supported. There is limited support for @lnk("java.lang.Class", "https://docs.oracle.com/javase/7/docs/api/java/lang/Class.html"), e.g., @hl.scala{obj.getClass.getName} will work for any Scala.js object but not for objects that come from JavaScript interop (i.e. anything which @hl.scala{extends js.Object}). Reflection makes it difficult to perform the optimizations that Scala.js heavily relies on. For a more detailed discussion on this topic, take a look at the section @sect.ref{Why No Reflection?}.
+ For a more detailed rationale, see the section @sect.ref{Why does error behavior differ?}.
- @sect{Exceptions}
+ @sect{Reflection}
@p
- In general, Scala.js supports exceptions, including catching them based on their type. However, exceptions that are typically triggered by the JVM have flaky semantics, in particular:
-
- @ul
- @li
- @hl.scala{ArrayIndexOutOfBoundsException} is never thrown. Instead, the value becomes @hl.javascript{undefined}, which will probably propagate through your program and blow up somewhat later.
- @li
- @hl.scala{NullPointerException} is reported as JavaScript @hl.scala{TypeError} instead.
- @li
- @hl.scala{StackOverflowError} is unsupported since the underlying JavaScript exception type varies based on the browser. e.g. in Chrome the browser just hangs and kills the process/tab without any chance for the developer to catch the error.
+ Java reflection and, a fortiori, Scala reflection, are not supported. There is limited support for @code{java.lang.Class}, e.g., @code{obj.getClass.getName} will work for any Scala.js object (not for objects that come from JavaScript interop). Reflection makes it difficult to perform the optimizations that Scala.js heavily relies on. For a more detailed discussion on this topic, take a look at the section @sect.ref{Why No Reflection?}.
@sect{Regular expressions}
@p
- JavaScript regular expressions are slightly different from Java regular expressions. The support for regular expressions in Scala.js is implemented on top of JavaScript regexes.
+ @lnk("JavaScript regular expressions", "http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Guide:Regular_Expressions") are slightly different from @lnk("Java regular expressions", "http://docs.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html"). The support for regular expressions in Scala.js is implemented on top of JavaScript regexes.
@p
- This sometimes has an impact on functions in the Scala library that use regular expressions themselves. A list of known functions that are affected is given here:
+ This sometimes has an impact on functions in the Scala library that use regular expressions themselves. A list of known functions that are
+ affected is given here:
@ul
@li
- @hl.scala{StringLike.split(x: Array[Char])}
+ @code{StringLike.split(x: Array[Char])}
@sect{Symbols}
@p
- @hl.scala{scala.Symbol} is supported, but is a potential source of memory leaks in applications that make heavy use of symbols. The main reason is that JavaScript does not support weak references, causing all symbols created by Scala.js tow remain in memory throughout the lifetime of the application.
+ @code{scala.Symbol} is supported, but is a potential source of memory leaks in applications that make heavy use of symbols. The main reason is that
+ JavaScript does not support weak references, causing all symbols created by Scala.js to remain in memory throughout the lifetime of the application.
@sect{Enumerations}
@p
- The methods @hl.scala{Value()} and @hl.scala{Value(i: Int)} on @lnk.scala{scala.Enumeration} use reflection to retrieve a string representation of the member name and are therefore -- in principle -- unsupported. However, since Enumerations are an integral part of the Scala library, Scala.js adds limited support for these two methods:
+ The methods @code{Value()} and @code{Value(i: Int)} on @code{scala.Enumeration} use reflection to retrieve a string representation of the member name and are therefore -- in principle -- unsupported. However, since Enumerations are an integral part of the Scala library, Scala.js adds limited support for these two methods:
@p
Calls to either of these two methods of the forms:
@hl.scala
@@ -136,17 +156,17 @@
are statically rewritten to (a slightly more complicated version of):
@hl.scala
val <ident> = Value("<ident>")
- val <ident> = Value(<num>,"<ident>")
+ val <ident> = Value(<num>, "<ident>")
@p
Note that this also includes calls like
@hl.scala
- val A,B,C,D = Value
+ val A, B, C, D = Value
@p
- since they are desugared into separate val definitions.
+ since they are desugared into separate @code{val} definitions.
@p
- Calls to either of these two methods which could not be rewritten, or calls to constructors of the protected Val class without an explicit name as parameter, will issue a warning.
+ Calls to either of these two methods which could not be rewritten, or calls to constructors of the protected <code>Val</code> class without an explicit name as parameter, will issue a warning.
@p
- Note that the name rewriting honors the @hl.scala{nextName} iterator. Therefore, the full rewrite is:
+ Note that the name rewriting honors the @code{nextName} iterator. Therefore, the full rewrite is:
@hl.scala
val <ident> = Value(
if (nextName != null && nextName.hasNext)
@@ -155,7 +175,7 @@
"<ident>"
)
@p
- We believe that this covers most use cases of scala.Enumeration. Please let us know if another (generalized) rewrite would make your life easier.
+ We believe that this covers most use cases of @code{scala.Enumeration}. Please let us know if another (generalized) rewrite would make your life easier.
@sect{Library Differences}
@val myTable = Seq(