Sébastien Doeraene @sjrdoeraene
June 10, 2015 -- Scala Days Amsterdam
LAMP, lamp.epfl.ch
École polytechnique fédérale de Lausanne
Hundreds of languages compile to JavaScript
Languages that don't compile to JS are weird
Compiling to JavaScript: Problem solved!
Rely on Semantics
["10", "10", "10", "10"].map(parseInt)
10,NaN,2,3
[parseInt("10", 0), parseInt("10", 1),
parseInt("10", 2), parseInt("10", 3)]
["10", "10", "10", "10"].map(s => parseInt(s)) // ES 6 FTW
10,10,10,10
new Array[Any](3).toSet()
false
new Array[Any](3).toSet
Set(null)
new Array[Any](3).toSet.apply(()) // contains () ?
new js.Array[Any](3).toSet()
true
new js.Array[Any](3).toSet
Set(undefined)
() == js.undefined
true
Scala.js | JavaScript |
---|---|
a + b | (a + b) | 0 |
a - b | (a - b) | 0 |
a * b | Math.imul(a, b) |
a / b | (a / b) | 0 |
a % b | (a % b) | 0 |
(a+b)|0
is faster than a+b
(at least on desktop)Scala.js gives you true, efficient Ints on the JS platform
Data type | JVM semantics | Fast |
---|---|---|
Int | check | check |
Byte, Short, Char | check | check |
Long | check | close |
Float | close | check |
Float (strict) | check | close |
Double | check | check |
Requires two changes in the semantics, wrt Scala/JVM
Type | Scala/JVM | Scala.js |
---|---|---|
Integers, Char, Boolean | Same on both platforms | |
1.0.toString | 1.0 | 1 |
().toString | () | undefined |
Based on the value rather than the type
1.0, -3 | Byte, Short, Int, Double |
128, -200 | Short, Int, Double |
2147483647 | Int, Double |
2147483648 | Double |
1.5, -0.0, NaN, ±∞ | Double |
val s = "hello"
s.asInstanceOf[List[String]]
ClassCastException
java.lang.ClassCastException: java.lang.String
cannot be cast to scala.collection.immutable.List
UndefinedBehaviorError
case NonFatal(e)
ClassCastException
, as on the JVMNullPointerException
ArrayIndexOutOfBoundsException
* and StringIndexOutOfBoundsException
*ArrayStoreException
*ArithmeticException
* (such as integer division by 0)StackOverflowError
and other VirtualMachineError
sCurrently all unchecked
* Will eventually have Fatal/Unchecked/Compliant settings
def main(pre: html.Pre): Unit = {
val xhr = new dom.XMLHttpRequest()
xhr.open("GET",
"http://api.openweathermap.org/" +
"data/2.5/weather?q=Singapore")
xhr.onload = { (e: dom.Event) =>
if (xhr.status == 200)
pre.textContent = xhr.responseText
}
xhr.send()
}
trait js.Any extends scala.AnyRef
class XMLHttpRequest extends /*...*/ js.Any {
def status: Int = js.native
def responseText: String = js.native
var onload: js.Function1[Event, _] = js.native
def open(method: String, url: String,
async: Boolean = js.native, ...): Unit = js.native
def send(data: js.Any = js.native): Unit = js.native
}
Scala.js | JavaScript semantics |
---|---|
new Foo(a, b) | new global.Foo(a, b) |
xhr.open(a, b, c, d) | xhr.open(a, b, c, d) |
xhr.open(a, b) | xhr.open(a, b) |
xhr.status | xhr.status |
pre.textContent = v | pre.textContent = v |
global
is the JavaScript global object
def main(pre: html.Pre): Unit = {
val xhr = new dom.XMLHttpRequest()
xhr.open("GET",
"http://api.openweathermap.org/" +
"data/2.5/weather?q=Singapore")
xhr.onload = { (e: dom.Event) =>
if (xhr.status == 200)
pre.textContent = xhr.responseText
}
xhr.send()
}
Scala.js values | JavaScript type |
---|---|
primitive numbers except Longs | number |
true and false | boolean |
non-null strings | string |
() | undefined |
null | null |
Instances of other Scala classes (non-raw JS types) are opaque to JavaScript
trait js.Function1[-T1, +R] extends /*...*/ js.Any {
def apply(arg1: T1): R = js.native
}
implicit def fromFunction1[T1, R](
f: T1 => R): js.Function1[T1, R] = <primitive>
Returns a JavaScript function g
such that:
JavaScript | Scala.js semantics |
---|---|
g(a) | f.apply(a) |
js.Dynamic
def main(pre: js.Dynamic): Unit = {
val xhr = js.Dynamic.newInstance(
js.Dynamic.global.XMLHttpRequest)()
xhr.open("GET", "http://api.openweathermap.org/...")
xhr.onload = { (e: js.Dynamic) =>
if (xhr.status == 200)
pre.textContent = xhr.responseText
}
xhr.send()
}
package pck
@JSExport class Point(x: Double, y: Double) {
@JSExport def abs(): Double = Math.hypot(x, y)
}
@JSExport object MyApp {
@JSExport def main(): Unit = println("hello")
}
JavaScript | Scala.js semantics |
---|---|
new global.pck.Point(x, y) | new Point(x, y) |
p.abs() | p.abs() |
global.pck.MyApp().main() | MyApp.main() |
package java.lang
class Object {
...
@JSExport def toString(): String = ???
}
JavaScript | Scala.js semantics |
---|---|
("" + foo) / foo.toString() | foo.toString() |
@JSExport
'ed entities can be accessed from JavaScriptConclusion: we live in a closed world
The closed world assumption is a heaven for optimizations
val (white, black) = allSquares.foldLeft((0, 0)) {
case ((white, black), square) =>
square.owner match {
case White => (white+1, black)
case Black => (white, black+1)
case NoPlayer => (white, black)
}
}
var xs = this.allSquares$1;
var start = 0;
var end = xs.u["length"];
var z_1 = 0;
var z_2 = 0;
while (true) {
if ((start === end)) {
var x1_1 = z_1;
var x1_2 = z_2;
break
} else {
var temp$start = ((1 + start) | 0);
var x1$1 = xs.u[start];
var white = z_1;
var black = z_2;
var x1$2 = x1$1.$$undowner$1;
if (($m_Lreversi_White$() === x1$2)) {
var temp$z_1 = ((1 + white) | 0);
var temp$z_2 = black
} else if (($m_Lreversi_Black$() === x1$2)) {
var temp$z_1 = white;
var temp$z_2 = ((1 + black) | 0)
} else if (($m_Lreversi_NoPlayer$() === x1$2)) {
var temp$z_1 = white;
var temp$z_2 = black
} else {
throw new $c_s_MatchError().init___O(x1$2)
};
start = temp$start;
z_1 = temp$z_1;
z_2 = temp$z_2;
continue
}
};
var white$1 = x1_1;
var black$1 = x1_2;
println("Hello world!")
3 times {
println("Hi!")
}
implicit class IntTimes(val self: Int) extends AnyVal {
def times(body: => Unit): Unit = {
for (i <- 0 until self)
body
}
}
this.println__O__V("Hello world!");
var i = 0;
var count = 0;
while ((i !== 3)) {
var v1 = i;
$m_Lhelloworld_HelloWorld$().println__O__V("Hi!");
count = ((1 + count) | 0);
i = ((1 + i) | 0)
}