Jekyll2016-12-01T23:39:59+01:00http://jandupal.com//Jan DupalWeb Workers in PureScript – part I2016-11-27T00:00:00+01:002016-11-27T00:00:00+01:00http://jandupal.com/Web%20Workers%20in%20PureScript%20-%20part%20I<p>This article <strong>introduces</strong> PureScript support for <em>Web Workers</em> I am currently working on. Any form of <strong>feedback</strong> on the approach or API design is more than welcome – <em>feel free to comment bellow or on <a href="https://github.com/JanDupal/purescript-web-workers">GitHub</a> commits</em>.<!--more--></p>
<ul id="markdown-toc">
<li><a href="#web-workers-introduction" id="markdown-toc-web-workers-introduction">Web Workers introduction</a> <ul>
<li><a href="#inter-thread-communication" id="markdown-toc-inter-thread-communication">Inter-thread communication</a></li>
<li><a href="#javascript-api" id="markdown-toc-javascript-api">JavaScript API</a></li>
</ul>
</li>
<li><a href="#web-workers-in-purescript" id="markdown-toc-web-workers-in-purescript">Web Workers in PureScript</a> <ul>
<li><a href="#purescript-api" id="markdown-toc-purescript-api">PureScript API</a> <ul>
<li><a href="#low-level-api" id="markdown-toc-low-level-api">Low-level API</a></li>
<li><a href="#channel-like-api" id="markdown-toc-channel-like-api">Channel-like API</a></li>
</ul>
</li>
<li><a href="#example" id="markdown-toc-example">Example</a> <ul>
<li><a href="#worker-code" id="markdown-toc-worker-code">Worker code</a></li>
<li><a href="#eliminating-per-worker-compilation" id="markdown-toc-eliminating-per-worker-compilation">Eliminating per-worker compilation</a></li>
<li><a href="#running-the-worker" id="markdown-toc-running-the-worker">Running the worker</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#summary" id="markdown-toc-summary">Summary</a></li>
</ul>
<h2 id="web-workers-introduction">Web Workers introduction</h2>
<p><em>Skip to section <a href="#web-workers-in-purescript">Web Workers in PureScript</a> if you know Web Workers JavaScript API.</em></p>
<p>Web Workers is a HTML5 technology that brings support of multithreading to JavaScript in browsers. It's core use case in web development is execution of long-running computational-heavy tasks, which would normally block the main thread, thus inconveniently <strong>freezing the user interface</strong>.</p>
<p>Web Workers allow to deal with blocking the main thread via offloading the blocking task to a standalone thread, leaving the main thread unblocked.</p>
<h3 id="inter-thread-communication">Inter-thread communication</h3>
<p>Usually, we need some sort of communication between main thread and worker to convey information back and forth – Web Workers don't involve the traditional <em>shared memory</em> approach for concurrency, but provide <strong>bidirectional messaging</strong> API for <em>shared nothing</em> approach.</p>
<p>Thanks to <em>shared nothing</em> architecture JavaScript doesn't have to deal with implications coming with shared memory – data races, synchronization etc.</p>
<h3 id="javascript-api">JavaScript API</h3>
<p>Complete API description can be found in MDN's <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers">Using Web Workers</a> article.</p>
<p>In summary the API provides means to:</p>
<ul>
<li>Start a worker by passing URL of the workers source (e.g. JS file) via <code class="highlighter-rouge">new Worker()</code>.</li>
<li>Send a message to & from worker via <code class="highlighter-rouge">postMessage()</code>.</li>
<li>Define callbacks for processing incoming message via <code class="highlighter-rouge">onmessage</code>.</li>
</ul>
<h2 id="web-workers-in-purescript">Web Workers in PureScript</h2>
<p>The JavaScript API <em>could</em> be used directly without any library via PureScript’s FFI. We would have to:</p>
<ol>
<li>define FFI mapping,</li>
<li>create a PureScript module defining <code class="highlighter-rouge">main</code> function for each worker with FFI calls,</li>
<li>compile all worker modules as standalone “binaries”, <em>one at a time</em>,</li>
<li>instantiate workers via FFI calls, passing name of the <em>compiled JS file in <code class="highlighter-rouge">String</code> as a parameter</em>.</li>
</ol>
<p>However, as you can imagine this approach is tiresome, error prone and namely the messaging API can be modeled in more PureScript-friendly way. Let's ask ourselves a question:</p>
<blockquote>
<p><strong>What can be done to achieve seamless adoption of Web Workers technology to PureScript?</strong></p>
<ol>
<li>Provide a functional API befitting PureScript’s ecosystem.</li>
<li>Eliminate need to compile a standalone JS file with <code class="highlighter-rouge">main</code> for each worker.</li>
<li>Allow to run any function as an anonymous Web Worker, like <a href="http://hackage.haskell.org/package/base-4.9.0.0/docs/Control-Concurrent.html#v:forkIO">Haskell’s <code class="highlighter-rouge">forkIO</code></a>.</li>
</ol>
</blockquote>
<p>What comes next is my proposal for this matter.</p>
<h3 id="purescript-api">PureScript API</h3>
<p>The overarching philosophy is:</p>
<blockquote>
<p>Keep <em>low-level API</em> close to the metal while allowing to build <em>higher abstraction</em> on top of it.</p>
</blockquote>
<h4 id="low-level-api">Low-level API</h4>
<p>A simple foreign function interface based on the <a href="https://pursuit.purescript.org/packages/purescript-eff/2.0.0"><code class="highlighter-rouge">Eff</code></a> monad located in namespace <code class="highlighter-rouge">Control.Monad.Eff.Worker</code>. The API consists of two similar sets of functions – one for the <strong>main thread</strong> and second for <strong>worker thread</strong>.</p>
<p><a href="https://github.com/JanDupal/purescript-web-workers/blob/245688e307316f640406c42a7d6d9b982ac26ab9/src/Control/Monad/Eff/Worker/Master.purs"><code class="highlighter-rouge">Control.Monad.Eff.Worker.Master</code></a> defines:</p>
<ul>
<li>Spawning a worker from the main thread with <code class="highlighter-rouge">startWorker</code>,</li>
<li>sending a message to the worker via <code class="highlighter-rouge">sendMessage</code>,</li>
<li>and defining callback for messages from worker with <code class="highlighter-rouge">onMessage</code>.</li>
</ul>
<p>And analogously <a href="https://github.com/JanDupal/purescript-web-workers/blob/245688e307316f640406c42a7d6d9b982ac26ab9/src/Control/Monad/Eff/Worker/Slave.purs"><code class="highlighter-rouge">Control.Monad.Eff.Worker.Slave</code></a> allows to:</p>
<ul>
<li>Send a message to the main thread via <code class="highlighter-rouge">sendMessage</code>,</li>
<li>and define callback for messages from main thread with <code class="highlighter-rouge">onMessage</code>.</li>
</ul>
<p>To add <em>type safety</em> to the native JS API, <a href="https://github.com/JanDupal/purescript-web-workers/blob/245688e307316f640406c42a7d6d9b982ac26ab9/src/Control/Monad/Eff/Worker.purs"><code class="highlighter-rouge">Control.Monad.Eff.Worker</code></a> introduces two types specifying types of incoming and outgoing messages:</p>
<ul>
<li><code class="highlighter-rouge">WorkerModule req res</code> represents a PureScript module, where the worker code is located</li>
<li><code class="highlighter-rouge">Worker req res</code> is an instance of underlying JavaScript Web Worker</li>
</ul>
<p>Type parameters <code class="highlighter-rouge">req</code> and <code class="highlighter-rouge">res</code> are types of <em>request</em> (sent by main thread) and <em>response</em> (sent by worker thread) messages.</p>
<p>Role of both types will be demonstrated in the <a href="#example">example</a> bellow.</p>
<h4 id="channel-like-api">Channel-like API</h4>
<p>The low-level API in the previous section is suitable for simple worker tasks, where no complex communication is required. However for more complex use cases, it might be a bit clumsy to use. Asynchronous API based on <a href="https://pursuit.purescript.org/packages/purescript-aff/">Aff monad</a> and <a href="https://pursuit.purescript.org/packages/purescript-aff/2.0.1/docs/Control.Monad.Aff.AVar">AVar</a> primitive may be more useful in such cases.</p>
<p>The basic concept behind this approach is that both <strong>incoming and outgoing messages are queued</strong> in two <code class="highlighter-rouge">AVar</code> primitives – one for each direction.</p>
<p>The <code class="highlighter-rouge">Aff</code> monad allows to access message queues via <code class="highlighter-rouge">putVar</code> and <code class="highlighter-rouge">takeVar</code> functions. Although the underlying operation is asynchronous, the code looks like an ordinary, imperative, synchronous code:</p>
<div class="language-haskell highlighter-rouge"><pre class="highlight"><code><span class="n">main</span> <span class="o">=</span> <span class="n">launchAff</span> <span class="o">$</span> <span class="kr">do</span>
<span class="c1">-- AVar setup omitted</span>
<span class="n">putVar</span> <span class="n">requestQueue</span> <span class="s">"42"</span> <span class="c1">-- add message "42" to request queue</span>
<span class="n">response</span> <span class="o"><-</span> <span class="n">takeVar</span> <span class="n">responseQueue</span> <span class="c1">-- await message on response queue</span>
<span class="n">liftEff</span> <span class="o">$</span> <span class="n">log</span> <span class="n">response</span>
</code></pre>
</div>
<p>However, before we can queue and dequeue messages we have to <strong>create queues bound to the worker</strong> – function <code class="highlighter-rouge">makeChan</code> from <a href="https://github.com/JanDupal/purescript-web-workers/blob/5c0c25ebaaf25818b6ae8ecbc567a690a477658c/src/Control/Monad/Aff/Worker/Master.purs"><code class="highlighter-rouge">Control.Monad.Aff.Worker.Master</code></a> or <a href="https://github.com/JanDupal/purescript-web-workers/blob/5c0c25ebaaf25818b6ae8ecbc567a690a477658c/src/Control/Monad/Aff/Worker/Slave.purs"><code class="highlighter-rouge">Control.Monad.Aff.Worker.Slave</code></a>, depending on wether applied in main or worker thread context, is available.</p>
<p>Both flavours return a tuple, which contains two AVars representing <em>request</em> and <em>response</em> queues. We'll see usage in the example bellow.</p>
<h3 id="example">Example</h3>
<p>The following example demonstrates the proposed API and dealing with worker code separation as required by JavaScript Web Worker API.</p>
<p>The goal is to create a simple echo worker that resends every incoming message back.</p>
<h4 id="worker-code">Worker code</h4>
<p>Let’s start with defining the echo worker. Basically we need to wait for the incoming message, process it and return back.</p>
<p>To achieve the goal we'll use the <a href="#channel-like-api">Channel-like API</a> described in the previous section.</p>
<p>First we'll create <code class="highlighter-rouge">Echo.purs</code> module housing the worker thread logic:</p>
<div class="language-haskell highlighter-rouge"><pre class="highlight"><code><span class="kr">module</span> <span class="nn">Echo</span> <span class="kr">where</span>
<span class="c1">-- imports redacted</span>
<span class="kr">import</span> <span class="nn">Control.Monad.Aff.AVar</span> <span class="p">(</span><span class="nf">putVar</span><span class="p">,</span> <span class="nf">takeVar</span><span class="p">,</span> <span class="kt">AVAR</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Control.Monad.Aff.Worker.Slave</span> <span class="p">(</span><span class="nf">makeChan</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Control.Monad.Eff.Worker</span> <span class="p">(</span><span class="kt">WORKER</span><span class="p">,</span> <span class="kt">WorkerModule</span><span class="p">)</span>
<span class="n">echo</span> <span class="o">::</span> <span class="n">forall</span> <span class="n">e</span><span class="o">.</span> <span class="kt">Aff</span> <span class="p">(</span><span class="n">avar</span> <span class="o">::</span> <span class="kt">AVAR</span><span class="p">,</span> <span class="n">console</span> <span class="o">::</span> <span class="kt">CONSOLE</span><span class="p">,</span> <span class="n">worker</span> <span class="o">::</span> <span class="kt">WORKER</span> <span class="o">|</span> <span class="n">e</span><span class="p">)</span> <span class="kt">Unit</span>
<span class="n">echo</span> <span class="o">=</span> <span class="kr">do</span>
<span class="kt">Tuple</span> <span class="n">req</span> <span class="n">res</span> <span class="o"><-</span> <span class="n">makeChan</span> <span class="n">workerModule</span> <span class="c1">-- create worker-bound AVar queues</span>
<span class="n">forever</span> <span class="o">$</span> <span class="n">void</span> <span class="o">$</span> <span class="kr">do</span>
<span class="n">message</span> <span class="o"><-</span> <span class="n">takeVar</span> <span class="n">req</span> <span class="c1">-- await message on request queue</span>
<span class="n">liftEff</span> <span class="o">$</span> <span class="n">log</span> <span class="o">$</span> <span class="s">"Worker received: "</span> <span class="o"><></span> <span class="n">message</span>
<span class="n">putVar</span> <span class="n">res</span> <span class="n">message</span> <span class="c1">-- send the message back</span>
<span class="kr">default</span> <span class="o">::</span> <span class="n">forall</span> <span class="n">e</span><span class="o">.</span> <span class="kt">Eff</span> <span class="p">(</span><span class="n">avar</span> <span class="o">::</span> <span class="kt">AVAR</span><span class="p">,</span> <span class="n">console</span> <span class="o">::</span> <span class="kt">CONSOLE</span><span class="p">,</span> <span class="n">err</span> <span class="o">::</span> <span class="kt">EXCEPTION</span><span class="p">,</span> <span class="n">worker</span> <span class="o">::</span> <span class="kt">WORKER</span> <span class="o">|</span> <span class="n">e</span><span class="p">)</span> <span class="kt">Unit</span>
<span class="kr">default</span> <span class="o">=</span> <span class="n">void</span> <span class="o">$</span> <span class="n">launchAff</span> <span class="n">echo</span>
</code></pre>
</div>
<p>In <code class="highlighter-rouge">echo</code> function we create queues via <code class="highlighter-rouge">makeChan</code>. And then loop the following asynchronous computation: await for an incoming request from the main thread via <code class="highlighter-rouge">takeVar</code>, log the message and send the message back by queueing it to the response queue by <code class="highlighter-rouge">putVar</code>.</p>
<p>The last function, <code class="highlighter-rouge">default</code>, is an equivalent of <code class="highlighter-rouge">main</code> function. But in this case it's the function that is executed when the worker thread starts. It takes care of launching the <code class="highlighter-rouge">echo</code> handler.</p>
<h4 id="eliminating-per-worker-compilation">Eliminating per-worker compilation</h4>
<p>Compiling each worker module separately, e.g. via <code class="highlighter-rouge">pulp browserify -m Worker --to dist/Worker.js</code>, to get a standalone JavaScript file that can be later fed to constructor <code class="highlighter-rouge">new Worker(url)</code>, is somewhat tedious approach.</p>
<p>Fortunately, <a href="https://github.com/substack/webworkify">webworkify</a> in combination with <em>browserify</em> allows any module to be used as a worker’s source. The only task that is left to the programmer is to explicitly call <code class="highlighter-rouge">require()</code> with hardcoded name of module, which contains the worker definition. Thanks to the <em>require</em> call, <em>webworkify</em> post-processing will do the rest.</p>
<p>Let's add a foreign module to our Echo module, <code class="highlighter-rouge">Echo.js</code>:</p>
<div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="nx">exports</span><span class="p">.</span><span class="nx">workerModule</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">"Echo"</span><span class="p">);</span>
</code></pre>
</div>
<p>And use FFI to introduce it in <code class="highlighter-rouge">Echo.purs</code>:</p>
<div class="language-haskell highlighter-rouge"><pre class="highlight"><code><span class="kr">type</span> <span class="kt">Request</span> <span class="o">=</span> <span class="kt">String</span>
<span class="kr">type</span> <span class="kt">Response</span> <span class="o">=</span> <span class="kt">String</span>
<span class="n">foreign</span> <span class="kr">import</span> <span class="nn">workerModule</span> <span class="o">::</span> <span class="kt">WorkerModule</span> <span class="kt">Request</span> <span class="kt">Response</span>
</code></pre>
</div>
<p>Apart from exposing foreign <code class="highlighter-rouge">workerModule</code>, this line also defines types of incoming and outgoing message types – named <code class="highlighter-rouge">Request</code> and <code class="highlighter-rouge">Response</code> for unambiguous meaning when reasoning either from perspective of main thread or worker thread.</p>
<h4 id="running-the-worker">Running the worker</h4>
<p>With worker logic defined, we’ll take a look at spawning a worker and sending a message:</p>
<div class="language-haskell highlighter-rouge"><pre class="highlight"><code><span class="kr">module</span> <span class="nn">Main</span> <span class="kr">where</span>
<span class="c1">-- imports redacted</span>
<span class="kr">import</span> <span class="nn">Control.Monad.Aff.AVar</span> <span class="p">(</span><span class="nf">putVar</span><span class="p">,</span> <span class="nf">takeVar</span><span class="p">,</span> <span class="kt">AVAR</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Control.Monad.Aff.Worker.Master</span> <span class="p">(</span><span class="nf">makeChan</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Control.Monad.Eff.Worker</span> <span class="p">(</span><span class="kt">Worker</span><span class="p">,</span> <span class="kt">WORKER</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Control.Monad.Eff.Worker.Master</span> <span class="p">(</span><span class="nf">startWorker</span><span class="p">)</span>
<span class="kr">import</span> <span class="nn">Echo</span> <span class="p">(</span><span class="kt">Request</span><span class="p">,</span> <span class="kt">Response</span><span class="p">,</span> <span class="nf">workerModule</span><span class="p">)</span>
<span class="n">ping</span> <span class="o">::</span> <span class="n">forall</span> <span class="n">e</span><span class="o">.</span> <span class="kt">Worker</span> <span class="kt">Request</span> <span class="kt">Response</span> <span class="o">-></span> <span class="kt">Request</span> <span class="o">-></span> <span class="kt">Aff</span> <span class="p">(</span><span class="n">avar</span> <span class="o">::</span> <span class="kt">AVAR</span><span class="p">,</span> <span class="n">console</span> <span class="o">::</span> <span class="kt">CONSOLE</span><span class="p">,</span> <span class="n">worker</span> <span class="o">::</span> <span class="kt">WORKER</span> <span class="o">|</span> <span class="n">e</span><span class="p">)</span> <span class="kt">Unit</span>
<span class="n">ping</span> <span class="n">w</span> <span class="n">message</span> <span class="o">=</span> <span class="kr">do</span>
<span class="kt">Tuple</span> <span class="n">req</span> <span class="n">res</span> <span class="o"><-</span> <span class="n">makeChan</span> <span class="n">w</span>
<span class="n">forkAff</span> <span class="o">$</span> <span class="n">forever</span> <span class="kr">do</span>
<span class="n">response</span> <span class="o"><-</span> <span class="n">takeVar</span> <span class="n">res</span>
<span class="n">liftEff</span> <span class="o">$</span> <span class="n">log</span> <span class="o">$</span> <span class="s">"Worker returned: "</span> <span class="o"><></span> <span class="n">response</span>
<span class="n">putVar</span> <span class="n">req</span> <span class="n">message</span>
<span class="n">main</span> <span class="o">::</span> <span class="n">forall</span> <span class="n">e</span><span class="o">.</span> <span class="kt">Eff</span> <span class="p">(</span><span class="n">avar</span> <span class="o">::</span> <span class="kt">AVAR</span><span class="p">,</span> <span class="n">console</span> <span class="o">::</span> <span class="kt">CONSOLE</span><span class="p">,</span> <span class="n">err</span> <span class="o">::</span> <span class="kt">EXCEPTION</span><span class="p">,</span> <span class="n">worker</span> <span class="o">::</span> <span class="kt">WORKER</span> <span class="o">|</span> <span class="n">e</span><span class="p">)</span> <span class="kt">Unit</span>
<span class="n">main</span> <span class="o">=</span> <span class="n">void</span> <span class="o">$</span> <span class="kr">do</span>
<span class="n">worker</span> <span class="o"><-</span> <span class="n">startWorker</span> <span class="n">workerModule</span>
<span class="n">void</span> <span class="o">$</span> <span class="n">launchAff</span> <span class="o">$</span> <span class="n">ping</span> <span class="n">worker</span> <span class="s">"foobar"</span>
</code></pre>
</div>
<p>First we instantiate request and response queues by <code class="highlighter-rouge">makeChan</code>, but this time from the main thread context. Then we fork an asynchronous computation, that awaits responses from worker via <code class="highlighter-rouge">takeVar</code>. Finally we send the initial message to the worker via <code class="highlighter-rouge">putVar</code>.</p>
<p>Without <code class="highlighter-rouge">forkAff</code>, the message would never be sent – <code class="highlighter-rouge">forever</code> would block for, well, forever.</p>
<p>Again there's <code class="highlighter-rouge">main</code> function, which is responsible for starting up JavaScript worker and proceeding with our <code class="highlighter-rouge">ping</code> computation.</p>
<p>When we compile the example and open it in a browser, we'll see the following log in console:</p>
<div class="highlighter-rouge"><pre class="highlight"><code>Worker received: foobar
Worker returned: foobar
</code></pre>
</div>
<h1 id="summary">Summary</h1>
<p>The source code can be found on GitHub: <a href="https://github.com/JanDupal/purescript-web-workers">JanDupal/purescript-web-workers</a>. There's still a lot of aspects to cover:</p>
<ul>
<li>Polishing the API</li>
<li>Publish the library on</li>
<li>Documentation and test coverage</li>
<li><em>forkIO</em>-like seamless integration to allow almost any function to be executed as a worker – requires changes in the PureScript compiler</li>
</ul>
<p>And more as tracked on <a href="https://github.com/JanDupal/purescript-web-workers/projects/1">project's board</a>.</p>
<p>Stay tuned for more. <strong>Any feedback appreciated!</strong></p>JanDupalThis article introduces PureScript support for Web Workers I am currently working on. Any form of feedback on the approach or API design is more than welcome – feel free to comment bellow or on GitHub commits.