Scala.js With Monix Example
One of the annoying thing in front-end development is managing long calculations in a browser. JavaScript does things synchronously, and when you want to do something, that needs time for execution, JavaScript resists. Here is an example of such long calculation. At first, I shall demonstrate synchronous code, then asynchronous. Scala has the great library Monix, that works well with Scala.js. I have already told about Monix in some of my previous posts. Of cause, I’m going to use this library in my asynchronous example.
My son goes to school. One time he was doing his math homework and he had problems with tasks on factorization:
444,333,222 = 2 * 3 * 3 * 3 * 37 * 222,389
I decided to help him with these dull calculations and made a simple Android application for that purpose.
Now it’s all in the past. Some days ago I saw this code again.
“Can I use this Scala code for a web application?” – I asked myself.
I decided that I can do it by using Scala.js and of course in a functional way, mostly.
Version 1, Synchronous
Javascript works in single-thread environment. So I start with synchronous code execution. I need only 100 lines of Scala code to do all I want:
package sync
import scala.annotation.tailrec
import scala.scalajs.js.JSApp
import org.scalajs.dom
import dom.{Element, document, window}
import org.scalajs.dom.raw.{Event, HTMLInputElement}
import scala.util.{Failure, Success, Try}
object Primes extends JSApp {
private val calculate = document.getElementById("calculate")
private val target = document.getElementById("target")
private val numbers = document.getElementById("number")
sealed trait Action
case class PrintResult(n: Vector[Long], t: Long) extends Action
case class State(
nextNumber: Long,
primes: Vector[Long],
target: Long,
isFinished: Boolean = false,
Error: Option[String] = None
)
private def makeString(n: Long): String = n.toString.reverse.grouped(3).reduceLeft { (x, s) =>
if (x.length >= 3) x + "," + s
else x
}.reverse
private def fillElement(target: Element)(text: String) = target.innerHTML = text
private val fillEl = fillElement(target)(_)
private def doAction(action: Action) = action match {
case PrintResult(n, t) if t == 1 =>
val s = n.map(makeString).mkString("final: ", " * ", " = " + makeString(n.product))
fillEl(s)
case _ =>
}
def main(): Unit = {
calculate.addEventListener("click",
(_: Event) => {
val number = Try(numbers.asInstanceOf[HTMLInputElement].value.toLong) match {
case Success(n) => n
case Failure(_) => 0
}
if (number > 1) {
target.innerHTML = ""
val initialState = State(1, Vector.empty[Long], number)
implicit val dispatcher: (Action) => Unit = doAction
window.setTimeout(() => doCalculation(initialState), 100)
} else {
target.innerHTML = "You must enter a number > 1"
}
}
)
}
@tailrec
private def doDivide(state: State)(implicit dispatcher: (Action) => Unit): State = {
state match {
case State(n, p, t, _, _) if t % n == 0 =>
val target = t / n
val primes = p :+ n
dispatcher(PrintResult(primes, target))
doDivide(state.copy(primes = primes, target = target))
case _ => state
}
}
private def newState(s: State)(implicit dispatcher: (Action) => Unit): State =
s match {
case State(n, _, t, _, _) if t % n == 0 => doDivide(s)
case State(_, _, t, _, _) if t > 1 => s
case _ => s.copy(isFinished = true)
}
@tailrec
private def doCalculation(state: State)(implicit dispatcher: (Action) => Unit): Unit = {
state match {
case State(_, _, _, true, _) =>
case State(_, _, _, _, Some(_)) =>
doCalculation(state.copy(isFinished = true))
case State(n, _, t, _, _) if n >= t =>
doCalculation(state.copy(isFinished = true))
case State(n, _, _, _, _) =>
doCalculation(newState(state.copy(nextNumber = n + 1)))
}
}
}
This application has immutable state State
case class State(
nextNumber: Long,
primes: Vector[Long],
target: Long,
isFinished: Boolean = false,
Error: Option[String] = None
)
The state is passed as a parameter to the recursive function doCalculation
:
@tailrec
private def doCalculation(state: State)(implicit dispatcher: (Action) => Unit): Unit = {
state match {
case State(_, _, _, true, _) =>
case State(_, _, _, _, Some(_)) =>
doCalculation(state.copy(isFinished = true))
case State(n, _, t, _, _) if n >= t =>
doCalculation(state.copy(isFinished = true))
case State(n, _, _, _, _) =>
doCalculation(newState(state.copy(nextNumber = n + 1)))
}
}
This function executes recursively, sequentially incrementing a nextNumber
field of the state. A new state is taken as a parameter by the newState
function:
private def newState(s: State)(implicit dispatcher: (Action) => Unit): State =
s match {
case State(n, _, t, _, _) if t % n == 0 => doDivide(s)
case State(_, _, t, _, _) if t > 1 => s
case _ => s.copy(isFinished = true)
}
This function checks if the nextNumber
is a divisor without residue. If so it calls recursive function doDivide
:
@tailrec
private def doDivide(state: State)(implicit dispatcher: (Action) => Unit): State = {
state match {
case State(n, p, t, _, _) if t % n == 0 =>
val target = t / n
val primes = p :+ n
dispatcher(PrintResult(primes, target))
doDivide(state.copy(primes = primes, target = target))
case _ => state
}
}
This function is needed because a number can be a divisor without residue several times. Our divisors are stored in the primes
field of our state.
You can ask: “What the mysterious implicit dispatcher
is?”
implicit val dispatcher: (Action) => Unit = doAction
It’s a function, that takes a role of a message machine. In our case it takes a value of doAction
function:
private def fillElement(target: Element)(text: String) = target.innerHTML = text
private val fillEl = fillElement(target)(_)
private def doAction(action: Action) = action match {
case PrintResult(n, t) if t == 1 =>
val s = n.map(makeString).mkString("final: ", " * ", " = " + makeString(n.product))
fillEl(s)
case _ =>
}
The function makeString
takes a number (12345678) as a parameter and returns a formatted string (12,345,678):
private def makeString(n: Long): String =
n.toString.reverse.grouped(3).reduceLeft { (x, s) =>
if (x.length >= 3) x + "," + s
else x
}.reverse
Do you now how to do the same in JavaScript? Some very different way, I suspect.
Here is the main
method:
def main(): Unit = {
calculate.addEventListener("click",
(_: Event) => {
val number = Try(numbers.asInstanceOf[HTMLInputElement].value.toLong) match {
case Success(n) => n
case Failure(_) => 0
}
if (number > 1) {
target.innerHTML = ""
val initialState = State(1, Vector.empty[Long], number)
implicit val dispatcher: (Action) => Unit = doAction
window.setTimeout(() => doCalculation(initialState), 100)
} else {
target.innerHTML = "You must enter a number > 1"
}
}
)
}
It’s a sole event handler. When you click on the button, it triggers the whole process.
First of all, the script reads the value of the input field of the form. If this value is transformed into Integer successfully, our calculation starts. Otherwise the caption about an error appears on the page.
The last remark: I use window.setTimeout
here to have time to clean the result field before calculation starts.
This code is successfully compiled into minified JavaScript file that weighs 120kB.
And it works, see here
What’s wrong with it?
If you enter a big number, greater than billion, nothing happens when you push the button. You will be waiting for tens of seconds before you get the result.
When you enter a very big number, the browser will stop responding. You will have to reload it.
There are two ways: you can limit size of a number, or you can choose asynchronous calculations.
Version 2, Asynchronous
When JavaScript starts synchronous execution of our doCalculation
function, it must go to the end before it allows any other processes. JavaScript has window.SetTimeout
to cope somehow with such problems. Actually I have already used it in our “sync” example: remember window.setTimeout(() => doCalculation(initialState), 100)
.
But I know another way – Monix.
Monix is a high-performance Scala / Scala.js library for composing asynchronous and event-based programs.
So Monix gives us tools to make our generated synchronous JavaScript asynchronous. I expect that our asynchronous application to gain new features:
- If user has decided that he has been waiting too long for a final result, he can stop calculation by clicking the “Stop” button.
- The browser will never stop responding.
- The user can see several intermediate results (as if he is watching the calculation process)
Let’s create a class Dispatcher
:
package async
import monix.execution.{Ack, Cancelable, Scheduler}
import monix.execution.Ack.Continue
import monix.reactive.Observer
import monix.reactive.subjects.BehaviorSubject
import monix.execution.ExecutionModel.AlwaysAsyncExecution
import scala.concurrent.Future
import scala.concurrent.duration.{Duration, MILLISECONDS}
class Dispatcher [Action](val initialState: Action){
implicit val scheduler = Scheduler(executionModel=AlwaysAsyncExecution)
private val stream: BehaviorSubject[Action] = BehaviorSubject.apply(initialState)
def dispatch(s: Action): Unit = stream.onNext(s)
def cancel():Unit = stream.onComplete
def observer(f:Action => Unit, c: () => Unit)= new Observer[Action] {
def onNext(s: Action): Future[Ack] = {
f(s)
Continue
}
def onError(ex: Throwable): Unit = {
ex.printStackTrace()
}
def onComplete(): Unit = c()
}
def subscribe(doAction: Action=>Unit, onCancel:() => Unit): Cancelable =
stream.sample(Duration(10, MILLISECONDS))
.delayOnNext(Duration(500, MILLISECONDS))
.subscribe(observer(doAction, onCancel))
}
When applying the constructor of this class, the Subject is created as a private field private val stream: BehaviorSubject[Action]
. Subject is an Observable, so you can subscribe to it, providing an Observer. But Subject is also an Observer: it is an object with the methods onNext(v)
, onError(e)
, and onComplete()
. To feed a new value to the Subject, just call onNext(theValue)
.
The Dispatcher
instance has three very useful methods:
dispatch
– puts new value to the Observable
cancel
– completes the Observable
subscribe
– allows to subscribe to the Observable
Do you remember the dispatcher
function from my first synchronous example. My instance of Dispatcher
class do the same thing, but asynchronously.
The private field stream
of type BehaviourSubject
is a transport, that takes a value of type Action
and gives it to doAction
function.
See the code:
def subscribe(doAction: Action=>Unit, onCancel:() => Unit): Cancelable =
stream.sample(Duration(10, MILLISECONDS))
.delayOnNext(Duration(500, MILLISECONDS))
.subscribe(observer(doAction, onCancel))
As you can see our stream
is transformed before being consumed. The subscriber gets only one value from those emitted per ten milliseconds. And these values are transported to the subscriber with an interval of 500 milliseconds. That’s great.
But what about the other part of the problem: asynchronous calculation of our math task?
package async
import scala.annotation.tailrec
import scala.scalajs.js.JSApp
import org.scalajs.dom
import monix.eval.Task
import dom.{Element, document}
import org.scalajs.dom.raw.{Event, HTMLElement, HTMLInputElement}
import scala.util.{Failure, Success, Try}
import monix.execution.Scheduler.Implicits.global
object Primes extends JSApp {
private val calculate = document.getElementById("calculate")
private val stop = document.getElementById("stop")
private val target = document.getElementById("target")
private val numbers = document.getElementById("number")
private val onCancel = () => {
numbers.asInstanceOf[HTMLElement].style.display = ""
calculate.asInstanceOf[HTMLElement].style.display = ""
stop.asInstanceOf[HTMLElement].style.display = "none"
}
sealed trait Action
case class PrintResult(n: Vector[Long], t: Long) extends Action
case class Start(initial: Long) extends Action
case class CalculationState(var flag: Boolean)
case class State(
nextNumber: Long,
primes: Vector[Long],
target: Long,
isFinished: Boolean = false,
Error: Option[String] = None
)
private def makeString(n: Long): String = n.toString.reverse.grouped(3).reduceLeft { (x, s) =>
if (x.length >= 3) x + "," + s
else x
}.reverse
private def fillElement(target: Element)(text: String) = target.innerHTML = text
private val fillEl = fillElement(target)(_)
private def doAction(action: Action) = action match {
case Start(i) =>
fillEl("1 * " + makeString(i))
case PrintResult(n, t) =>
val s = if (t != 1) n.map(makeString).mkString("1 * ", " * ", " * " + makeString(t))
else n.map(makeString).mkString("final: ", " * ", " = " + makeString(n.product))
fillEl(s)
}
def main(): Unit = {
stop.asInstanceOf[HTMLElement].style.display = "none"
calculate.addEventListener("click",
(_: Event) => {
val number = Try(numbers.asInstanceOf[HTMLInputElement].value.toLong) match {
case Success(n) => n
case Failure(_) => 0
}
if (number > 1) {
stop.asInstanceOf[HTMLElement].style.display = ""
numbers.asInstanceOf[HTMLElement].style.display = "none"
calculate.asInstanceOf[HTMLElement].style.display = "none"
implicit val mutableState: CalculationState = CalculationState(false)
val initialState = State(1, Vector.empty[Long], number)
implicit val dispatcher: Dispatcher[Action] = new Dispatcher[Action](Start(initialState.target))
dispatcher.subscribe(doAction, onCancel)
val stopClickHandler: Event => Unit = (_: Event) => {
mutableState.flag = true
}
stop.addEventListener("click", (e: Event) => {
stopClickHandler(e)
stop.removeEventListener("click", stopClickHandler)
})
doCalculation(initialState).runAsync
} else {
target.innerHTML = "You must enter a number > 1"
}
}
)
}
@tailrec
private def doDivide(state: State)(implicit dispatcher: Dispatcher[Action]): State = {
state match {
case State(n, p, t, _, _) if t % n == 0 =>
val target = t / n
val primes = p :+ n
dispatcher.dispatch(PrintResult(primes, target))
doDivide(state.copy(primes = primes, target = target))
case _ => state
}
}
private def newState(s: State)(implicit dispatcher: Dispatcher[Action]): State =
s match {
case State(n, _, t, _, _) if t % n == 0 => doDivide(s)
case State(_, _, t, _, _) if t > 1 => s
case _ => s.copy(isFinished = true)
}
private def doCalculation(state: State)(implicit dispatcher: Dispatcher[Action],
flag: CalculationState): Task[Unit] = {
Task.eval(state) flatMap {
case _ if flag.flag => Task.now {
dispatcher.cancel()
}
case State(_, _, _, true, _) =>
Task.now {
dispatcher.cancel()
}
case State(_, _, _, _, Some(_)) =>
doCalculation(state.copy(isFinished = true))
case State(n, _, t, _, _) if n >= t => doCalculation(state.copy(isFinished = true))
case State(n, _, _, _, _) =>
doCalculation(newState(state.copy(nextNumber = n + 1)))
}
}
}
I use Monix Task type. Task is a data type for controlling possibly lazy & asynchronous computations. See what happened with a doCalculation
function:
private def doCalculation(state: State)(implicit dispatcher: Dispatcher[Action],
flag: CalculationState): Task[Unit] = {
Task.eval(state) flatMap {
case _ if flag.flag => Task.now {
dispatcher.cancel()
}
case State(_, _, _, true, _) =>
Task.now {
dispatcher.cancel()
}
case State(_, _, _, _, Some(_)) =>
doCalculation(state.copy(isFinished = true))
case State(n, _, t, _, _) if n >= t => doCalculation(state.copy(isFinished = true))
case State(n, _, _, _, _) =>
doCalculation(newState(state.copy(nextNumber = n + 1)))
}
}
It returns Task[Unit]
instead of Unit
. And it runs asynchronously. Since Task is lazy-evaluated, we doesn’t need tail recursion any more.
Now we can stop calculation by chanding the implicit value of flag: CalculationState
. flag
is an instance of:
case class CalculationState(var flag: Boolean)
If flag.flag
is false
, calculation continues. If it becomes true
, calculation stops.
Yes, I used mutable field here, because I didn’t find better solution. Strange, I wasn’t eaten by a dinosaur.
An now we can add an event handler for our “Stop” button to the main
class:
val stopClickHandler: Event => Unit = (_: Event) => {
mutableState.flag = true
}
stop.addEventListener("click", (e: Event) => {
stopClickHandler(e)
stop.removeEventListener("click", stopClickHandler)
})
This code is successfully compiled into minified JavaScript file that weighs 307kB. It’s bigger than in my first example. But it works fine:
An you can stop calculation, if you want:
If you look at the source, you will see plain Scala code, very clear and expressive. I didn’t use any JavaScript library. I just wrote 200 lines in Scala, and here is the result.
See how it works here.
See the code on GitHub.