Scala.js With Monix Example

Scala.js With Monix Example

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:

  1. The user can see an intermediate result just after he click the button.
  2. If user has decided that he has been waiting too long for a final result, he can stop calculation by clicking the “Stop” button.
  3. The browser will never stop responding.
  4. 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.

 

 

 

Please follow and like us:

About Alexandre Kremlianski

Scala / Scala.js / JavaScript programmer

Leave a Reply

Your email address will not be published. Required fields are marked *