Slide 1

Slide 1 text

Scala.js 
 Towards Utopia Richard Dallaway, @d6y underscore.io

Slide 2

Slide 2 text

Make Change Easier

Slide 3

Slide 3 text

Agenda Part 1
 Scala.js introduction Part 2
 Collaborative text editing with WOOT Part 3
 SBT, calling to and from Javascript…

Slide 4

Slide 4 text

Two Ideas Gradually introduce Scala.js
 to an existing code base Distributed data types are
 fun & a great fit with Scala.js

Slide 5

Slide 5 text

5 Minute Scala.js Intro — Part 1 —

Slide 6

Slide 6 text

scala-js.org

Slide 7

Slide 7 text

Your Scala App Here JavaScript SBT Plugin

Slide 8

Slide 8 text

Your Scala App Here Std Lib Libs, Macros… JavaScript SBT Plugin

Slide 9

Slide 9 text

Your Scala App Here Std Lib Libs, Macros… JavaScript DOM jQuery… SBT Plugin

Slide 10

Slide 10 text

Yes, all of Scala But not Java or reflection-based code

Slide 11

Slide 11 text

Yes, all of Scala But not Java or reflection-based code Ported New Typed JS Bindings Scalaz Scalatags scalajs-dom ScalaCheck uTest scalajs-jquery shapeless uPickle scalajs-react … … …

Slide 12

Slide 12 text

Five Basic Steps addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.3")
 
 Write Scala @JSExport what you want expose sbt> fastOptJs Use resulting .js file

Slide 13

Slide 13 text

// Scala def answer = Option(41).map(_ + 1)

Slide 14

Slide 14 text

// JavaScript (function() { var o = Option().apply(41); if (o.isEmpty()) { var answer = None() } else { var arg1 = o.get(); var answer = new Some().init(1 + arg1); }; return answer }); // Scala def answer = Option(41).map(_ + 1)

Slide 15

Slide 15 text

// JavaScript (function() { var this$1 = $m_s_Option().apply__O__s_Option(41); if (this$1.isEmpty__Z()) { var answer = $m_s_None$() } else { var arg1 = this$1.get__O(); var x$1 = $uI(arg1); var answer = new $c_s_Some().init___O(((1 + x$1) | 0)) }; return answer }); // Scala def answer = Option(41).map(_ + 1)

Slide 16

Slide 16 text

Features Fast Compiler Optimized Output (this app: 295k) Resulting JS is Fast Great interop with JS Community

Slide 17

Slide 17 text

Not a Framework

Slide 18

Slide 18 text

Collaborative App — Part 2 —

Slide 19

Slide 19 text

What we want to build

Slide 20

Slide 20 text

What we want to build

Slide 21

Slide 21 text

What we want to build http4s

Slide 22

Slide 22 text

What we want to build http4s

Slide 23

Slide 23 text

What we want to build http4s

Slide 24

Slide 24 text

What we want to build http4s What should these messages be?

Slide 25

Slide 25 text

Don’t Do This C A T C A T Alice’s View Bob’s View 1 2 3 1 2 3

Slide 26

Slide 26 text

Don’t Do This C A T C A T Alice’s View Bob’s View 1 2 3 1 2 3 1 2 3 4 C H A T insert H @ 2

Slide 27

Slide 27 text

Don’t Do This C A T C A T Alice’s View Bob’s View 1 2 3 1 2 3 1 2 3 4 C H A T insert H @ 2 1 2 C A delete 3

Slide 28

Slide 28 text

Don’t Do This C A T C A T Alice’s View Bob’s View 1 2 3 1 2 3 1 2 3 4 C H A T insert H @ 2 1 2 C A delete 3 C H A C H T

Slide 29

Slide 29 text

Commutative
 Replicated
 Data
 Type

Slide 30

Slide 30 text

WOOT C A T C A T Alice’s View Bob’s View 1 2 3 1 2 3

Slide 31

Slide 31 text

WOOT C A T C A T Alice’s View Bob’s View 1 2 3 1 2 3 1 2 3 4 C H A T C ≺ H ≺ A

Slide 32

Slide 32 text

WOOT C A T C A T Alice’s View Bob’s View 1 2 3 1 2 3 1 2 3 4 C H A T C ≺ H ≺ A 1 2 C A Delete T

Slide 33

Slide 33 text

WOOT C A T C A T Alice’s View Bob’s View 1 2 3 1 2 3 1 2 3 4 C H A T C ≺ H ≺ A 1 2 C A Delete T C H A C H A

Slide 34

Slide 34 text

WChar ID Prev Next Alpha Visible?

Slide 35

Slide 35 text

WChar ID Prev Next Alpha Visible? Site Clock

Slide 36

Slide 36 text

WChar ID Prev Next Alpha Visible? Site Clock ID ID

Slide 37

Slide 37 text

Algorithm a b c d a b c d a b c d a b c x d a c d a b y c d a b c d ❌

Slide 38

Slide 38 text

Algorithm a b c d a b c d a b c d a b c x d a c d a b y c d a b c d x ❌

Slide 39

Slide 39 text

Algorithm a b c d a b c d a b c d a b c x d a c d a b y c d a b c d x / ❌

Slide 40

Slide 40 text

Algorithm a b c d a b c d a b c d a b c x d a c d a b y c d a b c d x y / ❌

Slide 41

Slide 41 text

In JavaScript Algorithm ~ 200 lines, 7.5k Tests ~ 200 lines + require.js, underscore.js, jasmine

Slide 42

Slide 42 text

But now we have 
 two problems

Slide 43

Slide 43 text

We’re going to look at… Local Insert Remote Ingest

Slide 44

Slide 44 text

We’re going to look at… (WString,Char,Pos) => (WString,WChar) Local Insert Remote Ingest

Slide 45

Slide 45 text

We’re going to look at… (WString,Char,Pos) => (WString,WChar) Local Insert (WString,WChar) => WString Remote Ingest

Slide 46

Slide 46 text

Migrating to Scala.js — Part 3 —

Slide 47

Slide 47 text

What we want to build http4s

Slide 48

Slide 48 text

What we want to build http4s

Slide 49

Slide 49 text

Server WOOT Editor UI WebSocket WOOT

Slide 50

Slide 50 text

Server WOOT Editor UI WebSocket WOOT Browser JVM { {

Slide 51

Slide 51 text

Server WOOT Editor UI WebSocket WOOT Browser JVM { { JavaScript JavaScript Scala.js Scala.js Scala

Slide 52

Slide 52 text

Server WOOT Editor UI WebSocket WOOT Browser JVM { { JavaScript JavaScript Scala.js Scala.js Scala

Slide 53

Slide 53 text

Server WOOT Editor UI WebSocket Client Wrapper

Slide 54

Slide 54 text

build.sbt shared client server

Slide 55

Slide 55 text

build.sbt shared client server > compile .sjsir
 .class .class

Slide 56

Slide 56 text

build.sbt shared client server > compile .sjsir
 .class .class > fastOptJS .js

Slide 57

Slide 57 text

build.sbt shared client server > compile .sjsir
 .class .class > fastOptJS .js > fullOptJS .js

Slide 58

Slide 58 text

ScalaCheck shared client server "org.scalacheck" %%% "scalacheck" % "1.12.2" % "test"

Slide 59

Slide 59 text

ScalaCheck shared client server "org.scalacheck" %%% "scalacheck" % "1.12.2" % "test" > test [info] + Local insert preserves original text: OK, passed 250 tests. [info] + Insert order is irrelevant: OK, passed 250 tests. [info] + Inserting produces consistent text: OK, passed 250 tests. [info] + Insert is idempotent: OK, passed 250 tests. 
 ︙

Slide 60

Slide 60 text

ID Prev Next Alpha Visible? Site Clock ID ID

Slide 61

Slide 61 text

// Scala import scala.scalajs.js import js.annotation.JSExport @JSExport case class WChar( id: Id, alpha: Byte, prev: Id, next: Id, isVisible: Boolean = true) // HTML + JavaScript <script> var char = new WChar(...)

Slide 62

Slide 62 text

// Scala import scala.scalajs.js import js.annotation.JSExport @JSExport case class WChar( id: Id, alpha: Byte, prev: Id, next: Id, isVisible: Boolean = true) // HTML + JavaScript <script> var char = new WChar(...)

Slide 63

Slide 63 text

OK, but… case class WChar( id: Id, alpha: Byte, prev: Id, next: Id, isVisible: Boolean = true) Should be Char? Can’t this whole API be easier to use?

Slide 64

Slide 64 text

Semantics = Scala

Slide 65

Slide 65 text

There are differences

Slide 66

Slide 66 text

There are differences JavaScript has no Char toString differences …not as many as you think

Slide 67

Slide 67 text

Wrapper Solution package client import js.annotation.JSExport import woot.WString @JSExport class WootClient() { var doc = WString.empty() @JSExport def insert(s: String, pos: Int): Json = ??? @JSExport def ingest(json: Json): Unit = ??? }

Slide 68

Slide 68 text

uPickle sealed trait Operation { def wchar: WChar } case class InsertOp(override val wchar: WChar) extends Operation case class DeleteOp(override val wchar: WChar) extends Operation import upickle._ // Produce JSON val op: Operation = ??? val json = write(op) // Consume JSON Try(read[Operation](json)).map( op => ...)

Slide 69

Slide 69 text

uPickle sealed trait Operation { def wchar: WChar } case class InsertOp(override val wchar: WChar) extends Operation case class DeleteOp(override val wchar: WChar) extends Operation import upickle._ // Produce JSON val op: Operation = ??? val json = write(op) // Consume JSON Try(read[Operation](json)).map( op => ...)

Slide 70

Slide 70 text

uPickle sealed trait Operation { def wchar: WChar } case class InsertOp(override val wchar: WChar) extends Operation case class DeleteOp(override val wchar: WChar) extends Operation import upickle._ // Produce JSON val op: Operation = ??? val json = write(op) // Consume JSON Try(read[Operation](json)).map( op => ...)

Slide 71

Slide 71 text

uPickle sealed trait Operation { def wchar: WChar } case class InsertOp(override val wchar: WChar) extends Operation case class DeleteOp(override val wchar: WChar) extends Operation import upickle._ // Produce JSON val op: Operation = ??? val json = write(op) // Consume JSON Try(read[Operation](json)).map( op => ...) ["woot.InsertOp", { "wchar": { "id": ["woot.CharId", {...}], "alpha": "*", "prev": ["woot.Beginning",{}], "next": ["woot.Ending", {}]} } ]

Slide 72

Slide 72 text

@JSExport class WootClient() { var doc = WString.empty() @JSExport def insert(s: String, pos: Int): Json = ??? @JSExport def ingest(json: Json): Unit = ??? }

Slide 73

Slide 73 text

@JSExport class WootClient() { var doc = WString.empty() @JSExport def insert(s: String, pos: Int): Json = { val (op, wstring) = doc.insert(s.head, pos) doc = wstring write(op) } @JSExport def ingest(json: Json): Unit = ??? }

Slide 74

Slide 74 text

Effecting the DOM import org.scalajs.dom val element = dom.document.getElementById("editor") // DANGER! Run away. // Unsafe access to Ace’s API val ace = js.Dynamic.global.ace ace.edit("editor") .getSession() .getDocument() .setValue("We have control”) // Risk JS Uncaught type error

Slide 75

Slide 75 text

Effecting the DOM import org.scalajs.dom val element = dom.document.getElementById("editor") // DANGER! Run away. // Unsafe access to Ace’s API val ace = js.Dynamic.global.ace ace.edit("editor") .getSession() .getDocument() .setValue("We have control”) // Risk JS Uncaught type error

Slide 76

Slide 76 text

Effecting the DOM // JavaScript var updateEditor = function(s, isVisible) { var delta = convertWootToAceCoordinates(…); editor.getSession() .getDocument().applyDeltas([delta]); } jQuery(document).ready(function() { client = new client.WootClient(updateEditor); });

Slide 77

Slide 77 text

Effecting the DOM // JavaScript var updateEditor = function(s, isVisible) { var delta = convertWootToAceCoordinates(…); editor.getSession() .getDocument().applyDeltas([delta]); } jQuery(document).ready(function() { client = new client.WootClient(updateEditor); });

Slide 78

Slide 78 text

@JSExport class WootClient() { var doc = WString.empty() @JSExport def ingest(json: Json): Unit = ??? }

Slide 79

Slide 79 text

@JSExport class WootClient(f: js.Function2[String,Boolean,Unit]) { var doc = WString.empty() @JSExport def ingest(json: Json): Unit = ??? }

Slide 80

Slide 80 text

@JSExport class WootClient(f: js.Function2[String,Boolean,Unit]) { var doc = WString.empty() @JSExport def ingest(json: Json): Unit = ??? } // JavaScript var updateEditor = function(s, isVisible) {…

Slide 81

Slide 81 text

@JSExport class WootClient(f: js.Function2[String,Boolean,Unit]) { var doc = WString.empty() @JSExport def ingest(json: Json): Unit = Try(read[Operation](json)).foreach(applyOperation) def applyOperation(op: Operation): Unit = ??? }

Slide 82

Slide 82 text

@JSExport class WootClient(f: js.Function2[String,Boolean,Unit]) { var doc = WString.empty() @JSExport def ingest(json: Json): Unit = Try(read[Operation](json)).foreach(applyOperation) def applyOperation(op: Operation): Unit = { val (ops, wstring) = doc.integrate(op) // Become the updated document: doc = wstring // Side effects: ops.foreach { case InsertOp(ch) => f(ch.alpha.toString, true) case DeleteOp(ch) => f(ch.alpha.toString, false) } } }

Slide 83

Slide 83 text

def integrate(op: Operation): (Vector[Operation], WString) = op match { // - Don't insert the same ID twice: case InsertOp(c,_) if chars.exists(_.id == c.id) => (Vector.empty, this) // - Insert can go ahead if the next & prev exist: case InsertOp(c,_) if canIntegrate(op) => val (ops, doc) = integrate(c, c.prev, c.next).dequeue() (op +: ops, doc) // - We can delete any char that exists: case DeleteOp(c,_) if canIntegrate(op) => (Vector(op), hide(c)) // - Anything else goes onto the queue for another time: case _ => (Vector.empty, enqueue(op)) } @scala.annotation.tailrec private def integrate(c: WChar, before: Id, after: Id): WString = { // Looking at all the characters between the previous and next positions: subseq(before, after) match { // - when where's no option about where to insert, perform the insert case Vector() => ins(c, indexOf(after)) // - when there's a choice, locate an insert point based on `Id.<` case search: Vector[WChar] => val L: Vector[Id] = before +: trim(search).map(_.id) :+ after val i = math.max(1, math.min(L.length-1, L.takeWhile(_ < c.id).length)) integrate(c, L(i-1), L(i)) }

Slide 84

Slide 84 text

What we’ve Seen Multi-project build ✓ Wrote Scala, ran it both places ✓ Great interop (call JS, be called by JS) ✓ Dirty dirty dynamic calls ✓ Used cross-compiled libraries ✓

Slide 85

Slide 85 text

github.com/d6y/wootjs

Slide 86

Slide 86 text

github.com/d6y/wootjs [info] Compiling 2 Scala sources to ... [info] Fast optimizing woot-client-fastopt.js 2015-05-13 WootServer - Starting Http4s-blaze

Slide 87

Slide 87 text

github.com/d6y/wootjs [info] Compiling 2 Scala sources to ... [info] Fast optimizing woot-client-fastopt.js 2015-05-13 WootServer - Starting Http4s-blaze

Slide 88

Slide 88 text

Benefits? IDE support Single language Write once, run both places

Slide 89

Slide 89 text

“It’s the types, stupid”

Slide 90

Slide 90 text

Make Change Easier

Slide 91

Slide 91 text

Two Ideas Gradually introduce Scala.js
 to an existing code base Distributed data types are
 fun & a great fit with Scala.js

Slide 92

Slide 92 text

Thanks! Richard Dallaway, @d6y https://github.com/d6y/wootjs underscore.io