Functional	OO	ImperativeFunctional	OO	Imperative
ScalaScala
関数型オブジェクト指向命関数型オブジェクト指向命
令型	Scala令型	Scala
Sébastien	Doeraene	--	セバスチャン·ドゥラン
June	28,	2019	--	Scala	Matsuri	--	2019年6⽉28⽇
@sjrdoeraene
Scala	Center,	
École	polytechnique	fédérale	de	Lausanne
 
	           	
scala.epfl.ch
1
BasicsBasics
Japanese	line	1
Japanese	line	2
2
def times2(xs: List[Int]): List[Int] = {
var result: List[Int] = Nil
var i = 0
while (i < xs.length) {
result = result :+ (xs(i) * 2)
i += 1
}
result
}
3
def times2(xs: List[Int]): List[Int] = {
var result: List[Int] = Nil
for (i <- 0 until xs.length) {
result = result :+ (xs(i) * 2)
}
result
}
4
def times2(xs: List[Int]): List[Int] = {
val builder = List.newBuilder[Int]
for (i <- 0 until xs.length) {
builder += xs(i) * 2
}
builder.result()
}
5
def times2(xs: List[Int]): List[Int] = {
val builder = List.newBuilder[Int]
for (x <- xs) {
builder += x * 2
}
builder.result()
}
6
def times2(xs: List[Int]): List[Int] = {
for (x <- xs)
yield x * 2
}
def times2(xs: List[Int]): List[Int] = {
xs.map(x => x * 2)
}
7
Sometimes,	builders	are	better
Japanese	line	1
Japanese	line	2
8
def fromPathClasspath(classpath: Seq[Path]): (Seq[IRContainer], Seq[Path]) = {
val containers = Seq.newBuilder[IRContainer]
val paths = Seq.newBuilder[Path]
for (entry <- classpath if Files.exists(entry)) {
val attrs = Files.readAttributes(entry, classOf[BasicFileAttributes])
if (attrs.isDirectory()) {
walkIR(entry) { (path, attrs) =>
containers += IRContainer.fromIRFile(...)
paths += path
}
} else if (entry.getFileName().toString().endsWith(".jar")) {
containers += new JarIRContainer(entry, attrs.lastModifiedTime())
paths += entry
} else {
throw new IllegalArgumentException("Illegal classpath entry " + entry)
}
}
(containers.result(), paths.result())
}
9
def fromPathClasspath(classpath: Seq[Path]): (Seq[IRContainer], Seq[Path]) = {
val containers = Seq.newBuilder[IRContainer]
val paths = Seq.newBuilder[Path]
for (entry <- classpath if Files.exists(entry)) {
val attrs = Files.readAttributes(entry, classOf[BasicFileAttributes])
if (attrs.isDirectory()) {
walkIR(entry) { (path, attrs) =>
containers += IRContainer.fromIRFile(...)
paths += path
}
} else if (entry.getFileName().toString().endsWith(".jar")) {
containers += new JarIRContainer(entry, attrs.lastModifiedTime())
paths += entry
} else {
throw new IllegalArgumentException("Illegal classpath entry " + entry)
}
}
(containers.result(), paths.result())
}
9
Sometimes,	 s	are	better
Japanese	line	1
Japanese	line	2
10
var test = {
genIsScalaJSObject(obj) &&
genIsClassNameInAncestors(...)
}
def typeOfTest(typeString: String): js.Tree = ...
if (isAncestorOfString)
test = test || typeOfTest("string")
if (isAncestorOfHijackedNumberClass) {
test = test || typeOfTest("number")
if (useBigIntForLongs)
test = test || genCallHelper("isLong", obj)
}
if (isAncestorOfBoxedBooleanClass)
test = test || typeOfTest("boolean")
if (isAncestorOfBoxedCharacterClass)
test = test || (obj instanceof envField("Char"))
test
11
var test = {
genIsScalaJSObject(obj) &&
genIsClassNameInAncestors(...)
}
def typeOfTest(typeString: String): js.Tree = ...
if (isAncestorOfString)
test = test || typeOfTest("string")
if (isAncestorOfHijackedNumberClass) {
test = test || typeOfTest("number")
if (useBigIntForLongs)
test = test || genCallHelper("isLong", obj)
}
if (isAncestorOfBoxedBooleanClass)
test = test || typeOfTest("boolean")
if (isAncestorOfBoxedCharacterClass)
test = test || (obj instanceof envField("Char"))
test
11
val test0 = {
genIsScalaJSObject(obj) &&
genIsClassNameInAncestors(...)
}
def typeOfTest(typeString: String): js.Tree = ...
val test1 =
if (isAncestorOfString) test0 || typeOfTest("string")
else test0
val test3 = if (isAncestorOfHijackedNumberClass) {
val test2 = test1 || typeOfTest("number")
if (useBigIntForLongs) test2 || genCallHelper("isLong", obj)
else test2
}
val test4 =
if (isAncestorOfBoxedBooleanClass) test3 || typeOfTest("boolean")
else test3
val test5 =
if (isAncestorOfBoxedCharacterClass) test4 || (obj instanceof envField("Char"))
else test4
test5
12
val test0 = {
genIsScalaJSObject(obj) &&
genIsClassNameInAncestors(...)
}
def typeOfTest(typeString: String): js.Tree = ...
val test1 =
if (isAncestorOfString) test0 || typeOfTest("string")
else test0
val test3 = if (isAncestorOfHijackedNumberClass) {
val test2 = test1 || typeOfTest("number")
if (useBigIntForLongs) test2 || genCallHelper("isLong", obj)
else test2
}
val test4 =
if (isAncestorOfBoxedBooleanClass) test3 || typeOfTest("boolean")
else test3
val test5 =
if (isAncestorOfBoxedCharacterClass) test4 || (obj instanceof envField("Char"))
else test4
test5
12
val test0 = {
genIsScalaJSObject(obj) &&
genIsClassNameInAncestors(...)
}
def typeOfTest(typeString: String): js.Tree = ...
val test1 =
if (isAncestorOfString) test0 || typeOfTest("string")
else test0
val test3 = if (isAncestorOfHijackedNumberClass) {
val test2 = test1 || typeOfTest("number")
if (useBigIntForLongs) test2 || genCallHelper("isLong", obj)
else test2
}
val test4 =
if (isAncestorOfBoxedBooleanClass) test3 || typeOfTest("boolean")
else test3
val test5 =
if (isAncestorOfBoxedCharacterClass) test4 || (obj instanceof envField("Char"))
else test4
test5
12
val test0 = {
genIsScalaJSObject(obj) &&
genIsClassNameInAncestors(...)
}
def typeOfTest(typeString: String): js.Tree = ...
val test1 =
if (isAncestorOfString) test0 || typeOfTest("string")
else test0
val test3 = if (isAncestorOfHijackedNumberClass) {
val test2 = test1 || typeOfTest("number")
if (useBigIntForLongs) test2 || genCallHelper("isLong", obj)
else test2
}
val test4 =
if (isAncestorOfBoxedBooleanClass) test3 || typeOfTest("boolean")
else test3
val test5 =
if (isAncestorOfBoxedCharacterClass) test4 || (obj instanceof envField("Char"))
else test4
test5
12
In	the	(super)	smallIn	the	(super)	small
Immutable/functional	API
Locally	imperative	implementation	(if	more	readable)
Japanese	line	1
Japanese	line	2
13
Algorithms	with	mutable	internal	dataAlgorithms	with	mutable	internal	data
structuresstructures
The	 	of	Scala.js
The	change	detection	algorithm	of	the	optimizer
Japanese	line	1
Japanese	line	2
14
At	the	instance	levelAt	the	instance	level
Japanese	line	1
Japanese	line	2
15
final class Emitter(config: CommonPhaseConfig) {
def emitAll(unit: LinkingUnit, builder: JSLineBuilder,
logger: Logger): Unit = {
...
}
}
16
final class Emitter(config: CommonPhaseConfig) {
private class State(val lastMentionedDangerousGlobalRefs: Set[String]) {
val jsGen: JSGen = new JSGen(..., lastMentionedDangerousGlobalRefs)
val classEmitter: ClassEmitter = new ClassEmitter(jsGen)
val coreJSLib: WithGlobals[js.Tree] = CoreJSLib.build(jsGen)
}
private var state: State = new State(Set.empty)
private def jsGen: JSGen = state.jsGen
private def classEmitter: ClassEmitter = state.classEmitter
private def coreJSLib: WithGlobals[js.Tree] = state.coreJSLib
private val classCaches = mutable.Map.empty[List[String], ClassCache]
def emitAll(unit: LinkingUnit, builder: JSLineBuilder,
logger: Logger): Unit = {
...
classCaches.getOrElseUpdate(..., ...)
...
val mentionedDangerousGlobalRefs = ...
state = new State(mentionedDangerousGlobalRefs)
...
}
}
17
final class Emitter(config: CommonPhaseConfig) {
private class State(val lastMentionedDangerousGlobalRefs: Set[String]) {
val jsGen: JSGen = new JSGen(..., lastMentionedDangerousGlobalRefs)
val classEmitter: ClassEmitter = new ClassEmitter(jsGen)
val coreJSLib: WithGlobals[js.Tree] = CoreJSLib.build(jsGen)
}
private var state: State = new State(Set.empty)
private def jsGen: JSGen = state.jsGen
private def classEmitter: ClassEmitter = state.classEmitter
private def coreJSLib: WithGlobals[js.Tree] = state.coreJSLib
private val classCaches = mutable.Map.empty[List[String], ClassCache]
def emitAll(unit: LinkingUnit, builder: JSLineBuilder,
logger: Logger): Unit = {
...
classCaches.getOrElseUpdate(..., ...)
...
val mentionedDangerousGlobalRefs = ...
state = new State(mentionedDangerousGlobalRefs)
...
}
}
17
final class Emitter(config: CommonPhaseConfig) {
private class State(val lastMentionedDangerousGlobalRefs: Set[String]) {
val jsGen: JSGen = new JSGen(..., lastMentionedDangerousGlobalRefs)
val classEmitter: ClassEmitter = new ClassEmitter(jsGen)
val coreJSLib: WithGlobals[js.Tree] = CoreJSLib.build(jsGen)
}
private var state: State = new State(Set.empty)
private def jsGen: JSGen = state.jsGen
private def classEmitter: ClassEmitter = state.classEmitter
private def coreJSLib: WithGlobals[js.Tree] = state.coreJSLib
private val classCaches = mutable.Map.empty[List[String], ClassCache]
def emitAll(unit: LinkingUnit, builder: JSLineBuilder,
logger: Logger): Unit = {
...
classCaches.getOrElseUpdate(..., ...)
...
val mentionedDangerousGlobalRefs = ...
state = new State(mentionedDangerousGlobalRefs)
...
}
}
17
final class Emitter(config: CommonPhaseConfig) {
private class State(val lastMentionedDangerousGlobalRefs: Set[String]) {
val jsGen: JSGen = new JSGen(..., lastMentionedDangerousGlobalRefs)
val classEmitter: ClassEmitter = new ClassEmitter(jsGen)
val coreJSLib: WithGlobals[js.Tree] = CoreJSLib.build(jsGen)
}
private var state: State = new State(Set.empty)
private def jsGen: JSGen = state.jsGen
private def classEmitter: ClassEmitter = state.classEmitter
private def coreJSLib: WithGlobals[js.Tree] = state.coreJSLib
private val classCaches = mutable.Map.empty[List[String], ClassCache]
def emitAll(unit: LinkingUnit, builder: JSLineBuilder,
logger: Logger): Unit = {
...
classCaches.getOrElseUpdate(..., ...)
...
val mentionedDangerousGlobalRefs = ...
state = new State(mentionedDangerousGlobalRefs)
...
}
}
17
At	the	instance	levelAt	the	instance	level
Immutable/functional	object	API
Mutable	state	carried	between	invocations
But	not	observable	from	the	outside
The	state	is	encapsulated	using	object-orientation
Japanese	line	1
Japanese	line	2
18
At	the	instance	levelAt	the	instance	level
Not	possible	using	pure	functional	programming
Difficult	to	reason	about	if	the	state	is	observable	(using
an	imperative	API)
Unique	power	of	combining	functional	programming,
object-orientation	and	imperative	features
Japanese	line	1
Japanese	line	2
19
Used	at	several	levels:
The	emitter
The	optimizer
The	caches	for	 	files
The	all-encompassing	 	method
etc.
Japanese	line	1
Japanese	line	2
20
Open	class	hierarchiesOpen	class	hierarchies
Japanese	line	1
Japanese	line	2
21
So	far:	 	traits	and	 	classes
Japanese	line	1
Japanese	line	2
22
/** A backend of a standard Scala.js linker. */
abstract class LinkerBackend {
/** Core specification that this linker backend implements. */
val coreSpec: CoreSpec
/** Symbols this backend needs to be present in the linking unit. */
val symbolRequirements: SymbolRequirement
/** Emit the given LinkingUnit to the target output. */
def emit(unit: LinkingUnit, output: LinkerOutput, logger: Logger)(
implicit ec: ExecutionContext): Future[Unit]
}
23
/** The basic backend for the Scala.js linker. */
final class BasicLinkerBackend(config: LinkerBackendImpl.Config)
extends LinkerBackend {
val coreSpec = config.commonConfig.coreSpec
private[this] val emitter = new Emitter(config.commonConfig)
val symbolRequirements: SymbolRequirement = emitter.symbolRequirements
def emit(unit: LinkingUnit, output: LinkerOutput, logger: Logger)(
implicit ec: ExecutionContext): Future[Unit] = {
...
val builder = new JSFileBuilder
emitter.emitAll(unit, builder, logger)
OutputFileImpl.fromOutputFile(output.jsFile)
.writeFull(builer.complete())
...
}
}
24
/** The Closure backend of the Scala.js linker. */
final class ClosureLinkerBackend(config: LinkerBackendImpl.Config)
extends LinkerBackend {
val coreSpec = config.commonConfig.coreSpec
private[this] val emitter = new Emitter(config.commonConfig)
val symbolRequirements: SymbolRequirement = emitter.symbolRequirements
def emit(unit: LinkingUnit, output: LinkerOutput, logger: Logger)(
implicit ec: ExecutionContext): Future[Unit] = {
...
val builer = new ClosureModuleBuilder
emitter.emitAll(unit, builer, logger)
val closureModules = makeClosureModules(builder.result())
val result = closureCompiler.compileModules(..., closureModules, ...)
writeResult(result, ...)
...
}
}
25
Possible	to	write	your	own	Scala.js	backend	in	user-space
	actually	does	that,
to	collect	imported	modules	for	Webpack
Japanese	line	1
Japanese	line	2
26
/** A JavaScript execution environment.
*
* This can run and interact with JavaScript code.
*
* Any implementation is expected to be fully thread-safe.
*/
trait JSEnv {
/** Human-readable name for this [[JSEnv]] */
val name: String
/** Starts a new (asynchronous) JS run. */
def start(input: Input, config: RunConfig): JSRun
/** Like [[start]], but initializes a communication channel. */
def startWithCom(input: Input, config: RunConfig,
onMessage: String => Unit): JSComRun
}
27
In	the	main	repo,	one	implementation:	
Completely	unrelated	but	compatible	implementations
are	in	other	repos:
Custom	environments	in	users'	builds
Japanese	line	1
Japanese	line	2
28
Custom	for	comprehensionsCustom	for	comprehensions
the	M	word
Japanese	line	1
Japanese	line	2
29
def genMethod(className: String, method: MethodDef)(
implicit globalKnowledge: GlobalKnowledge): WithGlobals[js.Tree] = {
val methodBody = method.body.getOrElse(
throw new AssertionError("Cannot generate an abstract method"))
implicit val pos = method.pos
val namespace = method.flags.namespace
val methodFunWithGlobals: WithGlobals[js.Function] =
desugarToFunction(className, method.args, methodBody, method.resultType)
methodFunWithGlobals.flatMap { methodFun =>
if (namespace != MemberNamespace.Public) {
...
} else {
if (useClasses) {
for (propName <- genPropertyName(method.name)) yield {
js.MethodDef(static = false, propName, methodFun.args,
methodFun.body)
}
} else {
genAddToPrototype(className, method.name, methodFun)
}
}
}
}
30
def genMethod(className: String, method: MethodDef)(
implicit globalKnowledge: GlobalKnowledge): WithGlobals[js.Tree] = {
val methodBody = method.body.getOrElse(
throw new AssertionError("Cannot generate an abstract method"))
implicit val pos = method.pos
val namespace = method.flags.namespace
val methodFunWithGlobals: WithGlobals[js.Function] =
desugarToFunction(className, method.args, methodBody, method.resultType)
methodFunWithGlobals.flatMap { methodFun =>
if (namespace != MemberNamespace.Public) {
...
} else {
if (useClasses) {
for (propName <- genPropertyName(method.name)) yield {
js.MethodDef(static = false, propName, methodFun.args,
methodFun.body)
}
} else {
genAddToPrototype(className, method.name, methodFun)
}
}
}
}
30
def genMethod(className: String, method: MethodDef)(
implicit globalKnowledge: GlobalKnowledge): WithGlobals[js.Tree] = {
val methodBody = method.body.getOrElse(
throw new AssertionError("Cannot generate an abstract method"))
implicit val pos = method.pos
val namespace = method.flags.namespace
val methodFunWithGlobals: WithGlobals[js.Function] =
desugarToFunction(className, method.args, methodBody, method.resultType)
methodFunWithGlobals.flatMap { methodFun =>
if (namespace != MemberNamespace.Public) {
...
} else {
if (useClasses) {
for (propName <- genPropertyName(method.name)) yield {
js.MethodDef(static = false, propName, methodFun.args,
methodFun.body)
}
} else {
genAddToPrototype(className, method.name, methodFun)
}
}
}
}
30
def genMethod(className: String, method: MethodDef)(
implicit globalKnowledge: GlobalKnowledge): WithGlobals[js.Tree] = {
val methodBody = method.body.getOrElse(
throw new AssertionError("Cannot generate an abstract method"))
implicit val pos = method.pos
val namespace = method.flags.namespace
val methodFunWithGlobals: WithGlobals[js.Function] =
desugarToFunction(className, method.args, methodBody, method.resultType)
methodFunWithGlobals.flatMap { methodFun =>
if (namespace != MemberNamespace.Public) {
...
} else {
if (useClasses) {
for (propName <- genPropertyName(method.name)) yield {
js.MethodDef(static = false, propName, methodFun.args,
methodFun.body)
}
} else {
genAddToPrototype(className, method.name, methodFun)
}
}
}
}
30
/** A monad that associates a set of global variable names to a value. */
private[emitter] final case class WithGlobals[+A](
value: A, globalVarNames: Set[String]) {
def map[B](f: A => B): WithGlobals[B] =
WithGlobals(f(value), globalVarNames)
def flatMap[B](f: A => WithGlobals[B]): WithGlobals[B] = {
val t = f(value)
WithGlobals(t.value, globalVarNames ++ t.globalVarNames)
}
}
private[emitter] object WithGlobals {
/** Constructs a `WithGlobals` with an empty set `globalVarNames`. */
def apply[A](value: A): WithGlobals[A] =
new WithGlobals(value, Set.empty)
def list[A](xs: List[WithGlobals[A]]): WithGlobals[List[A]] =
...
def option[A](xs: Option[WithGlobals[A]]): WithGlobals[Option[A]] =
...
} 31
Further	exploration	of	the	codeFurther	exploration	of	the	code
Time	permitting
Japanese	line	1
Japanese	line	2
32
         	
         scala-js.org scala.epfl.ch
33

Functional Object-Oriented Imperative Scala / 関数型オブジェクト指向命令型 Scala by Sébastien Doeraene

  • 1.
  • 2.
  • 3.
  • 4.
    def times2(xs: List[Int]):List[Int] = { var result: List[Int] = Nil var i = 0 while (i < xs.length) { result = result :+ (xs(i) * 2) i += 1 } result } 3
  • 5.
    def times2(xs: List[Int]):List[Int] = { var result: List[Int] = Nil for (i <- 0 until xs.length) { result = result :+ (xs(i) * 2) } result } 4
  • 6.
    def times2(xs: List[Int]):List[Int] = { val builder = List.newBuilder[Int] for (i <- 0 until xs.length) { builder += xs(i) * 2 } builder.result() } 5
  • 7.
    def times2(xs: List[Int]):List[Int] = { val builder = List.newBuilder[Int] for (x <- xs) { builder += x * 2 } builder.result() } 6
  • 8.
    def times2(xs: List[Int]):List[Int] = { for (x <- xs) yield x * 2 } def times2(xs: List[Int]): List[Int] = { xs.map(x => x * 2) } 7
  • 9.
  • 10.
    def fromPathClasspath(classpath: Seq[Path]):(Seq[IRContainer], Seq[Path]) = { val containers = Seq.newBuilder[IRContainer] val paths = Seq.newBuilder[Path] for (entry <- classpath if Files.exists(entry)) { val attrs = Files.readAttributes(entry, classOf[BasicFileAttributes]) if (attrs.isDirectory()) { walkIR(entry) { (path, attrs) => containers += IRContainer.fromIRFile(...) paths += path } } else if (entry.getFileName().toString().endsWith(".jar")) { containers += new JarIRContainer(entry, attrs.lastModifiedTime()) paths += entry } else { throw new IllegalArgumentException("Illegal classpath entry " + entry) } } (containers.result(), paths.result()) } 9
  • 11.
    def fromPathClasspath(classpath: Seq[Path]):(Seq[IRContainer], Seq[Path]) = { val containers = Seq.newBuilder[IRContainer] val paths = Seq.newBuilder[Path] for (entry <- classpath if Files.exists(entry)) { val attrs = Files.readAttributes(entry, classOf[BasicFileAttributes]) if (attrs.isDirectory()) { walkIR(entry) { (path, attrs) => containers += IRContainer.fromIRFile(...) paths += path } } else if (entry.getFileName().toString().endsWith(".jar")) { containers += new JarIRContainer(entry, attrs.lastModifiedTime()) paths += entry } else { throw new IllegalArgumentException("Illegal classpath entry " + entry) } } (containers.result(), paths.result()) } 9
  • 12.
  • 13.
    var test ={ genIsScalaJSObject(obj) && genIsClassNameInAncestors(...) } def typeOfTest(typeString: String): js.Tree = ... if (isAncestorOfString) test = test || typeOfTest("string") if (isAncestorOfHijackedNumberClass) { test = test || typeOfTest("number") if (useBigIntForLongs) test = test || genCallHelper("isLong", obj) } if (isAncestorOfBoxedBooleanClass) test = test || typeOfTest("boolean") if (isAncestorOfBoxedCharacterClass) test = test || (obj instanceof envField("Char")) test 11
  • 14.
    var test ={ genIsScalaJSObject(obj) && genIsClassNameInAncestors(...) } def typeOfTest(typeString: String): js.Tree = ... if (isAncestorOfString) test = test || typeOfTest("string") if (isAncestorOfHijackedNumberClass) { test = test || typeOfTest("number") if (useBigIntForLongs) test = test || genCallHelper("isLong", obj) } if (isAncestorOfBoxedBooleanClass) test = test || typeOfTest("boolean") if (isAncestorOfBoxedCharacterClass) test = test || (obj instanceof envField("Char")) test 11
  • 15.
    val test0 ={ genIsScalaJSObject(obj) && genIsClassNameInAncestors(...) } def typeOfTest(typeString: String): js.Tree = ... val test1 = if (isAncestorOfString) test0 || typeOfTest("string") else test0 val test3 = if (isAncestorOfHijackedNumberClass) { val test2 = test1 || typeOfTest("number") if (useBigIntForLongs) test2 || genCallHelper("isLong", obj) else test2 } val test4 = if (isAncestorOfBoxedBooleanClass) test3 || typeOfTest("boolean") else test3 val test5 = if (isAncestorOfBoxedCharacterClass) test4 || (obj instanceof envField("Char")) else test4 test5 12
  • 16.
    val test0 ={ genIsScalaJSObject(obj) && genIsClassNameInAncestors(...) } def typeOfTest(typeString: String): js.Tree = ... val test1 = if (isAncestorOfString) test0 || typeOfTest("string") else test0 val test3 = if (isAncestorOfHijackedNumberClass) { val test2 = test1 || typeOfTest("number") if (useBigIntForLongs) test2 || genCallHelper("isLong", obj) else test2 } val test4 = if (isAncestorOfBoxedBooleanClass) test3 || typeOfTest("boolean") else test3 val test5 = if (isAncestorOfBoxedCharacterClass) test4 || (obj instanceof envField("Char")) else test4 test5 12
  • 17.
    val test0 ={ genIsScalaJSObject(obj) && genIsClassNameInAncestors(...) } def typeOfTest(typeString: String): js.Tree = ... val test1 = if (isAncestorOfString) test0 || typeOfTest("string") else test0 val test3 = if (isAncestorOfHijackedNumberClass) { val test2 = test1 || typeOfTest("number") if (useBigIntForLongs) test2 || genCallHelper("isLong", obj) else test2 } val test4 = if (isAncestorOfBoxedBooleanClass) test3 || typeOfTest("boolean") else test3 val test5 = if (isAncestorOfBoxedCharacterClass) test4 || (obj instanceof envField("Char")) else test4 test5 12
  • 18.
    val test0 ={ genIsScalaJSObject(obj) && genIsClassNameInAncestors(...) } def typeOfTest(typeString: String): js.Tree = ... val test1 = if (isAncestorOfString) test0 || typeOfTest("string") else test0 val test3 = if (isAncestorOfHijackedNumberClass) { val test2 = test1 || typeOfTest("number") if (useBigIntForLongs) test2 || genCallHelper("isLong", obj) else test2 } val test4 = if (isAncestorOfBoxedBooleanClass) test3 || typeOfTest("boolean") else test3 val test5 = if (isAncestorOfBoxedCharacterClass) test4 || (obj instanceof envField("Char")) else test4 test5 12
  • 19.
  • 20.
  • 21.
  • 22.
    final class Emitter(config:CommonPhaseConfig) { def emitAll(unit: LinkingUnit, builder: JSLineBuilder, logger: Logger): Unit = { ... } } 16
  • 23.
    final class Emitter(config:CommonPhaseConfig) { private class State(val lastMentionedDangerousGlobalRefs: Set[String]) { val jsGen: JSGen = new JSGen(..., lastMentionedDangerousGlobalRefs) val classEmitter: ClassEmitter = new ClassEmitter(jsGen) val coreJSLib: WithGlobals[js.Tree] = CoreJSLib.build(jsGen) } private var state: State = new State(Set.empty) private def jsGen: JSGen = state.jsGen private def classEmitter: ClassEmitter = state.classEmitter private def coreJSLib: WithGlobals[js.Tree] = state.coreJSLib private val classCaches = mutable.Map.empty[List[String], ClassCache] def emitAll(unit: LinkingUnit, builder: JSLineBuilder, logger: Logger): Unit = { ... classCaches.getOrElseUpdate(..., ...) ... val mentionedDangerousGlobalRefs = ... state = new State(mentionedDangerousGlobalRefs) ... } } 17
  • 24.
    final class Emitter(config:CommonPhaseConfig) { private class State(val lastMentionedDangerousGlobalRefs: Set[String]) { val jsGen: JSGen = new JSGen(..., lastMentionedDangerousGlobalRefs) val classEmitter: ClassEmitter = new ClassEmitter(jsGen) val coreJSLib: WithGlobals[js.Tree] = CoreJSLib.build(jsGen) } private var state: State = new State(Set.empty) private def jsGen: JSGen = state.jsGen private def classEmitter: ClassEmitter = state.classEmitter private def coreJSLib: WithGlobals[js.Tree] = state.coreJSLib private val classCaches = mutable.Map.empty[List[String], ClassCache] def emitAll(unit: LinkingUnit, builder: JSLineBuilder, logger: Logger): Unit = { ... classCaches.getOrElseUpdate(..., ...) ... val mentionedDangerousGlobalRefs = ... state = new State(mentionedDangerousGlobalRefs) ... } } 17
  • 25.
    final class Emitter(config:CommonPhaseConfig) { private class State(val lastMentionedDangerousGlobalRefs: Set[String]) { val jsGen: JSGen = new JSGen(..., lastMentionedDangerousGlobalRefs) val classEmitter: ClassEmitter = new ClassEmitter(jsGen) val coreJSLib: WithGlobals[js.Tree] = CoreJSLib.build(jsGen) } private var state: State = new State(Set.empty) private def jsGen: JSGen = state.jsGen private def classEmitter: ClassEmitter = state.classEmitter private def coreJSLib: WithGlobals[js.Tree] = state.coreJSLib private val classCaches = mutable.Map.empty[List[String], ClassCache] def emitAll(unit: LinkingUnit, builder: JSLineBuilder, logger: Logger): Unit = { ... classCaches.getOrElseUpdate(..., ...) ... val mentionedDangerousGlobalRefs = ... state = new State(mentionedDangerousGlobalRefs) ... } } 17
  • 26.
    final class Emitter(config:CommonPhaseConfig) { private class State(val lastMentionedDangerousGlobalRefs: Set[String]) { val jsGen: JSGen = new JSGen(..., lastMentionedDangerousGlobalRefs) val classEmitter: ClassEmitter = new ClassEmitter(jsGen) val coreJSLib: WithGlobals[js.Tree] = CoreJSLib.build(jsGen) } private var state: State = new State(Set.empty) private def jsGen: JSGen = state.jsGen private def classEmitter: ClassEmitter = state.classEmitter private def coreJSLib: WithGlobals[js.Tree] = state.coreJSLib private val classCaches = mutable.Map.empty[List[String], ClassCache] def emitAll(unit: LinkingUnit, builder: JSLineBuilder, logger: Logger): Unit = { ... classCaches.getOrElseUpdate(..., ...) ... val mentionedDangerousGlobalRefs = ... state = new State(mentionedDangerousGlobalRefs) ... } } 17
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
    /** A backendof a standard Scala.js linker. */ abstract class LinkerBackend { /** Core specification that this linker backend implements. */ val coreSpec: CoreSpec /** Symbols this backend needs to be present in the linking unit. */ val symbolRequirements: SymbolRequirement /** Emit the given LinkingUnit to the target output. */ def emit(unit: LinkingUnit, output: LinkerOutput, logger: Logger)( implicit ec: ExecutionContext): Future[Unit] } 23
  • 33.
    /** The basicbackend for the Scala.js linker. */ final class BasicLinkerBackend(config: LinkerBackendImpl.Config) extends LinkerBackend { val coreSpec = config.commonConfig.coreSpec private[this] val emitter = new Emitter(config.commonConfig) val symbolRequirements: SymbolRequirement = emitter.symbolRequirements def emit(unit: LinkingUnit, output: LinkerOutput, logger: Logger)( implicit ec: ExecutionContext): Future[Unit] = { ... val builder = new JSFileBuilder emitter.emitAll(unit, builder, logger) OutputFileImpl.fromOutputFile(output.jsFile) .writeFull(builer.complete()) ... } } 24
  • 34.
    /** The Closurebackend of the Scala.js linker. */ final class ClosureLinkerBackend(config: LinkerBackendImpl.Config) extends LinkerBackend { val coreSpec = config.commonConfig.coreSpec private[this] val emitter = new Emitter(config.commonConfig) val symbolRequirements: SymbolRequirement = emitter.symbolRequirements def emit(unit: LinkingUnit, output: LinkerOutput, logger: Logger)( implicit ec: ExecutionContext): Future[Unit] = { ... val builer = new ClosureModuleBuilder emitter.emitAll(unit, builer, logger) val closureModules = makeClosureModules(builder.result()) val result = closureCompiler.compileModules(..., closureModules, ...) writeResult(result, ...) ... } } 25
  • 35.
  • 36.
    /** A JavaScriptexecution environment. * * This can run and interact with JavaScript code. * * Any implementation is expected to be fully thread-safe. */ trait JSEnv { /** Human-readable name for this [[JSEnv]] */ val name: String /** Starts a new (asynchronous) JS run. */ def start(input: Input, config: RunConfig): JSRun /** Like [[start]], but initializes a communication channel. */ def startWithCom(input: Input, config: RunConfig, onMessage: String => Unit): JSComRun } 27
  • 37.
  • 38.
  • 39.
    def genMethod(className: String,method: MethodDef)( implicit globalKnowledge: GlobalKnowledge): WithGlobals[js.Tree] = { val methodBody = method.body.getOrElse( throw new AssertionError("Cannot generate an abstract method")) implicit val pos = method.pos val namespace = method.flags.namespace val methodFunWithGlobals: WithGlobals[js.Function] = desugarToFunction(className, method.args, methodBody, method.resultType) methodFunWithGlobals.flatMap { methodFun => if (namespace != MemberNamespace.Public) { ... } else { if (useClasses) { for (propName <- genPropertyName(method.name)) yield { js.MethodDef(static = false, propName, methodFun.args, methodFun.body) } } else { genAddToPrototype(className, method.name, methodFun) } } } } 30
  • 40.
    def genMethod(className: String,method: MethodDef)( implicit globalKnowledge: GlobalKnowledge): WithGlobals[js.Tree] = { val methodBody = method.body.getOrElse( throw new AssertionError("Cannot generate an abstract method")) implicit val pos = method.pos val namespace = method.flags.namespace val methodFunWithGlobals: WithGlobals[js.Function] = desugarToFunction(className, method.args, methodBody, method.resultType) methodFunWithGlobals.flatMap { methodFun => if (namespace != MemberNamespace.Public) { ... } else { if (useClasses) { for (propName <- genPropertyName(method.name)) yield { js.MethodDef(static = false, propName, methodFun.args, methodFun.body) } } else { genAddToPrototype(className, method.name, methodFun) } } } } 30
  • 41.
    def genMethod(className: String,method: MethodDef)( implicit globalKnowledge: GlobalKnowledge): WithGlobals[js.Tree] = { val methodBody = method.body.getOrElse( throw new AssertionError("Cannot generate an abstract method")) implicit val pos = method.pos val namespace = method.flags.namespace val methodFunWithGlobals: WithGlobals[js.Function] = desugarToFunction(className, method.args, methodBody, method.resultType) methodFunWithGlobals.flatMap { methodFun => if (namespace != MemberNamespace.Public) { ... } else { if (useClasses) { for (propName <- genPropertyName(method.name)) yield { js.MethodDef(static = false, propName, methodFun.args, methodFun.body) } } else { genAddToPrototype(className, method.name, methodFun) } } } } 30
  • 42.
    def genMethod(className: String,method: MethodDef)( implicit globalKnowledge: GlobalKnowledge): WithGlobals[js.Tree] = { val methodBody = method.body.getOrElse( throw new AssertionError("Cannot generate an abstract method")) implicit val pos = method.pos val namespace = method.flags.namespace val methodFunWithGlobals: WithGlobals[js.Function] = desugarToFunction(className, method.args, methodBody, method.resultType) methodFunWithGlobals.flatMap { methodFun => if (namespace != MemberNamespace.Public) { ... } else { if (useClasses) { for (propName <- genPropertyName(method.name)) yield { js.MethodDef(static = false, propName, methodFun.args, methodFun.body) } } else { genAddToPrototype(className, method.name, methodFun) } } } } 30
  • 43.
    /** A monadthat associates a set of global variable names to a value. */ private[emitter] final case class WithGlobals[+A]( value: A, globalVarNames: Set[String]) { def map[B](f: A => B): WithGlobals[B] = WithGlobals(f(value), globalVarNames) def flatMap[B](f: A => WithGlobals[B]): WithGlobals[B] = { val t = f(value) WithGlobals(t.value, globalVarNames ++ t.globalVarNames) } } private[emitter] object WithGlobals { /** Constructs a `WithGlobals` with an empty set `globalVarNames`. */ def apply[A](value: A): WithGlobals[A] = new WithGlobals(value, Set.empty) def list[A](xs: List[WithGlobals[A]]): WithGlobals[List[A]] = ... def option[A](xs: Option[WithGlobals[A]]): WithGlobals[Option[A]] = ... } 31
  • 44.
  • 45.