In this article I want to give you a short intro to Scala.js à la Elm. So what is Elm? Elm is a language and framework which allows you to write web applications in a Haskell-ish style.
That reason why I would like to experiment with this style in Scala.js is simple: the Elm architecture is really elegant plus I like functional programming a lot. Scala.js allows us to share code between the server and the frontend. Apart from that there are more reasons why Scala.js is so interesting. Why not Elm? Well, it is not a full-stack solution. So you have to write a backend in Haskell or Node or whatever suits you best. Writing a backend and frontend in Scala and using strongly typed RPC (+ the same models) is quite nice to have!
Just to put up a disclaimer here: I have not built anything big with it. Just a few little experiments so far. So if you have any feedback, feel free to share!
The stack I’ll be using consists of several libraries for the time being. As this is is an experiment it is nice to get up to speed. This might change in the future of course.
Monifu
Monifu is a reactive programming library for Scala and Scala.Js. It is similar to Reactive Extensions (Rx) by Microsoft (.NET) and Netflix (Java). Monifu has a lot of stream combinators (folds, scan, filter, etc).
Scalaz
Powerful functional programming library. Has several abstractions which should be familiar to Haskell programmers, like Functors, Applicatives, Monads, Monoids, Validation, etc.
Scalajs-react
Awesome Facebook library for rendering views. We’ll use the Virtual-DOM part to re-render views on changes.
But how do we combine these libraries together to create a programming model like Elm has?
The Elm Architecture
Evan Czaplicki created a framework called Elm. In the tutorial “The Elm Architecture” (https://github.com/evancz/elm-architecture-tutorial), Evan explains how you should construct a web application using Elm.
So what do we need to write Scala.js à la Elm?
Combining multiple signals
Elm models input as signals. A signal is like an Observable[A]. A signal can be mapped, filtered, etc. Just like an Observable[A]. For more info about signals in Elm: http://elm-lang.org/learn/Using-Signals.elm
In Elm you can combine multiple signals to call a function each time a new value comes in. If you look at the documentation of Elm (http://package.elm-lang.org/packages/elm-lang/core/1.1.1/Signal) and tutorials you’ll see they are using map, map2, map3, etc or (~) and (<~) for that.
This is similar to what Applicative functors can do for you.
The question is: Can we create an Applicative functor for Monifu’s Observable[A]? Since Observable[A] already has a flatMap and point method, it’s quite obvious this is already a monad and therefore it implies there is also an Applicative functor for it.
If we derive the Applicative functor from the Monad, the observables will be processed in order: If we have two observables: Observable a and b, we’ll have to wait first to have some input from the first Observable (a). After that the second Observable (b) will be processed. We don’t want this, we want to combine the latest changes from the two observables like Elm does with map2 (map2 is for two signals as the name implies).
We can do that by implementing the Applicative by using Observable.combineLatest:
implicit val app = new Applicative[Observable] { def point[A](a: => A) =
Observable(a) def ap[A, B](as: => Observable[A])(fs: => Observable[(A) => B]) =
as.combineLatest(fs).map { case (a, f) => f(a) } }
So now we have applicative functors for Observable! What can we do with them? Well we can combine multiple Observables to call pure functions.
Views and Virtual-DOM
Views are pure functions. In our case some arguments go in and HTML should go out (since we are writing a webapp). For our views we are using Scalajs-react.
Scalajs-react has defined all HTML elements as Scala functions. So we can write HTML in Scala. However Scalajs-react does not immediately render these elements. It will output a ReactElement. A ReactElement can be passed to a method called React.render. This will render the changes into a DOM element by using a technique called ‘virtual-dom’. For example here’s a view powered by scalajs-react:
def view(size: (Int, Int), mouse: (Double, Double)) = div(
p(s”Window size; width: ${size._1}, height: ${size._2}”),
p(s”Mouse position; x: ${mouse._1}, y: ${mouse._2}”)
)
Creating input
It would be nice to render the view above. We need to define input for our view. Input are signals in Elm and our case it’s a Observable[A]. So how do we create an Observable[A]? This view will display the size of the window and the position of the mouse.
Window size
lazy val size: Observable[(Int,Int)] = {
val sub = BehaviorSubject(dom.innerWidth -> dom.innerHeight)
dom.onresize = (ev: UIEvent) => sub.onNext(dom.innerWidth -> dom.innerHeight)
sub.whileBusyDropEvents
}
This will create an Observable[(Int, Int)]. A subject is an Observable, but we can write values to it. So how do we do that? We’ll add a ‘callback’ function on Window.onresize (dom.onresize) and on each update we’ll invoke onNext.
Mouse position
lazy val position: Observable[(Double,Double)] = {
val sub = BehaviorSubject(0d -> 0d)
dom.onmousemove = (ev: MouseEvent) => sub.onNext(ev.pageX -> ev.pageY)
sub.whileBusyDropEvents
}
The code for the mouse position is quite similar. The only difference is that we don’t know the initial position of the mouse. So we just pass in zeroes.
Putting it together
So how do we put this together? Quite simple:
(size |@| position)(view)
The return type of this value will be an Observable[ReactTag]. To render these changes we need to create a subscriber which will render the patches (from React) to a DOM element by using the React.render function.
Dealing with state
What about state you say? I’ve composed a small example which shows how to work with state. Note that scan is quite familiar what Elm does with foldp.
Conclusion
As you can see it’s possible to write web applications in Scala ala Elm. We’ve covered signals/observables, views and state. What’s next? Well I would like to hear your feedback! I like writing and exploring applications this way, but are there potential pitfalls? Would it be nice to glue this in a framework?