Elm with Web Workers

Pablo Fernández Franco
3 min readOct 30, 2017

--

For a while, I’ve been thinking about starting to write AI algorithms in Elm lang. So I’ve started researching about the Web Workers.

There are no plans to add support for Web Workers in the Elm compiler as we can see here.

Actual state

I haven’t found many examples about this topic on the internet. There are several interesting approaches and in this post I will propuse another.

The proof-of-concept by Richard Feldman

The author of “Elm in Action” (a nice book, by the way) implements an interesting supervisor/worker pattern.

I think it’s a great idea, but too complicated if you want to do something simple.

You can learn a lot by reading his code here.

Web Worker renderer in Elm

There are a series of videos (only two) about an experiment to run Elm’s Model and Update in a Web Worker. It’s very interesting:

Again this experiment is too complex for most use cases and I don’t recommend it outside your localhost because you’ll have to modify the code generated by elm-make.

My approach

My proposal requires dividing the elm app into 2 apps:

  • The main app.
  • The brain app .

The first one will be our normal elm app, we’ll have the UI logic here. The brain app will have the expensive process we want to have in a worker.

Finally we’ll connect these applications through Javascript ports. Just like this:

Example adder

I have made example to explain this architecture, you can see all the code here.

This small program allows adding two numbers given by the user. The adding process will be in a worker.

Main and Worker

These components will have the Javascript code.

The main.js is the entry point of this app. Here we’ll handle the Main.elm compiled and its ports and we’ll begin communication with the worker:

let BrainWorker = require("worker-loader!./worker.js");
let Main = require("./source/Main.elm").Main;
let mountNode = document.getElementById("app");

let app = Main.embed(mountNode);
let brainWorker = new BrainWorker();

//send data from Main elm program to the worker
app.ports.sendData.subscribe(function (data) {
brainWorker.postMessage(data);
});

//receive data from worker and pass it on to Main elm program
brainWorker.onmessage = function (event) {
app.ports.receiveResult.send(event.data);
};

In another hand we handle the Brain.elm stuff and the responses with main.js through the web workers API in worker.js:

let Calc        = require("./source/Calc.elm").Calc;
let brain = Calc.worker();


//receive data from main.js and pass it on to Calc elm program
self.addEventListener('message', function (event) {
brain.ports.receiveInput.send(event.data);
});

//send data from Calc elm program to the main.js
brain.ports.sendResult.subscribe(function (result) {
self.postMessage(result);
});

Platform.program

In Brain.elm we have not a view, because it’s a headless program, so we cannot use Html.program. Luckily, the Elm’s guys offer us a way to create a program without a view:

port module Calc exposing (..)

import Platform exposing (..)
-- More stuff heremain : Program Never {} Msg
main =
Platform.program
{ init = init
, update = update
, subscriptions = subscriptions
}

Conclusion

In my opinion this is a good and simple approach and it’s as recommend for production as Elm programs with native code are.

Do you know a better way or can you improve my example? let me know.

I hope you find it useful and thank you very much for reading!

--

--