webbureaucratThe articles are just window-dressing for code snippets I want to keep.2023-09-25T00:00:00Zhttps://webbureaucrat.gitlab.io/Eleanoreleanor.webbureaucrat@mailmasker.comReasonML Journey Part I: Getting Started with BuckleScript2020-06-10T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/reasonml-journey-part-i-getting-started-with-bucklescript/<p>ReasonML is, at the time of this post, a very young language and, as such, very
much underdocumented. It's also missing a lot of the JavaScript standard
libraries. I see both of these things as an opportunity to contribute both
to the foundational documentation and to the libraries that haven't been
written yet. This blog series will serve mainly as a resource to me as an
absolute beginner trying to retrace my steps, but I hope that someone else
may someday find it useful as well.</p>
<p>This post in particular will cover the first steps of initiating an npm package
module and a ReasonML project in BuckleScript. The purpose of this post is not
as much instructional as it is contextual, providing a way to recreate the
precise environment in which the code was written.</p>
<h3>Prerequisites</h3>
<ul>
<li>You know basic terminal commands like <code>ls</code> and <code>cd</code>.</li>
<li>npm is installed.</li>
<li>git is installed, and you know how to create and clone a repository.</li>
</ul>
<h3>Setting up your BuckleScript workbench</h3>
<p>Whenever I can, I avoid installing npm packages globally. I prefer to have each
project maintain its own dependencies that can be updated independently
without affecting one another, so I will be setting up my environment in a
very specific way.</p>
<ol start="10">
<li><strong>In your workspace, create a new directory</strong> for this and all future
BuckleScript projects. For example, I created <em>~/workbench/bucklescript/</em>.</li>
<li><strong>Enter that directory in a terminal.</strong></li>
<li><strong>Run <code>npm init -y</code> to initialize your workspace.</strong> The <code>-y</code> tells npm to
adopt all defaults in the npm <em>package.json</em>, which is what we want because
this is not the project directory. Instead, this is the directory where we're
going to create project directories.</li>
<li><strong>Run <code>npm install bs-platform</code></strong> to install the BuckleScript platform
locally.</li>
</ol>
<p>Now the BuckleScript platform can create new BuckleScript projects in your
directory. In the next section, we will be creating a new ReasonML project.</p>
<h3>Creating a new project</h3>
<ol start="10">
<li><strong>Choose a name for your project.</strong> I have decided to name my project
<em>bs-service-worker</em> because my ultimate goal will be to write a service worker
library for BuckleScript, the development environment for ReasonML and OCAML.</li>
<li><strong>Run <code>npx bsb -init $projectName -theme basic-reason</code></strong> to create your
new project directory.</li>
<li><strong>Enter this new project directory.</strong></li>
<li><strong>Install the bucklescript platform in this directory</strong>, which is already
an npm package, by running <code>npm install bs-platform</code>. (Recall that it was only
installed locally in the parent folder, so we do need to install it again here
to use the BuckleScript platform.)</li>
<li><strong>Open your <em>package.json</em>.</strong></li>
<li><strong>Edit each member of the scripts property by prefixing each command with
"npx".</strong> For example, "build" correspond to "npx bsb -make-world" instead of
"bsb -make-world. Do this for each command to reflect that we've installed
BuckleScript only locally.</li>
<li><strong>Edit other project metadata in <em>package.json</em></strong> as you see fit. I
like to set:</li>
</ol>
<ul>
<li>my version to 0.0.1.</li>
<li>my keywords to accepted Redex tags.</li>
<li>the author to include my first name and my blog.</li>
<li>the license to something open.</li>
</ul>
<ol start="80">
<li><strong>Close your <em>package.json</em>.</strong></li>
<li>Test your new project's setup by <strong>running <code>npm run build</code>.</strong></li>
<li>Optionally, initialize a git repository and add a remote <em>origin</em>, but be
careful not to automatically create a <em><a href="http://readme.md/">README.md</a></em> when creating your remote
repository or you will have merge conflicts.</li>
</ol>
<h3>Conclusion</h3>
<p>If your build command ran successfully, you have a new ReasonML project. In
the next section, we will write some code for it.</p>
ReasonML Journey Part II: Subtyping and ExtendableEvent2020-06-11T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/reasonml-journey-part-ii-subtyping-and-extendableevent/<p>The first stop on my quest to wrap a JavaScript library in ReasonML is a small,
simple JavaScript interface
<a href="https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent">ExtendableEvent</a>,
a class with no properties and only one method on top of what it inherits from
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Event">Event</a>.
In this brief blog post I will set out a simple template for subtyping in
ReasonML.</p>
<h3>Source as a learning resource</h3>
<p>There are not many BuckleScript tutorials out there, so in addition to the
official documents, I've found that one of the best ways to learn ReasonML is
by reading the
<a href="https://github.com/reasonml-community/">ReasonML community libraries</a>.
The <a href="https://github.com/reasonml-community/bs-webapi-incubator/blob/master/README.md">bs-webapi-incubator</a>,
for example, includes a helpful explanation of subtyping and inheritance in
ReasonML. It gives the following example:</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">type</span> _element<span class="token punctuation">(</span><span class="token type-variable function">'a</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">type</span> element_like<span class="token punctuation">(</span><span class="token type-variable function">'a</span><span class="token punctuation">)</span> <span class="token operator">=</span> node_like<span class="token punctuation">(</span>_element<span class="token punctuation">(</span><span class="token type-variable function">'a</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">type</span> element <span class="token operator">=</span> element_like<span class="token punctuation">(</span>_baseClass<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Candidly, while I think I pretty much understand what's going on here, I do
not understand it well enough to explain it, so that will be left as an
exercise for the user or for a future blog post after I get my bearings. For
now, I'm satisfied to know that this code makes a type <code>element</code> that's a
subtype of <code>node</code>, or rather, it's close cousin <code>node_like</code> which takes a type
parameter.</p>
<p>This code snippet is an example of <em>source code</em> from the standard library
itself, not a project that references the standard library, so to make this
code compile, we need to reference the Dom module in the standard library like
so:</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">type</span> _element<span class="token punctuation">(</span><span class="token type-variable function">'a</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">type</span> element_like<span class="token punctuation">(</span><span class="token type-variable function">'a</span><span class="token punctuation">)</span> <span class="token operator">=</span> Dom<span class="token punctuation">.</span>node_like<span class="token punctuation">(</span>_element<span class="token punctuation">(</span><span class="token type-variable function">'a</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">type</span> element <span class="token operator">=</span> element_like<span class="token punctuation">(</span>Dom<span class="token punctuation">.</span>_baseClass<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>This code snippent will compile, and we're now well on our way to recreating
ReasonML's element type.</p>
<p>But that's not what we're here for. We're here to write create
<code>ExtendableEvent</code> from <code>Event</code>, and even though I only kind of understand this
code, I can pretty copy this format for my use case:</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">type</span> _extendableEvent<span class="token punctuation">(</span><span class="token type-variable function">'a</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">type</span> extendableEvent_like<span class="token punctuation">(</span><span class="token type-variable function">'a</span><span class="token punctuation">)</span> <span class="token operator">=</span> Dom<span class="token punctuation">.</span>event_like<span class="token punctuation">(</span>_extendableEvent<span class="token punctuation">(</span><span class="token type-variable function">'a</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">type</span> extendableEvent <span class="token operator">=</span> extendableEvent_like<span class="token punctuation">(</span>Dom<span class="token punctuation">.</span>_baseClass<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Now all I have to do is paste this into a <em>.re</em> file in <em>src/</em> and run <code>npm run build</code>, and I have the first step done. You'll have to <code>waitUntil</code> the next
blog post to see how to extend this subtype to include a new method.</p>
ReasonML Journey Part III: Generics, Promises, and ExtendableEvent.waitUntil()2020-06-12T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/reasonml-journey-part-iii-generics-promises-extendableevent/<p>This post continues the quest of trying to recreate the JavaScript service
worker class
<a href="https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent">ExtendableEvent</a> in ReasonML. In this post I will show how to extend the subtype of <code>event</code>
to include <code>ExtendableEvent</code>'s <a href="https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent/waitUntil">waitUntil</a> method, which takes a generic
Promise as a parameter.</p>
<p>The <a href="https://bucklescript.github.io/docs/en/function">BuckleScript official documentation</a>
gives us a good starting point for defining functions that reference JavaScript
object methods, as shown below:</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span> <span class="token keyword">external</span> getElementById<span class="token punctuation">:</span> <span class="token punctuation">(</span>document<span class="token punctuation">,</span> string<span class="token punctuation">)</span> <span class="token operator">=></span> Dom<span class="token punctuation">.</span>element <br /> <span class="token operator">=</span> <span class="token string">"getElementById"</span><span class="token punctuation">;</span></code></pre>
<p>In this example, <code>[@bs.send] external</code> tells BuckleScript to call out
to JavaScript,
trusting that there is such a function as the string <code>"getElementById"</code>. The
rest of the statement provides the types we will use to interact with it. We
need to adapt this to take a type parameter for our promise.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span> <span class="token keyword">external</span> waitUntil<span class="token punctuation">:</span> <span class="token punctuation">(</span>extendableEvent<span class="token punctuation">,</span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span><span class="token type-variable function">'a</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">=></span> unit<br /> <span class="token operator">=</span> <span class="token string">"waitUntil"</span><span class="token punctuation">;</span></code></pre>
<p>This gives us access to the JavaScript function <code>"waitUntil"</code>, which takes our
extendableEvent as a first "parameter" (in this context) and a strongly typed
promise, returning <code>unit</code>, which, in functional languages, is like <code>void</code>.</p>
<p>This doesn't quite do what we need though--we need this method of
<code>ExtendableEvent</code> to be heritable. We can do this by using our supertype.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span> <br /><span class="token keyword">external</span> waitUntil<span class="token punctuation">:</span> <span class="token punctuation">(</span>extendableEvent_like<span class="token punctuation">(</span><span class="token type-variable function">'subtype</span><span class="token punctuation">)</span><span class="token punctuation">,</span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span><span class="token type-variable function">'a</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">=></span> unit<br /> <span class="token operator">=</span> <span class="token string">"waitUntil"</span><span class="token punctuation">;</span></code></pre>
<p>This allows <code>waitUntil</code> to accept any subtype of <code>extendableEvent_like</code>, not
just <code>extendableEvent</code> itself.</p>
<p>And we're done for the day. In the next post, I'll work on the npm side of
things so that we can use our new type elsewhere.</p>
ReasonML Journey Part IV: Publishing BuckleScript Packages on NPM2020-06-16T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/reasonml-journey-part-iv-publishing-bucklescript-packages-on-npm/<p>In the previous post, we finished wrapping
<a href="https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent">ExtendableEvent</a> in ReasonML. In this post I will publish our type on npm.</p>
<h3>bsconfig.json</h3>
<p>I will start by making two further changes in the <em>bsconfig.json</em>. First, I
want to set the "version" to "0.0.1" to best reflect the state of the project.
More importantly, I want to change the "namespace" attribute toward the bottom
of the file. At the time of this post, <code>npx bsb -init $projectName theme basic-reason</code> produces a default namespace of <code>true</code>. This results in a default
namespace to match the "name", so in my case, it would prefix my module
with the namespace of <code>BsServiceWorker</code>.</p>
<p>The resulting <em>bsconfig.json</em> follows:</p>
<pre class="language-json"><code class="language-json"><span class="token punctuation">{</span><br /> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"bs-service-worker"</span><span class="token punctuation">,</span><br /> <span class="token property">"version"</span><span class="token operator">:</span> <span class="token string">"0.0.1"</span><span class="token punctuation">,</span><br /> <span class="token property">"sources"</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token property">"dir"</span> <span class="token operator">:</span> <span class="token string">"src"</span><span class="token punctuation">,</span><br /> <span class="token property">"subdirs"</span> <span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token property">"public"</span><span class="token operator">:</span> <span class="token string">"all"</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token property">"package-specs"</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token property">"module"</span><span class="token operator">:</span> <span class="token string">"commonjs"</span><span class="token punctuation">,</span><br /> <span class="token property">"in-source"</span><span class="token operator">:</span> <span class="token boolean">true</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token property">"suffix"</span><span class="token operator">:</span> <span class="token string">".bs.js"</span><span class="token punctuation">,</span><br /> <span class="token property">"bs-dependencies"</span><span class="token operator">:</span> <span class="token punctuation">[</span><br /> <span class="token punctuation">]</span><span class="token punctuation">,</span><br /> <span class="token property">"warnings"</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token property">"error"</span> <span class="token operator">:</span> <span class="token string">"+101"</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token property">"namespace"</span><span class="token operator">:</span> <span class="token string">"ServiceWorker"</span><span class="token punctuation">,</span><br /> <span class="token property">"refmt"</span><span class="token operator">:</span> <span class="token number">3</span><br /><span class="token punctuation">}</span></code></pre>
<h3>Writing a good <em><a href="http://readme.md/">README.md</a></em></h3>
<p>The official redex documentation has some good
<a href="https://redex.github.io/publish">recommendations</a> for what should go into a
<em><a href="http://readme.md/">README.md</a></em>. For now, I'm going to forgo a changelog in favor of a Todo list.
A changelog will be more useful after I hit a major version. I'm also going to
put off usage examples because I don't have enough written to effectively use
yet. The rest of the suggestions are very applicable and I have incorporated
them into a good starter <em><a href="http://readme.md/">README.md</a></em>.</p>
<pre class="language-md"><code class="language-md"><span class="token title important"><span class="token punctuation">#</span> bs-service-worker</span><br /><br />This package will <span class="token italic"><span class="token punctuation">*</span><span class="token content">eventually</span><span class="token punctuation">*</span></span> be the home of BuckleScript bindings for the<br /><span class="token url">[<span class="token content">JavaScript service worker API</span>](<span class="token url">https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API</span>)</span>. <br />It is currently in a very early stage of development.<br /><br /><span class="token title important"><span class="token punctuation">##</span> Installation</span><br /><span class="token code-snippet code keyword">`npm install bs-service-worker`</span><br /><br /><span class="token title important"><span class="token punctuation">##</span> Implemented</span><br /><span class="token list punctuation">-</span> <span class="token url">[<span class="token content">x</span>] [<span class="token variable">ExtendableEvent</span>]</span>(https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent)</code></pre>
<h3>Testing Locally</h3>
<p>The official npm documentation
<a href="https://docs.npmjs.com/creating-node-js-modules">recommends</a> testing your
package locally. Translating those steps to the BuckleScript ecosystem
looks like:</p>
<ol start="10">
<li><strong>Initialize a new ReasonML project.</strong> For a description of my own setup,
see <a href="https://webbureaucrat.gitlab.io/TODO">part one of this series</a>.</li>
<li><strong>Install your package from local</strong> using the relative path. For example,
I will run <code>npm install ../bs-service-worker</code>.</li>
<li><strong>Add your package by name to the bs-dependencies</strong> of your new project.
For example, I will add "bs-service-worker".</li>
<li><strong>Reference your package using the namespace of your project</strong> in a source
file. For example, in <em>src/Demo.re</em>, I will add <code>open ServiceWorker;</code>.</li>
</ol>
<p>If your project builds and your using code runs as expected, you may be ready
to publish using <code>npm login</code> and <code>npm publish</code>.</p>
<p>When I'm a little further along, I'll also want to publish my npm registery
information to <a href="https://npmjs.com/package/package-name">the redex</a>, but it
would be misleading at this point to advertise my package that only wraps one
type.</p>
<h3>Thanks for riding along with me!</h3>
<p>This concludes my four-part series on setting up a BuckleScript library in
Reason. I'll still have plenty to write about ReasonML
going forward as I build out my binding, and as a beginner, I'm glad I don't
have to be worried about not having the ability to publish my work. Turns out,
it's pretty simple.</p>
Binding FetchEvent Using Properties and Constructors2020-06-17T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/binding-fetchevent-using-properties-and-constructors/<p>Having successfully bound
<a href="https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent">ExtendableEvent</a>, I can now work on inheriting this interface for
<a href="https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent">FetchEvent</a>.
Along the way, I'll install <a href="https://www.npmjs.com/package/bs-fetch">bs-fetch</a>
as a BuckleScript dependency and bind to JavaScript properties.</p>
<h3>Installing bs-fetch</h3>
<p>I've already bound
<a href="https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent">ExtendableEvent</a>,
but according to the
<a href="https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent">FetchEvent</a>
documentation, I need a couple more types before I can bind FetchEvent. The
property
<a href="https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/preloadResponse">preloadResponse</a>
depends on the
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Response">Response</a> type,
and the property
<a href="https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/request">request</a>
depends on the
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Request">Request</a> type. These
types are out of scope for me because I'm trying to implement the
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Worker Api</a>, whereas <code>Request</code> and <code>Response</code> are a part of the
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">Fetch API</a>.
Fortunately for me, there's already a binding for the Fetch API in
<a href="https://www.npmjs.com/package/bs-fetch">bs-fetch</a>. Installation is very
simple.</p>
<ol start="10">
<li><strong>Run <code>npm install bs-fetch</code>.</strong></li>
<li>Add "bs-fetch" to the list of bs-dependencies in the <em>bsconfig.json</em>.</li>
</ol>
<p>After that, we should be able to use bs-fetch.</p>
<h3>Initializing the type and binding to a JavaScript constructor</h3>
<p>I have a controversial hottake in functional programming--I love creating a
bunch of short files. A lot of functional programmers pride themselves in how
little they can fit into a single file, but personally, while I love how
succinct functional languages are, I still do not like to scroll, so I am going
to start by creating a new file specificlly for this type and call it
<em><a href="http://fetchevent.re/">FetchEvent.re</a></em>.</p>
<p>One benefit of having a bunch of short files is it makes it easier to manage
a bunch of <a href="https://reasonml.github.io/docs/en/module#open-ing-a-module">open</a>
statements without making a mess or creating ambiguities, so I'm going to open
two modules I'm dependent on.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">open</span> ExtendableEvent<span class="token punctuation">;</span><br /><span class="token keyword">open</span> Fetch<span class="token punctuation">;</span></code></pre>
<p>Next, I'm going to initialize my new type using the same
<a href="https://webbureaucrat.gitlab.io/TODO">subtyping technique</a> we used in my first ReasonML series.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">type</span> _fetchEvent<span class="token punctuation">(</span><span class="token type-variable function">'a</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">type</span> fetchEvent_like<span class="token punctuation">(</span><span class="token type-variable function">'a</span><span class="token punctuation">)</span> <span class="token operator">=</span> extendableEvent_like<span class="token punctuation">(</span>_fetchEvent<span class="token punctuation">(</span><span class="token type-variable function">'a</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">type</span> fetchEvent <span class="token operator">=</span> fetchEvent_like<span class="token punctuation">(</span>Dom<span class="token punctuation">.</span>_baseClass<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>This effectively makes <code>fetchEvent</code> a subtype of <code>extendableEvent</code>, so it can
use <code>extendableEvent</code>'s
<a href="https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent/waitUntil">waitUntil</a> method that we defined in a previous blog post.</p>
<p>Lastly, for this section, I'm going to bind to the constructor for this type
(even though the constructor is almost never called directly). By convention,
this function is called <code>make</code>.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">]</span> <span class="token keyword">external</span> make <span class="token punctuation">:</span> fetchEvent <span class="token operator">=</span> <span class="token string">"FetchEvent"</span><span class="token punctuation">;</span></code></pre>
<h3>Binding to JavaScript object properties in ReasonML</h3>
<p>At this point, I'm just moving straight down the page of the
<a href="https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent">FetchEvent</a>
documentation, which brings me to the "Properties" section. I couldn't find
anything on binding properties in the
ReasonML documentation. Fortunately, the BuckleScript
<a href="https://bucklescript.github.io/docs/en/property-access#static-property-access">documentation</a> had the syntax I needed. Basically, each property is a function
that takes an instance of my object, like so:</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token operator">/*</span> properties <span class="token operator">*/</span><br /><span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>get<span class="token punctuation">]</span> <span class="token keyword">external</span> clientId<span class="token punctuation">:</span> fetchEvent <span class="token operator">=></span> string <span class="token operator">=</span> <span class="token string">"clientId"</span><span class="token punctuation">;</span><br /><span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>get<span class="token punctuation">]</span> <span class="token keyword">external</span> preloadResponse<span class="token punctuation">:</span> fetchEvent <span class="token operator">=></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>Response<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=</span><br /> <span class="token string">"preloadResponse"</span><span class="token punctuation">;</span><br /><span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>get<span class="token punctuation">]</span> <span class="token keyword">external</span> replacesClientId<span class="token punctuation">:</span> fetchEvent <span class="token operator">=></span> string <span class="token operator">=</span> <span class="token string">"replacesClientId"</span><span class="token punctuation">;</span><br /><span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>get<span class="token punctuation">]</span> <span class="token keyword">external</span> resultingClientId<span class="token punctuation">:</span> fetchEvent <span class="token operator">=></span> string <span class="token operator">=</span><br /> <span class="token string">"resultingClientId"</span><span class="token punctuation">;</span><br /><span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>get<span class="token punctuation">]</span> <span class="token keyword">external</span> request<span class="token punctuation">:</span> fetchEvent <span class="token operator">=></span> Request<span class="token punctuation">.</span>t <span class="token operator">=</span> <span class="token string">"request"</span><span class="token punctuation">;</span>`</code></pre>
<p>Note that the <code>Request.t</code> and <code>Response.t</code> types are available to us directly
because we <code>open</code>ed <code>Fetch</code> earlier. Otherwise, we would have had to preface
those references with <code>Fetch.</code>.</p>
<h3>Finishing up by binding methods and exposing t</h3>
<p>In continuing working my way down the documentation page, I see there are just
two methods for the type, and one of them is built into the supertype
<code>ExtendableEvent</code>. We can implement this the same way we implement the
properties, but with <code>[@bs.send]</code> to tell BuckleScript to emit a function
binding instead of a property binding.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span> <span class="token keyword">external</span> respondWith<span class="token punctuation">:</span> fetchEvent <span class="token operator">=></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>Response<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=</span><br /> <span class="token string">"respondWith"</span><span class="token punctuation">;</span><br /><br /><span class="token operator">//</span> also inherits waitUntil from ExtendableEvent<span class="token punctuation">.</span></code></pre>
<p>Lastly, unlike <code>ExtendableEvent</code>, I expect my library users to reference
<code>FetchEvent</code> directly, so let's be nice and expose a <code>t</code> reference.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">type</span> t <span class="token operator">=</span> fetchEvent<span class="token punctuation">;</span></code></pre>
<p>This makes it easy for users to reference our type as <code>FetchEvent.t</code>.</p>
<h3>In Conclusion</h3>
<p>That's good enough for tonight. Right now I assume I now have a usable
<code>FetchEvent</code>, thought I still need to learn to actually test this stuff.
I'm optimistic about this binding project. I think if I bind just another
couple of types I'll have enough to make a real ServiceWorker in ReasonML.</p>
Dynamic Options and Optional Parameters in ReasonML2020-06-20T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/dynamic-options-and-optional-parameters-in-reasonml/<p>The next type I want to bind from the
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">JavaScript ServiceWorker API</a>
is <a href="https://developer.mozilla.org/en-US/docs/Web/API/Cache">Cache</a>. At first
glance, <code>Cache</code>
doesn't have any dependencies on any JavaScript interfaces we don't already
have access to, but its methods do use dynamic JavaScript <code>options</code> parameters,
and the way we deal with this in typed languages is to name and create new
specialized types. In this post, I will implement types for these <code>options</code>
and the functions that use them.</p>
<h3>A word on the many definitions of options.</h3>
<p>Unfortunately, this post requires using three different meanings for <em>option</em>
all in the same context. I will distinguish between them as follows:</p>
<ul>
<li><code>option</code> monad - is a type built into ReasonML which helps to handle cases
when a value may or may not exist. Monads are important in functional
programming, and it's not always easy for newcomers, so if you're
unaccustomed, I suggest
<a href="https://reasonml.github.io/docs/en/null-undefined-option#docsNav">studying up</a>.</li>
<li>optional parameters - Parameters that a function doesn't necessarily need
in order to be called (e. g., a function that can be invoked as either
<code>foo(y)</code> or <code>foo(x, y)</code>. In ReasonML, optional parameters often use option
monads, but they don't have to. For further reading, see
<a href="https://reasonml.github.io/docs/en/function#optional-labeled-arguments">the docs</a>.</li>
<li><code>options</code> dynamic object (associative array) - a common name for an
optional parameter for many JavaScript functions. In our binding, it will
correspond directly to a statically-typed ReasonML record.</li>
</ul>
<p>For this reason I'm going to avoid referring to "options" by itself and stick
to the phrases I've defined above. If you're still confused, feel free to
<a href="https://twitter.com/webbureaucrat">@ me</a>.</p>
<h3>Defining the type as a record.</h3>
<p>Looking at the documentation for
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Cache">Cache</a>, I can see
that the first method on the list,
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Cache/match">match</a> takes a
<code>Request</code> and a dynamic <code>options</code> object. <code>Request</code> is already available in
this project through <a href="https://www.npmjs.com/package/bs-fetch">bs-fetch</a>, so we
will here focus on the <code>options</code> dynamic object.</p>
<p>Defining the type itself is almost trivial; all we need to do are to add the
expected property names and type names from <code>match()</code>'s documentation and copy
those into a <a href="https://reasonml.github.io/docs/en/record">ReasonML record</a>
like so:</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">type</span> cacheMatchOptions <span class="token operator">=</span><br /><span class="token punctuation">{</span><br /> ignoreSearch<span class="token punctuation">:</span> bool<span class="token punctuation">,</span><br /> ignoreMethod<span class="token punctuation">:</span> bool<span class="token punctuation">,</span><br /> ignoreVary<span class="token punctuation">:</span> bool<br /><span class="token punctuation">}</span></code></pre>
<p>Now, let's start to implement
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Cache">Cache</a>. We'll start by
opening bs-fetch for the response and request types and declaring our new
type.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">open</span> Fetch<span class="token punctuation">;</span><br /><br /><span class="token keyword">type</span> cache<span class="token punctuation">;</span></code></pre>
<p>Next, let's get our type signature going.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">let</span> <span class="token keyword">match</span> <span class="token operator">=</span> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> <span class="token label property">~options</span><span class="token operator">=?</span><span class="token punctuation">,</span> <span class="token label property">~req</span><span class="token punctuation">:</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">:</span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>Response<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=></span> <br /><span class="token punctuation">{</span><br /> <span class="token operator">/*</span> TODO <span class="token operator">*/</span> <br /><span class="token punctuation">}</span></code></pre>
<p>There's a lot going on here. From left to right, we have a name <code>match</code>, an
unnamed parameter of type <code>cache</code>, a named, optional parameter <code>options</code>, a
<code>Request</code> typed parameter named <code>req</code>, and a return type of a promise of a
response.</p>
<p>I want to spend some time on that middle parameter called <code>options</code>. The "~"
makes it a named parameter, as opposed to <code>cache</code> which is identified by it's
type. The <code>=</code> after the name makes it optional---we don't need it in order
to call <code>match</code>. The thing after the equals sign helps determine its type. If
we had followed it with a value of some kind, then that value would be used
as the default value whenever we called <code>match</code> without that parameter, but
instead we followed it with a question mark, meaning that the parameter will
be typed as an <code>option</code> monad, defaulting to <code>None</code> when there is no value
passed in.</p>
<p>As a side note, the type of the <code>options</code> parameter can't fully be
determined right now.
I didn't add one explicitly because that function signature is already pretty
long. The compiler will be able to infer the type after we've implemented the
rest of the function. Let's get to it.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">let</span> <span class="token keyword">match</span> <span class="token operator">=</span> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> <span class="token label property">~options</span><span class="token operator">=?</span><span class="token punctuation">,</span> <span class="token label property">~req</span><span class="token punctuation">:</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">:</span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>Response<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=></span> <br /><span class="token punctuation">{</span><br /> switch<span class="token punctuation">(</span>options<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token operator">|</span> None <span class="token operator">=></span> <span class="token operator">/*</span> TODO <span class="token operator">*/</span><br /> <span class="token operator">|</span> Some<span class="token punctuation">(</span>o<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token operator">/*</span> TODO <span class="token operator">*/</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>The <code>options</code> parameter is an <code>option</code> monad, so we need to unwrap it with a
switch before we can use the underlying value, which we've just named <code>o</code>.
We're now set up to handle both cases: one where the function is not passed
an <code>options</code> parameter, and one where it is passed. Now we need to write
two bindings: one for each case, and call those bindings inside our switch. I
don't want the library consumers to call these bindings directly, though, so
I'm going to wrap them in a module called <code>Private</code> as a signal to users.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">module</span> Private <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span> <span class="token keyword">external</span> matchWithoutOptions<span class="token punctuation">:</span> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">)</span><br /> <span class="token operator">=></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>Response<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"match"</span><span class="token punctuation">;</span><br /><br /> <span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span> <span class="token keyword">external</span> matchWithOptions<span class="token punctuation">:</span><br /> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">,</span> CacheMatchOptions<span class="token punctuation">.</span>t<span class="token punctuation">)</span><br /> <span class="token operator">=></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>Response<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"match"</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">let</span> <span class="token keyword">match</span> <span class="token operator">=</span> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> <span class="token label property">~options</span><span class="token operator">=?</span><span class="token punctuation">,</span> <span class="token label property">~req</span><span class="token punctuation">:</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">:</span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>Response<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=></span> <br /><span class="token punctuation">{</span><br /> switch<span class="token punctuation">(</span>options<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token operator">|</span> None <span class="token operator">=></span> Private<span class="token punctuation">.</span>matchWithoutOptions<span class="token punctuation">(</span>cache<span class="token punctuation">,</span> req<span class="token punctuation">)</span><br /> <span class="token operator">|</span> Some<span class="token punctuation">(</span>o<span class="token punctuation">)</span> <span class="token operator">=></span> Private<span class="token punctuation">.</span>matchWithOptions<span class="token punctuation">(</span>cache<span class="token punctuation">,</span> req<span class="token punctuation">,</span> o<span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>This will compile and correctly infer the types. It now understands that the
<code>options</code> parameter is an <code>option</code> monad of a
<code>CacheMatchOptions.t</code> through the wild magical
inferencing that comes with ReasonML.</p>
<p>Let's do that all again to bind the <code>matchAll</code> function. It's the same thing
over again but with <code>list</code>s.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">module</span> Private <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span> <span class="token keyword">external</span> matchWithoutOptions<span class="token punctuation">:</span> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">)</span><br /> <span class="token operator">=></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>Response<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"match"</span><span class="token punctuation">;</span><br /><br /> <span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span> <span class="token keyword">external</span> matchWithOptions<span class="token punctuation">:</span> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">,</span> CacheMatchOptions<span class="token punctuation">.</span>t<span class="token punctuation">)</span><br /> <span class="token operator">=></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>Response<span class="token punctuation">.</span>t<span class="token punctuation">)</span><br /> <span class="token operator">=</span> <span class="token string">"match"</span><span class="token punctuation">;</span><br /><br /> <span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span> <span class="token keyword">external</span> matchAllWithoutOptions<span class="token punctuation">:</span> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">)</span><br /> <span class="token operator">=></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>list<span class="token punctuation">(</span>Response<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"match"</span><span class="token punctuation">;</span><br /><br /> <span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span> <span class="token keyword">external</span> matchAllWithOptions<span class="token punctuation">:</span><br /> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">,</span> CacheMatchOptions<span class="token punctuation">.</span>t<span class="token punctuation">)</span><br /> <span class="token operator">=></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>list<span class="token punctuation">(</span>Response<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"match"</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">let</span> <span class="token keyword">match</span> <span class="token operator">=</span> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> <span class="token label property">~options</span><span class="token operator">=?</span><span class="token punctuation">,</span> <span class="token label property">~req</span><span class="token punctuation">:</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">:</span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>Response<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=></span> <br /><span class="token punctuation">{</span><br /> switch<span class="token punctuation">(</span>options<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token operator">|</span> None <span class="token operator">=></span> Private<span class="token punctuation">.</span>matchWithoutOptions<span class="token punctuation">(</span>cache<span class="token punctuation">,</span> req<span class="token punctuation">)</span><br /> <span class="token operator">|</span> Some<span class="token punctuation">(</span>o<span class="token punctuation">)</span> <span class="token operator">=></span> Private<span class="token punctuation">.</span>matchWithOptions<span class="token punctuation">(</span>cache<span class="token punctuation">,</span> req<span class="token punctuation">,</span> o<span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">let</span> matchAll <span class="token operator">=</span> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> <span class="token label property">~options</span><span class="token operator">=?</span><span class="token punctuation">,</span> <span class="token label property">~req</span><span class="token punctuation">:</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">:</span><br /> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>list<span class="token punctuation">(</span>Response<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">=></span> <br /> <span class="token punctuation">{</span><br /> switch<span class="token punctuation">(</span>options<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token operator">|</span> None <span class="token operator">=></span> Private<span class="token punctuation">.</span>matchAllWithoutOptions<span class="token punctuation">(</span>cache<span class="token punctuation">,</span> req<span class="token punctuation">)</span><br /> <span class="token operator">|</span> Some<span class="token punctuation">(</span>o<span class="token punctuation">)</span> <span class="token operator">=></span> Private<span class="token punctuation">.</span>matchAllWithOptions<span class="token punctuation">(</span>cache<span class="token punctuation">,</span> req<span class="token punctuation">,</span> o<span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Continuing through MDN's list of <code>Cache</code> methods, let's add bindings for a few
more functions.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span> <span class="token keyword">external</span> add<span class="token punctuation">:</span> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>T<span class="token punctuation">(</span>unit<span class="token punctuation">)</span><br /> <span class="token operator">=</span> <span class="token string">"add"</span><span class="token punctuation">;</span><br /> <br /><span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span> <span class="token keyword">external</span> addAll<span class="token punctuation">:</span> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> list<span class="token punctuation">(</span>Request<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">=></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>T<span class="token punctuation">(</span>unit<span class="token punctuation">)</span><br /> <span class="token operator">=</span> <span class="token string">"addAll"</span><span class="token punctuation">;</span><br /><br /><span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span> <span class="token keyword">external</span> put<span class="token punctuation">:</span> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">,</span> Response<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>T<span class="token punctuation">(</span>unit<span class="token punctuation">)</span><br /> <span class="token operator">=</span> <span class="token string">"put"</span><span class="token punctuation">;</span><br /></code></pre>
<p>Lastly, we have two more functions with <code>options</code> dynamic objects as
parameters. Each of these takes an <code>options</code> similar to the <code>CacheMatchOptions</code>
we defined in the beginning, but with an additional property, <code>cacheName</code>.</p>
<p>I used the same <code>options</code> record type to represent the <code>options</code> dynamic
objects in <code>match</code> and <code>matchAll</code> because that's fairly intuitive. I wouldn't
expect those definitions to change independently. However, I'm pretty reluctant
to do the same for the
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Cache/delete">delete</a> and
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Cache/keys">keys</a> functions.
Besides, since if I had one record type to serve both purposes, I don't think
I could name it cleanl and intuitively. It's better to make two record types.</p>
<p><em>src/CacheDeleteOptions.re</em></p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">type</span> cacheDeleteOptions <span class="token operator">=</span><br /><span class="token punctuation">{</span><br /> ignoreSearch<span class="token punctuation">:</span> bool<span class="token punctuation">,</span><br /> ignoreMethod<span class="token punctuation">:</span> bool<span class="token punctuation">,</span><br /> ignoreVary<span class="token punctuation">:</span> bool<span class="token punctuation">,</span><br /> cacheName<span class="token punctuation">:</span> string<br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">type</span> t <span class="token operator">=</span> cacheDeleteOptions<span class="token punctuation">;</span></code></pre>
<p><em>src/CacheKeysOptions.re</em></p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">type</span> cacheKeysOptions <span class="token operator">=</span><br /><span class="token punctuation">{</span><br /> ignoreSearch<span class="token punctuation">:</span> bool<span class="token punctuation">,</span><br /> ignoreMethod<span class="token punctuation">:</span> bool<span class="token punctuation">,</span><br /> ignoreVary<span class="token punctuation">:</span> bool<span class="token punctuation">,</span><br /> cacheName<span class="token punctuation">:</span> string<br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">type</span> t <span class="token operator">=</span> cacheKeysOptions<span class="token punctuation">;</span></code></pre>
<p>Then we take what we did for <code>match</code> and <code>matchAll</code> and apply it to <code>delete</code>
and <code>keys</code>, respectively. I'm also going to break <code>Private</code> into more modules
because I like how the short function names look.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">open</span> Fetch<span class="token punctuation">;</span><br /><br /><span class="token keyword">type</span> cache<span class="token punctuation">;</span><br /><br /><span class="token keyword">module</span> Private <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token keyword">module</span> Delete <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span><br /> <span class="token keyword">external</span> withOptions<span class="token punctuation">:</span><br /> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">,</span> CacheDeleteOptions<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>bool<span class="token punctuation">)</span> <span class="token operator">=</span><br /> <span class="token string">"delete"</span><span class="token punctuation">;</span><br /><br /> <span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span><br /> <span class="token keyword">external</span> withoutOptions<span class="token punctuation">:</span> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>bool<span class="token punctuation">)</span> <span class="token operator">=</span><br /> <span class="token string">"delete"</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">module</span> Keys <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token keyword">module</span> WithRequest <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span><br /> <span class="token keyword">external</span> withoutOptions<span class="token punctuation">:</span><br /> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>list<span class="token punctuation">(</span>Request<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">=</span><br /> <span class="token string">"keys"</span><span class="token punctuation">;</span><br /><br /> <span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span><br /> <span class="token keyword">external</span> withOptions<span class="token punctuation">:</span><br /> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">,</span> CacheMatchOptions<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=></span><br /> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>list<span class="token punctuation">(</span>Request<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">=</span><br /> <span class="token string">"keys"</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">module</span> WithoutRequest <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span><br /> <span class="token keyword">external</span> withoutOptions<span class="token punctuation">:</span> cache <span class="token operator">=></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>list<span class="token punctuation">(</span>Request<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">=</span><br /> <span class="token string">"keys"</span><span class="token punctuation">;</span><br /><br /> <span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span><br /> <span class="token keyword">external</span> withOptions<span class="token punctuation">:</span><br /> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> CacheMatchOptions<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>list<span class="token punctuation">(</span>Request<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">=</span><br /> <span class="token string">"keys"</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token keyword">module</span> Match <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span><br /> <span class="token keyword">external</span> withoutOptions<span class="token punctuation">:</span> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>Response<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=</span><br /> <span class="token string">"match"</span><span class="token punctuation">;</span><br /><br /> <span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span><br /> <span class="token keyword">external</span> withOptions<span class="token punctuation">:</span><br /> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">,</span> CacheMatchOptions<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>Response<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=</span><br /> <span class="token string">"match"</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">module</span> MatchAll <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token keyword">module</span> WithRequest <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span><br /> <span class="token keyword">external</span> withoutOptions<span class="token punctuation">:</span><br /> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>list<span class="token punctuation">(</span>Response<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">=</span><br /> <span class="token string">"match"</span><span class="token punctuation">;</span><br /><br /> <span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span><br /> <span class="token keyword">external</span> withOptions<span class="token punctuation">:</span><br /> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">,</span> CacheMatchOptions<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=></span><br /> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>list<span class="token punctuation">(</span>Response<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">=</span><br /> <span class="token string">"match"</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">module</span> WithoutRequest <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span><br /> <span class="token keyword">external</span> withoutOptions<span class="token punctuation">:</span> cache <span class="token operator">=></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>list<span class="token punctuation">(</span>Response<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">=</span><br /> <span class="token string">"match"</span><span class="token punctuation">;</span><br /><br /> <span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span><br /> <span class="token keyword">external</span> withOptions<span class="token punctuation">:</span><br /> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> CacheMatchOptions<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>list<span class="token punctuation">(</span>Response<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">=</span><br /> <span class="token string">"match"</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">let</span> <span class="token keyword">match</span> <span class="token operator">=</span> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> <span class="token label property">~options</span><span class="token operator">=?</span><span class="token punctuation">,</span> <span class="token label property">~req</span><span class="token punctuation">:</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">:</span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>Response<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> switch <span class="token punctuation">(</span>options<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token operator">|</span> None <span class="token operator">=></span> Private<span class="token punctuation">.</span>Match<span class="token punctuation">.</span>withoutOptions<span class="token punctuation">(</span>cache<span class="token punctuation">,</span> req<span class="token punctuation">)</span><br /> <span class="token operator">|</span> Some<span class="token punctuation">(</span>o<span class="token punctuation">)</span> <span class="token operator">=></span> Private<span class="token punctuation">.</span>Match<span class="token punctuation">.</span>withOptions<span class="token punctuation">(</span>cache<span class="token punctuation">,</span> req<span class="token punctuation">,</span> o<span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">let</span> matchAll <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token label property">~options</span><span class="token operator">=?</span><span class="token punctuation">,</span> <span class="token label property">~req</span><span class="token operator">=?</span><span class="token punctuation">,</span> cache<span class="token punctuation">)</span><span class="token punctuation">:</span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>list<span class="token punctuation">(</span>Response<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> switch <span class="token punctuation">(</span>req<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token operator">|</span> None <span class="token operator">=></span><br /> switch <span class="token punctuation">(</span>options<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token operator">|</span> None <span class="token operator">=></span> Private<span class="token punctuation">.</span>MatchAll<span class="token punctuation">.</span>WithoutRequest<span class="token punctuation">.</span>withoutOptions<span class="token punctuation">(</span>cache<span class="token punctuation">)</span><br /> <span class="token operator">|</span> Some<span class="token punctuation">(</span>o<span class="token punctuation">)</span> <span class="token operator">=></span> Private<span class="token punctuation">.</span>MatchAll<span class="token punctuation">.</span>WithoutRequest<span class="token punctuation">.</span>withOptions<span class="token punctuation">(</span>cache<span class="token punctuation">,</span> o<span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /> <span class="token operator">|</span> Some<span class="token punctuation">(</span>r<span class="token punctuation">)</span> <span class="token operator">=></span><br /> switch <span class="token punctuation">(</span>options<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token operator">|</span> None <span class="token operator">=></span> Private<span class="token punctuation">.</span>MatchAll<span class="token punctuation">.</span>WithRequest<span class="token punctuation">.</span>withoutOptions<span class="token punctuation">(</span>cache<span class="token punctuation">,</span> r<span class="token punctuation">)</span><br /> <span class="token operator">|</span> Some<span class="token punctuation">(</span>o<span class="token punctuation">)</span> <span class="token operator">=></span> Private<span class="token punctuation">.</span>MatchAll<span class="token punctuation">.</span>WithRequest<span class="token punctuation">.</span>withOptions<span class="token punctuation">(</span>cache<span class="token punctuation">,</span> r<span class="token punctuation">,</span> o<span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span> <span class="token keyword">external</span> add<span class="token punctuation">:</span> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>unit<span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"add"</span><span class="token punctuation">;</span><br /><br /><span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span><br /><span class="token keyword">external</span> addAll<span class="token punctuation">:</span> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> list<span class="token punctuation">(</span>Request<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">=></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>unit<span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"addAll"</span><span class="token punctuation">;</span><br /><br /><span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>send<span class="token punctuation">]</span><br /><span class="token keyword">external</span> put<span class="token punctuation">:</span> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">,</span> Response<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>unit<span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"put"</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">let</span> delete <span class="token operator">=</span> <span class="token punctuation">(</span>cache<span class="token punctuation">,</span> <span class="token label property">~options</span><span class="token operator">=?</span><span class="token punctuation">,</span> <span class="token label property">~req</span><span class="token punctuation">:</span> Request<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">:</span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>bool<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> switch <span class="token punctuation">(</span>options<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token operator">|</span> None <span class="token operator">=></span> Private<span class="token punctuation">.</span>Delete<span class="token punctuation">.</span>withoutOptions<span class="token punctuation">(</span>cache<span class="token punctuation">,</span> req<span class="token punctuation">)</span><br /> <span class="token operator">|</span> Some<span class="token punctuation">(</span>o<span class="token punctuation">)</span> <span class="token operator">=></span> Private<span class="token punctuation">.</span>Delete<span class="token punctuation">.</span>withOptions<span class="token punctuation">(</span>cache<span class="token punctuation">,</span> req<span class="token punctuation">,</span> o<span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">let</span> keys <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token label property">~options</span><span class="token operator">=?</span><span class="token punctuation">,</span> <span class="token label property">~req</span><span class="token operator">=?</span><span class="token punctuation">,</span> cache<span class="token punctuation">)</span><span class="token punctuation">:</span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>t<span class="token punctuation">(</span>list<span class="token punctuation">(</span>Request<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> switch <span class="token punctuation">(</span>req<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token operator">|</span> None <span class="token operator">=></span><br /> switch <span class="token punctuation">(</span>options<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token operator">|</span> None <span class="token operator">=></span> Private<span class="token punctuation">.</span>Keys<span class="token punctuation">.</span>WithoutRequest<span class="token punctuation">.</span>withoutOptions<span class="token punctuation">(</span>cache<span class="token punctuation">)</span><br /> <span class="token operator">|</span> Some<span class="token punctuation">(</span>o<span class="token punctuation">)</span> <span class="token operator">=></span> Private<span class="token punctuation">.</span>Keys<span class="token punctuation">.</span>WithoutRequest<span class="token punctuation">.</span>withOptions<span class="token punctuation">(</span>cache<span class="token punctuation">,</span> o<span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /> <span class="token operator">|</span> Some<span class="token punctuation">(</span>r<span class="token punctuation">)</span> <span class="token operator">=></span><br /> switch <span class="token punctuation">(</span>options<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token operator">|</span> None <span class="token operator">=></span> Private<span class="token punctuation">.</span>Keys<span class="token punctuation">.</span>WithRequest<span class="token punctuation">.</span>withoutOptions<span class="token punctuation">(</span>cache<span class="token punctuation">,</span> r<span class="token punctuation">)</span><br /> <span class="token operator">|</span> Some<span class="token punctuation">(</span>o<span class="token punctuation">)</span> <span class="token operator">=></span> Private<span class="token punctuation">.</span>Keys<span class="token punctuation">.</span>WithRequest<span class="token punctuation">.</span>withOptions<span class="token punctuation">(</span>cache<span class="token punctuation">,</span> r<span class="token punctuation">,</span> o<span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>And that's our type. I'm not sure, but I <em>think</em> I'm pretty close to having
enough types implemented to write a small ServiceWorker. Hopefully I'll be
able to write that post in the near future.</p>
Elm Line Charts Part I: Times and Timezones2020-07-26T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/elm-line-charts-part-i-times-and-timezones/<p>Elm has a <a href="https://package.elm-lang.org/packages/terezka/line-charts/2.0.1/LineChart">very fine third-party line chart library</a>
which I've enjoyed using in my passion project,
<a href="https://chicagotestout.netlify.app/">Chicago Test Out</a>. It's well-documented
as a library, but if I haven't used it in a while, I find myself struggling
to get started with it. I end up starting at the middle or near the end and
then clumsily working backwards from where I want to end up. For this reason,
I'm working on writing a step-by-step guide for working with
<code>terezka/line-charts</code> based on my own preferences and assuming a high-degree
of customization apart from the defaults, using
<a href="https://chicagotestout.netlify.app/">Chicago Test Out</a> as a model.</p>
<p>One of my preferences is that a linechart almost always be used with time
on the x-axis, and you can't use a time without knowing the time zone, so
we'll start there.</p>
<h3>Step 1: Add a <code>Time.Zone</code> field to your model.</h3>
<p>Timezones have to be fetched asynchronously, which means</p>
<ol>
<li>Open the file that has your model record in it and, if you have not already,
<code>import Time</code>.</li>
<li>Add a field of type <code>Time.Zone</code> to your <code>Model</code> type record.</li>
<li>In your model initialization, initialize your timezone field to Time.utc.</li>
<li>Compile.</li>
</ol>
<p>If you haven't already used <code>elm/time</code> <em>somewhere</em>, the elm compiler may
object and insist that you install the dependency, which it will walk you
through Assuming you don't have a lot of different functions that initialize
a new
model, this should bring your code to a compiling state. I am including the
full source for <em>src/Models.elm</em> from Chicago Test out below for reference.</p>
<pre class="language-elm"><code class="language-elm"><span class="token keyword">module</span> <span class="token constant">Models</span> <span class="token keyword">exposing</span> <span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span> <br /> <br /><span class="token import-statement"><span class="token keyword">import</span> Hospitalization <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token constant">Hospitalization</span><span class="token punctuation">)</span> <br /><span class="token import-statement"><span class="token keyword">import</span> TestDay <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token constant">TestDay</span><span class="token punctuation">)</span> <br /><span class="token import-statement"><span class="token keyword">import</span> Time</span> <br /><br /><span class="token keyword">type</span> <span class="token keyword">alias</span> <span class="token constant">Model</span> <span class="token operator">=</span> <br /> <span class="token punctuation">{</span> <span class="token hvariable">days</span> <span class="token operator">:</span> <span class="token constant">List</span> <span class="token constant">TestDay</span> <br /> <span class="token punctuation">,</span> <span class="token hvariable">hospitalizations</span> <span class="token operator">:</span> <span class="token constant">List</span> <span class="token constant">Hospitalization</span> <br /> <span class="token punctuation">,</span> <span class="token hvariable">mode</span> <span class="token operator">:</span> <span class="token constant">Mode</span> <br /> <span class="token punctuation">,</span> <span class="token hvariable">zone</span><span class="token operator">:</span> <span class="token constant">Time.Zone</span><br /> <span class="token punctuation">}</span><br /><br /><span class="token keyword">type</span> <span class="token constant">Mode</span> <span class="token operator">=</span> <span class="token constant">Test</span> <span class="token operator">|</span> <span class="token constant">HospitalizationMode</span><br /><br /><span class="token hvariable">init</span> <span class="token operator">:</span> <span class="token constant">Model</span><br /><span class="token hvariable">init</span> <span class="token operator">=</span><br /> <span class="token punctuation">{</span> <span class="token hvariable">days</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">hospitalizations</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">mode</span> <span class="token operator">=</span> <span class="token constant">Test</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">zone</span> <span class="token operator">=</span> <span class="token hvariable">Time.utc</span><br /> <span class="token punctuation">}</span><br /></code></pre>
<h3>Step 2: Add a time zone update message</h3>
<ol>
<li>Open the file containing your Elm messages. (Mine is in <em>src/Msg.elm</em>.)</li>
<li>If you haven't already, <code>import Time</code> into that module.</li>
<li>Add an <code>UpdateZone</code> type that takes a <code>Time.Zone</code> parameter, like so:</li>
</ol>
<pre class="language-elm"><code class="language-elm"><span class="token keyword">module</span> <span class="token constant">Msg</span> <span class="token keyword">exposing</span> <span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span><br /><br /><span class="token import-statement"><span class="token keyword">import</span> Hospitalization <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token constant">Hospitalization</span><span class="token punctuation">)</span><br /><span class="token import-statement"><span class="token keyword">import</span> Http</span><br /><span class="token import-statement"><span class="token keyword">import</span> Json.Encode</span><br /><span class="token import-statement"><span class="token keyword">import</span> Models <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token constant">Mode</span><span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /><span class="token import-statement"><span class="token keyword">import</span> RawTestDay <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token constant">RawTestDay</span><span class="token punctuation">)</span><br /><span class="token import-statement"><span class="token keyword">import</span> Time</span><br /><br /><span class="token keyword">type</span> <span class="token constant">Msg</span> <span class="token operator">=</span> <span class="token constant">GotRawTestDays</span> <span class="token punctuation">(</span><span class="token constant">Result</span> <span class="token constant">Http.Error</span> <span class="token punctuation">(</span><span class="token constant">List</span> <span class="token constant">RawTestDay</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token operator">|</span> <span class="token constant">SetMode</span> <span class="token constant">Mode</span><br /> <span class="token operator">|</span> <span class="token constant">UpdateHospitalizationDays</span> <span class="token constant">Json.Encode.Value</span><br /> <span class="token operator">|</span> <span class="token constant">UpdateZone</span> <span class="token constant">Time.Zone</span></code></pre>
<ol start="4">
<li>Implement your new message in your <code>update</code> method of your elm application,
likely located in <em>src/Main.elm</em>. (You may need to <code>import Time</code> here as well.)</li>
<li>Compile.</li>
</ol>
<p>Your code should be once again in a compiling state.</p>
<h3>Get the Timezone from a Task</h3>
<p>Now, just hook a
<a href="https://package.elm-lang.org/packages/elm/core/latest/Task">Task</a> to get
<code>Time.here</code> into your new message, and you should be up and running with
time zones.</p>
<p><a href="https://guide.elm-lang.org/effects/time.html">This example</a> shows how to do
it from <code>init</code>, but in Chicago Test Out, I want to fetch the time zone after
I've run a fetch, so I'm going to hook in from the end of my fetch event.
This is what I mean:</p>
<pre class="language-elm"><code class="language-elm"><span class="token hvariable">update</span> <span class="token operator">:</span> <span class="token constant">Msg</span> <span class="token operator">-></span> <span class="token constant">Model</span> <span class="token operator">-></span> <span class="token punctuation">(</span><span class="token constant">Model</span><span class="token punctuation">,</span> <span class="token constant">Cmd</span> <span class="token constant">Msg</span><span class="token punctuation">)</span><br /><span class="token hvariable">update</span> <span class="token hvariable">msg</span> <span class="token hvariable">model</span> <span class="token operator">=</span><br /> <span class="token keyword">case</span> <span class="token hvariable">msg</span> <span class="token keyword">of</span><br /> <span class="token constant">GotRawTestDays</span> <span class="token hvariable">result</span> <span class="token operator">-></span><br /> <span class="token keyword">case</span> <span class="token hvariable">result</span> <span class="token keyword">of</span><br /> <span class="token constant">Ok</span> <span class="token hvariable">response</span> <span class="token operator">-></span> <br /> <span class="token hvariable">TestDay.fromRaws</span> <span class="token hvariable">response</span><br /> <span class="token operator">|></span> \<span class="token hvariable">days</span> <span class="token operator">-></span> <span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token hvariable">model</span> <span class="token operator">|</span> <span class="token hvariable">days</span> <span class="token operator">=</span> <span class="token hvariable">days</span><span class="token punctuation">}</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">Task.perform</span> <span class="token constant">UpdateZone</span> <span class="token hvariable">Time.here</span><br /> <span class="token punctuation">)</span><br /> <span class="token constant">Err</span> <span class="token builtin">e</span> <span class="token operator">-></span><br /> <span class="token keyword">case</span> <span class="token builtin">e</span> <span class="token keyword">of</span><br /> <span class="token operator"> .<br /></span> <span class="token operator"> .<br /></span> <span class="token operator"> .</span></code></pre>
<p>That's a lot to look at, but as you can see I'm calling <code>Task.perform</code> if the
response from <code>GotRawTestDays</code> is <code>Ok</code>. If the Task is successfull, the
current time zone, <code>Time.here</code> will be passed to the message handler of
<code>UpdateZone</code>.</p>
<h3>A word on modeling times in Elm</h3>
<p>As a reminder, Elm rejects ISO 8601 as a standard for dates. If you want dates
along the x-axis of your chart, you need to have those dates passed as a
posix number. For line charts, this should be a <code>Float</code> type. The steps to
get your data will vary, so I won't enumerate them here, but keep that in mind
as you work on fetching your line chart dataset: You need a <code>Float</code> for a date.</p>
<h3>In Conclusion</h3>
<p>This pretty well covers our prerequisites for working with line charts. In the
next post, I'll start to scaffold a real chart.</p>
Elm Line Charts Part II: Imports and Axes2020-07-27T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/elm-line-charts-part-ii-imports-and-axes/<p>This is the second in a series of blog posts dealing with
<a href="https://package.elm-lang.org/packages/terezka/line-charts/latest/LineChart">LineChart</a>
in Elm. In the
<a href="https://webbureaucrat.gitlab.io/posts/elm-line-charts-part-i-times-and-timezones/">previous post</a>,
I outlined how to grab the timezone as a
prerequisite for time-based linecharts. In this post, I will begin to write
the chart module I'm trying to use in
<a href="https://chicagotestout.netlify.app/">Chicago Test Out</a> by defining my imports
and creating a custom axis.</p>
<h3>Imports and Aliases</h3>
<p><a href="https://package.elm-lang.org/packages/terezka/line-charts/latest/LineChart">LineChart</a>
requires a lot of imports. For my own convenience, I'm going to leave them
here.</p>
<pre class="language-elm"><code class="language-elm"><span class="token keyword">module</span> <span class="token constant">HospitalizationPercentChart</span> <span class="token keyword">exposing</span> <span class="token punctuation">(</span><span class="token hvariable">chart</span><span class="token punctuation">)</span><br /><br /><span class="token import-statement"><span class="token keyword">import</span> Hospitalization <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token constant">Hospitalization</span><span class="token punctuation">)</span><br /><span class="token import-statement"><span class="token keyword">import</span> Html <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span><br /><span class="token import-statement"><span class="token keyword">import</span> LineChart</span><br /><span class="token import-statement"><span class="token keyword">import</span> LineChart.Colors <span class="token keyword">as</span> Colors</span><br /><span class="token import-statement"><span class="token keyword">import</span> LineChart.Junk <span class="token keyword">as</span> Junk</span><br /><span class="token import-statement"><span class="token keyword">import</span> LineChart.Area <span class="token keyword">as</span> Area</span><br /><span class="token import-statement"><span class="token keyword">import</span> LineChart.Axis <span class="token keyword">as</span> Axis</span><br /><span class="token import-statement"><span class="token keyword">import</span> LineChart.Axis.Line <span class="token keyword">as</span> AxisLine</span><br /><span class="token import-statement"><span class="token keyword">import</span> LineChart.Axis.Range <span class="token keyword">as</span> Range</span><br /><span class="token import-statement"><span class="token keyword">import</span> LineChart.Axis.Ticks <span class="token keyword">as</span> Ticks</span><br /><span class="token import-statement"><span class="token keyword">import</span> LineChart.Axis.Title <span class="token keyword">as</span> Title</span><br /><span class="token import-statement"><span class="token keyword">import</span> LineChart.Junk <span class="token keyword">as</span> Junk</span><br /><span class="token import-statement"><span class="token keyword">import</span> LineChart.Dots <span class="token keyword">as</span> Dots</span><br /><span class="token import-statement"><span class="token keyword">import</span> LineChart.Grid <span class="token keyword">as</span> Grid</span><br /><span class="token import-statement"><span class="token keyword">import</span> LineChart.Dots <span class="token keyword">as</span> Dots</span><br /><span class="token import-statement"><span class="token keyword">import</span> LineChart.Line <span class="token keyword">as</span> Line</span><br /><span class="token import-statement"><span class="token keyword">import</span> LineChart.Colors <span class="token keyword">as</span> Colors</span><br /><span class="token import-statement"><span class="token keyword">import</span> LineChart.Events <span class="token keyword">as</span> Events</span><br /><span class="token import-statement"><span class="token keyword">import</span> LineChart.Legends <span class="token keyword">as</span> Legends</span><br /><span class="token import-statement"><span class="token keyword">import</span> LineChart.Container <span class="token keyword">as</span> Container</span><br /><span class="token import-statement"><span class="token keyword">import</span> LineChart.Interpolation <span class="token keyword">as</span> Interpolation</span><br /><span class="token import-statement"><span class="token keyword">import</span> LineChart.Axis.Intersection <span class="token keyword">as</span> Intersection</span><br /><span class="token import-statement"><span class="token keyword">import</span> Msg <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span><br /><span class="token import-statement"><span class="token keyword">import</span> Time</span></code></pre>
<p>The aliases are not only convenient, but are an established convention in the
documentation, so they're a good idea if you'd like to copy and paste code.</p>
<h3>Defining a View Model</h3>
<p>Line charts are typed according to a model, and axes are configured according
to specific fields within that model. It's a good design, but a consequence
of that is that it often requires a generic data viewmodel specifically for the
chart. (For example, let's say I have a list of records and each record
represents a day. If I define the Y axis to be the number of ICU beds for that
day, it cannot also be used to show the number of ventilators in use for that
day.)</p>
<p>For this line chart, I'm definitely going to want multiple lines on the chart,
and even if I didn't, it would probably be a good practice to leave that option
open to extension, so let's create that model:</p>
<pre class="language-elm"><code class="language-elm"><span class="token keyword">type</span> <span class="token keyword">alias</span> <span class="token constant">ChartModel</span> <span class="token operator">=</span><br /> <span class="token punctuation">{</span> <span class="token hvariable">date</span><span class="token operator">:</span> <span class="token constant">Float</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">val</span><span class="token operator">:</span> <span class="token constant">Float</span><br /> <span class="token punctuation">}</span></code></pre>
<p>While I expect the date to display as a date, the line charts library will
ultimately handle that conversion--it expects a float. <code>val</code> is a generic
name for a wrapper around any float value.</p>
<h3>Defining an Axis</h3>
<p>One of the reasons why I'm writing this series is that the examples in the
documentation use a lot of the defaults for the library. These are great if
you're trying to get a quick sense for how the library works without being
overwhelmed by choices right off the bat. This series aims to be a little more
in-depth and a little more oriented toward real-world usage, so I'm going to
assume a certain amount of customization.</p>
<pre class="language-elm"><code class="language-elm"><span class="token hvariable">xAxisConfig</span> <span class="token operator">:</span> <span class="token constant">Time.Zone</span> <span class="token operator">-></span> <span class="token constant">Axis.Config</span> <span class="token constant">ChartModel</span> <span class="token hvariable">msg</span><br /><span class="token hvariable">xAxisConfig</span> <span class="token hvariable">zone</span> <span class="token operator">=</span><br /> <span class="token hvariable">Axis.custom</span><br /> <span class="token punctuation">{</span> <span class="token hvariable">title</span> <span class="token operator">=</span> <span class="token hvariable">Title.default</span> <span class="token string">"Time"</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">variable</span> <span class="token operator">=</span> <span class="token constant">Just</span> <span class="token operator"><<</span> <span class="token punctuation">.</span><span class="token hvariable">date</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">pixels</span> <span class="token operator">=</span> <span class="token number">1000</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">range</span> <span class="token operator">=</span> <span class="token hvariable">Range.default</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">axisLine</span> <span class="token operator">=</span> <span class="token hvariable">AxisLine.rangeFrame</span> <span class="token hvariable">Colors.black</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">ticks</span> <span class="token operator">=</span> <span class="token hvariable">Ticks.time</span> <span class="token hvariable">zone</span> <span class="token number">5</span><br /> <span class="token punctuation">}</span></code></pre>
<p>This code is fairly self-explanatory, and I'm mainly including it as
copypasta for myself for the future. Basically, it's a function that takes
a time zone and spits out an axis based on the <code>.date</code> property of our
<code>ChartModel</code>. Also note that I'm not using the default <code>ticks</code> because I have
found that doesn't work well with time as an axis. (The letters overlap
a great deal.)</p>
<p>If your imports are alright, you should once again have a program in a
compiling state.
<a href="https://webbureaucrat.gitlab.io/posts/elm-line-charts-part-iii-lines-and-the-chart/">Next</a>, we'll work on defining lines.</p>
Elm Line Charts Part III: Lines and the Chart2020-07-31T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/elm-line-charts-part-iii-lines-and-the-chart/<p>This is the last installment of a series describing how to configure an
<a href="https://package.elm-lang.org/packages/terezka/line-charts/latest/LineChart">Elm LineChart</a>.
In the <a href="https://webbureaucrat.gitlab.io/posts/elm-line-charts-imports-and-axes/">previous post</a> I used a
viewmodel to configure an axis, so this post will cover how to use lists of
those viewmodels to plot the rest of the chart.</p>
<h2>Converting to the ViewModel</h2>
<p>I want this line chart to have three lines based on my <code>ChartModel</code> from the
previous post, and I know I'm starting with a model that has a list of
<code>Hospitalization</code>s, so I'll start with ways to convert between the two.</p>
<pre class="language-elm"><code class="language-elm"><span class="token hvariable">icuBeds</span><span class="token operator">:</span> <span class="token constant">List</span> <span class="token constant">Hospitalization</span> <span class="token operator">-></span> <span class="token constant">List</span> <span class="token constant">ChartModel</span><br /><span class="token hvariable">icuBeds</span> <span class="token hvariable">days</span> <span class="token operator">=</span><br /> <span class="token hvariable">List.map</span> <span class="token punctuation">(</span>\<span class="token hvariable">d</span> <span class="token operator">-></span> <span class="token hvariable">d</span><span class="token punctuation">.</span><span class="token hvariable">icuBedsTotal</span><span class="token operator">/</span><span class="token hvariable">d</span><span class="token punctuation">.</span><span class="token hvariable">icuBedsTotalCapacity</span><br /> <span class="token operator">|></span> \<span class="token hvariable">ratio</span> <span class="token operator">-></span> <span class="token hvariable">ratio</span> <span class="token operator">*</span> <span class="token number">100</span><br /> <span class="token operator">|></span> <span class="token constant">ChartModel</span> <span class="token hvariable">d</span><span class="token punctuation">.</span><span class="token hvariable">date</span><span class="token punctuation">)</span><br /> <span class="token hvariable">days</span><br /><br /><span class="token hvariable">nonIcuBeds</span><span class="token operator">:</span> <span class="token constant">List</span> <span class="token constant">Hospitalization</span> <span class="token operator">-></span> <span class="token constant">List</span> <span class="token constant">ChartModel</span><br /><span class="token hvariable">nonIcuBeds</span> <span class="token hvariable">days</span> <span class="token operator">=</span><br /> <span class="token hvariable">List.map</span> <span class="token punctuation">(</span>\<span class="token hvariable">d</span> <span class="token operator">-></span> <span class="token hvariable">d</span><span class="token punctuation">.</span><span class="token hvariable">acuteNonIcuBedsTotal</span><span class="token operator">/</span><span class="token hvariable">d</span><span class="token punctuation">.</span><span class="token hvariable">acuteNonIcuBedsTotalCapacity</span><br /> <span class="token operator">|></span> \<span class="token hvariable">ratio</span> <span class="token operator">-></span> <span class="token hvariable">ratio</span> <span class="token operator">*</span> <span class="token number">100</span><br /> <span class="token operator">|></span> <span class="token constant">ChartModel</span> <span class="token hvariable">d</span><span class="token punctuation">.</span><span class="token hvariable">date</span><br /> <span class="token punctuation">)</span><br /> <span class="token hvariable">days</span><br /><br /><span class="token hvariable">ventilators</span> <span class="token operator">:</span> <span class="token constant">List</span> <span class="token constant">Hospitalization</span> <span class="token operator">-></span> <span class="token constant">List</span> <span class="token constant">ChartModel</span><br /><span class="token hvariable">ventilators</span> <span class="token hvariable">days</span> <span class="token operator">=</span><br /> <span class="token hvariable">List.map</span> <span class="token punctuation">(</span>\<span class="token hvariable">d</span> <span class="token operator">-></span> <span class="token hvariable">d</span><span class="token punctuation">.</span><span class="token hvariable">ventilatorsTotal</span><span class="token operator">/</span><span class="token hvariable">d</span><span class="token punctuation">.</span><span class="token hvariable">ventilatorsTotalCapacity</span><br /> <span class="token operator">|></span> \<span class="token hvariable">ratio</span> <span class="token operator">-></span> <span class="token hvariable">ratio</span> <span class="token operator">*</span> <span class="token number">100</span><br /> <span class="token operator">|></span> <span class="token constant">ChartModel</span> <span class="token hvariable">d</span><span class="token punctuation">.</span><span class="token hvariable">date</span><span class="token punctuation">)</span><br /> <span class="token hvariable">days</span></code></pre>
<p>All I'm doing here is taking different fields from <code>Hospitalization</code>,
converting to a percent, and piping out to a <code>ChartModel</code>. I've configured
this for each of the three percentages I want on my chart.</p>
<h2><a href="https://www.reddit.com/r/restofthefuckingowl/">Draw the Rest of the Owl</a></h2>
<p>I'm happy with the rest of the defaults and they don't generaly need a ton of
explanation, so I'm just going to dump this pile of code unceremoniously here
so that I can paste it someplace else when I need it.</p>
<pre class="language-elm"><code class="language-elm"><span class="token hvariable">chart</span> <span class="token operator">:</span> <span class="token constant">Time.Zone</span> <span class="token operator">-></span> <span class="token constant">List</span> <span class="token constant">Hospitalization</span> <span class="token operator">-></span> <span class="token constant">Html</span> <span class="token constant">Msg</span><br /><span class="token hvariable">chart</span> <span class="token hvariable">zone</span> <span class="token hvariable">days</span> <span class="token operator">=</span><br /> <span class="token hvariable">LineChart.viewCustom</span><br /> <span class="token punctuation">{</span> <span class="token hvariable">x</span> <span class="token operator">=</span> <span class="token hvariable">xAxisConfig</span> <span class="token hvariable">zone</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">y</span> <span class="token operator">=</span> <span class="token hvariable">Axis.default</span> <span class="token number">400</span> <span class="token string">"Volume"</span> <span class="token punctuation">.</span><span class="token hvariable">val</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">area</span> <span class="token operator">=</span> <span class="token hvariable">Area.default</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">container</span> <span class="token operator">=</span> <span class="token hvariable">Container.default</span> <span class="token string">"hospitalization-raw-chart"</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">interpolation</span> <span class="token operator">=</span> <span class="token hvariable">Interpolation.default</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">intersection</span> <span class="token operator">=</span> <span class="token hvariable">Intersection.default</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">legends</span> <span class="token operator">=</span> <span class="token hvariable">Legends.default</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">events</span> <span class="token operator">=</span> <span class="token hvariable">Events.default</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">junk</span> <span class="token operator">=</span> <span class="token hvariable">Junk.default</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">grid</span> <span class="token operator">=</span> <span class="token hvariable">Grid.default</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">line</span> <span class="token operator">=</span> <span class="token hvariable">Line.default</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">dots</span> <span class="token operator">=</span> <span class="token hvariable">Dots.default</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">[</span> <span class="token hvariable">LineChart.line</span> <span class="token hvariable">Colors.blue</span> <span class="token hvariable">Dots.none</span> <span class="token string">"ICU Beds"</span> <span class="token punctuation">(</span><span class="token hvariable">icuBeds</span> <span class="token hvariable">days</span><span class="token punctuation">)</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">LineChart.line</span> <span class="token hvariable">Colors.purple</span> <span class="token hvariable">Dots.none</span> <span class="token string">"Acute Non-ICU Beds"</span><br /> <span class="token punctuation">(</span><span class="token hvariable">nonIcuBeds</span> <span class="token hvariable">days</span><span class="token punctuation">)</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">LineChart.line</span> <span class="token hvariable">Colors.red</span> <span class="token hvariable">Dots.none</span> <span class="token string">"Ventilators"</span> <span class="token punctuation">(</span><span class="token hvariable">ventilators</span> <span class="token hvariable">days</span><span class="token punctuation">)</span><br /> <span class="token punctuation">]</span></code></pre>
<p>As you can see you plug in your time zone and some default values and a list
of lines based on the functions defined above and give it a good stir.</p>
<p>In all seriousness, the important thing here is to use <code>viewCustom</code>
rather than fiddle with
making the numbered signatures work. (One thing I absolutely do not
understand is the way that Elm deals with parametric polymorphism by putting
numbers in the signatures. Elm, you are flawed and I love you anyway.)</p>
Issues and Contribution2020-08-03T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/issues-and-contribution/<p>I've pushed out a few packages recently, and I'd like to take the opportunity
to publish a catch-all document on contribution and issues for each of these.
This post will walk the user through my current thinking on how to engage
these projects, and I'll keep it up to date as my thinking evolves.</p>
<h3>Abandonment</h3>
<p>At this time, each of my projects has my full support. Some of these projects,
though, especially smaller projects, may go for long stretches of time without
receiving new commits simply because they are <strong>finished</strong> for the forseeable
future. If I ever decide to sunset a project later, I'll be sure to list it
here. Absent that, feel free to reach out to me at any time about any of my
projects.</p>
<h3>Issues</h3>
<p>It's my intention to host my projects on GitLab going forward, so if you have
a GitLab account and you're comfortable opening issues, feel free to do so
directly. You can find a list of my repositories on
<a href="https://gitlab.com/users/eleanorofs/projects">my GitLab projects page</a>.</p>
<p>If you don't have an account, no worries. Feel free to contact me another way.
I've listed some other ways to contact me below. After we've talked over your
issue, I may open an issue on your behalf for tracking and transparency
purposes.</p>
<h3>Contribution</h3>
<h4>Gratuity</h4>
<p>There are a couple ways of showing your appreciation for the project. If you're
using and enjoying this project, please consider showing it by giving me a
star on GitLab. It's very much appreciated.</p>
<p>Fortunately, I have enough money, but if you'd like to contribute financially,
I strongly encourage you to donate to
<a href="https://www.rehumanizeintl.org/donate">Rehumanize International</a> and let me
know that you did. I'll consider it an incentive to keep working on these
projects, so it'll be like you're donating to both.</p>
<h4>Code</h4>
<p>If you have something you'd like to see added to one of my projects, it's
probably faster to do it yourself than to wait around for me to do it. I'm
open to merge requests on each of my projects, which you can find on
<a href="https://gitlab.com/users/eleanorofs/projects">my GitLab projects page</a>.</p>
<p>If you'd like, feel free to start a conversation with me before you spend time
on your contribution. I can give you
an up-or-down to make sure you aren't wasting your time and give you my
half-baked thoughts on style for easier merging. The various ways to
get in touch are listed below.</p>
<p>I do want to say ahead of time: For my own projects, I tend to like my code
<em>just so</em>. I'm still formulating a style guide in my head. A few bullet points:</p>
<ul>
<li>80-character lines are very important to me.</li>
<li>Type annotations are required wherever feasible.</li>
<li>Code comments are <strong>always</strong> encouraged.</li>
<li>I generally prefer lambda expressions with an explicitly named parameter.
(E. g., <code>map(data => myFunction(data))</code> over <code>map(myFunction)</code>. This isn't a
hard-and-fast rule, though, and it's not necessary especially if you're
fighting with the 80-char line rule.</li>
</ul>
<p>Please don't take it personally if I nit-pick your contribution. I am fully
self-aware that most of my judgements are arbitrary, and I definitely don't
think any less of you as a developer if I decide to send something back to
you for a silly reason. I promise I am a good person when I'm not reading
other people's code.</p>
<h3>Coordinated disclosure of security vulnerabilities</h3>
<p>I've listed several ways of contacting me below. Please only discuss
vulnerabilities over an end-to-end encrypted channel.</p>
<ul>
<li>My KeyBase chat is published below.</li>
<li>I also have a Signal account and a ProtonMail account. If you would like
to use one of those, feel free to ask me for it by opening an issue or
contacting me on Twitter or some other way.</li>
<li>I'm open to using whatever end-to-end encrypted service you prefer, even if
I don't already have an account, just let me know.</li>
</ul>
<p>After the issue has been resolved, I will post it as an issue and then close
the issue out for transparency purposes, so that users know that there has been
an issue and that it's important to update.</p>
<p>Thank you so much in advance for working on these issues!</p>
<h3>Questions</h3>
<p>If you have any questions about any of my projects or you'd like to see
something documented that isn't already, please feel free to contact me any
of the ways listed below.</p>
<p>I encourage you, though, to start on StackOverflow and then shoot me a link.
I like StackOverflow and I'd like to see the young communities I'm involved in
like Elm and ReasonML to thrive there by building a public, searchable body
of knowledge.</p>
<h3>Contacting Me</h3>
<p>For public discussions:</p>
<ul>
<li>@ me or DM me on <a href="https://twitter.com/webbureaucrat">Twitter</a>.</li>
<li>Open an issue on the appropriate
<a href="https://gitlab.com/users/eleanorofs/projects">GitLab project page</a>.</li>
</ul>
<p>For private discussions (for example, security vulnerabilities):</p>
<ul>
<li>Add me on KeyBase chat at eleanorholley.</li>
<li>DM me on <a href="https://twitter.com/webbureaucrat">Twitter</a> for my Signal
account or my ProtonMail account.</li>
</ul>
<p>Feel free to reach out--I'm happy to shoot the breeze with any randos for
all things tech-related.</p>
How to Use Excerpts in Eleventy2020-08-08T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/eleventy-excerpts/<p>Recently, I added first-paragraph
<a href="https://www.11ty.dev/docs/data-frontmatter-customize/">post excerpts</a>
to this Eleventy blog's
homepage post list. I found it wasn't easy. It wasn't all documented all in
one place. Further, in order to use Markdown excerpts in HTML, I had to write
a simple custom filter. I'd like to
document the process here from end to end.</p>
<h3>Enable <code>grey-matter</code> excerpts in <em>.eleventy.js</em>.</h3>
<p>The first thing we need is to configure Eleventy to be able to see our
excerpts. This is easily done by adding this line to the <em>.eleventy.js</em>
configuration file into the main
<code>module.exports = function(eleventyConfig) { ... }</code> function.</p>
<pre class="language-js"><code class="language-js">eleventyConfig<span class="token punctuation">.</span><span class="token function">setFrontMatterParsingOptions</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">excerpt</span><span class="token operator">:</span> <span class="token boolean">true</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<h3>Optional: Set the excerpt separator</h3>
<p>The excerpt separator is some string which marks the end of the excerpt and
the beginning of the rest of the article. By default, it is "---" but this
default is easily overridden using the optional <code>excerpt_separator</code> property
of the Front Matter parsing options object, like so:</p>
<pre class="language-js"><code class="language-js">eleventyConfig<span class="token punctuation">.</span><span class="token function">setFrontMatterParsingOptions</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">excerpt</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">excerpt_separator</span><span class="token operator">:</span> <span class="token string">"--excerpt--"</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<h3>Add excerpt separators into each post</h3>
<p>Now you can mark each post with excerpt separators. I use the default "---",
but you can use whatever <code>excerpt_separator</code> you may have overridden it with
in the previous step.</p>
<h3>Write a simple custom filter to read Markdown excerpts</h3>
<p>At this point, we need to reconcile a potential conflict. In my case at least,
I am writing blog posts in markdown, but my post list on the homepage is
an <em>.njk</em> that compiles to HTML. I don't want to change either of those
things, but if I reference my markdown excerpts in HTML, they'll show up as
raw markdown text. I need to write a bit of middleware to reconcile the two.</p>
<p>Start by locating your markdownIt options object. You'll find it in a block of
code that looks something like this:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> markdownLibrary <span class="token operator">=</span> <span class="token function">markdownIt</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> <span class="token literal-property property">html</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">breaks</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">linkify</span><span class="token operator">:</span> <span class="token boolean">true</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br />eleventyConfig<span class="token punctuation">.</span><span class="token function">setLibrary</span><span class="token punctuation">(</span><span class="token string">"md"</span><span class="token punctuation">,</span> markdownLibrary<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>You'll need to reuse this object, so it's a good practice to separate it into
its own constant at the top of the file...</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token constant">MARKDOWN_OPTIONS</span> <span class="token operator">=</span><br /> <span class="token punctuation">{</span><br /> <span class="token literal-property property">html</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">breaks</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">linkify</span><span class="token operator">:</span> <span class="token boolean">true</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>...that can be referenced in multiple places</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> markdownLibrary <span class="token operator">=</span> <span class="token function">markdownIt</span><span class="token punctuation">(</span><span class="token constant">MARKDOWN_OPTIONS</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br />eleventyConfig<span class="token punctuation">.</span><span class="token function">setLibrary</span><span class="token punctuation">(</span><span class="token string">"md"</span><span class="token punctuation">,</span> markdownLibrary<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>This is all in the service of writing a simple custom filters that can take
markdown strings and turn them into HTML fragments. This will do it for us.</p>
<pre class="language-js"><code class="language-js">eleventyConfig<span class="token punctuation">.</span><span class="token function">addFilter</span><span class="token punctuation">(</span><span class="token string">"toHTML"</span><span class="token punctuation">,</span> <span class="token parameter">str</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">markdownIt</span><span class="token punctuation">(</span><span class="token constant">MARKDOWN_OPTIONS</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">renderInline</span><span class="token punctuation">(</span>str<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<h3>Use the custom filter to display the HTML excerpts in your postlists</h3>
<p>Now, finally, I can include this excerpt in my <em>/_includes/postlist.njk</em>,
which iterates over the <code>posts</code> collection like so:</p>
<pre class="language-html"><code class="language-html"><br /> {% for post in postslist | reverse %}<br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>li</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>postlist-item{% if post.url == url %} postlist-item-active{% endif %}<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h2</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>h2-postlist<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>{{ post.url | url }}<span class="token punctuation">"</span></span><br /> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>postlist-link<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> {% if post.data.title %}{{ post.data.title }}<br /> {% else %}<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>code</span><span class="token punctuation">></span></span>{{ post.url }}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>code</span><span class="token punctuation">></span></span><br /> {% endif %}<br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h2</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>time</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>postlist-date<span class="token punctuation">"</span></span><br /> <span class="token attr-name">datetime</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>{{ post.date | htmlDateString }}<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> {{ post.date | htmlDateString }}<br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>time</span><span class="token punctuation">></span></span><br /> {% for tag in post.data.tags %}<br /> {%- if collections.tagList.indexOf(tag) != -1 -%}<br /> {% set tagUrl %}/tags/{{ tag }}/{% endset %}<br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>{{ tagUrl | url }}<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>tag<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>{{ tag }}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span><br /> {%- endif -%}<br /> {% endfor %}<br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>li</span><span class="token punctuation">></span></span><br /> {% endfor %}<br /></code></pre>
<p>Let us pipe the post excerpt into our <code>toHTML</code> custom filter and then pipe
the output of our filter to the built-in <code>safe</code> filter so that Eleventy treats
the output as HTML instead of plain text.</p>
<pre class="language-html"><code class="language-html"><br /> {% for post in postslist | reverse %}<br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>li</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>postlist-item{% if post.url == url %} postlist-item-active{% endif %}<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h2</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>h2-postlist<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>{{ post.url | url }}<span class="token punctuation">"</span></span><br /> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>postlist-link<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> {% if post.data.title %}{{ post.data.title }}<br /> {% else %}<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>code</span><span class="token punctuation">></span></span>{{ post.url }}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>code</span><span class="token punctuation">></span></span><br /> {% endif %}<br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h2</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>time</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>postlist-date<span class="token punctuation">"</span></span><br /> <span class="token attr-name">datetime</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>{{ post.date | htmlDateString }}<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> {{ post.date | htmlDateString }}<br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>time</span><span class="token punctuation">></span></span><br /> {% for tag in post.data.tags %}<br /> {%- if collections.tagList.indexOf(tag) != -1 -%}<br /> {% set tagUrl %}/tags/{{ tag }}/{% endset %}<br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>{{ tagUrl | url }}<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>tag<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>{{ tag }}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span><br /> {%- endif -%}<br /> {% endfor %}<br /> {%- if post.data.page.excerpt -%}<br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span>{{ post.data.page.excerpt | toHTML | safe}}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span><br /> {%- endif -%}<br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>li</span><span class="token punctuation">></span></span><br /> {% endfor %}<br /></code></pre>
<p>And now when we run <code>npx eleventy --serve</code> we should see our excerpts
everyplace we reference <em>_includes/postlist.njk</em>.</p>
How to Write Elm Ports in ReasonML2020-09-01T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/how-to-write-elm-ports-in-reasonml/<p>Recently I've published an npm package called
<a href="https://npmjs.org/package/bs-elm-es6/">bs-elm-es6</a> and put it into
production on a couple of projects. It's documented briefly by its README, but
I think it deserves a full post. This post will walk through how to set up
ports both into and out of an elm 0.19 project using BuckleScript 7. (If you're
curious, I'm
deferring decisions about the rebrand/new syntax until we get the new npm
package.)</p>
<h3>The Goal: shared control between ReasonML and Elm through ports</h3>
<p>The final product is intended to be minimally reproducible and easy to
understand, not necessarily useful. In this case, I think the best page to
show the features of this very small library is a very small web app--an
app with two text boxes that show the ReasonML app and the Elm app
communicating in real time. You can find such an app
<a href="https://elmandbucklescript.gitlab.io/">here</a>.</p>
<p>Take a moment to play around with the two text boxes. The first one
lives in Reasonland, but on its input event, Reason sends its content
into the Elm app. The second lives in Elmland, but on its input event,
sends its input to the Reason scripts through another port. The result
are two text boxes that always match.</p>
<p>Ordinarily, I would never have a textbox that lives outside the elm
app--I'd give control of the whole view to Elm, but it's easy to imagine that
the app instead has ports to something like an IndexedDB repository, in the
case of my <a href="https://chicagotestout.netlify.app/">Chicago area COVID-19 tracker</a>,
an HTTP call to some JSON data.</p>
<h3>Basic elm setup</h3>
<p>Detailed instructions for how to write a basic elm project is out of scope for
this kind of post, but I want some elm code here for
completeness--so that I could fully
reproduce this kind of project without having to flip back to the demo
project's source code.</p>
<p>I'll start with two basic messages <code>SendString</code> and <code>UpdateString</code> that
represent the two directions of information flow into and out of the app.</p>
<p><em>Msg.elm</em></p>
<pre class="language-elm"><code class="language-elm"><span class="token keyword">module</span> <span class="token constant">Msg</span> <span class="token keyword">exposing</span> <span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span><br /><br /><span class="token keyword">type</span> <span class="token constant">Msg</span> <span class="token operator">=</span> <span class="token constant">SendString</span> <span class="token constant">String</span><br /> <span class="token operator">|</span> <span class="token constant">UpdateString</span> <span class="token constant">String</span><br /></code></pre>
<p>If you're familiar with Elm ports already, you should be familiar with
JSON encoding/decoding in Elm ports. This is out of the scope of what
I'm trying to demonstrate, so strings here will be fine, but safely parsing
JSON is
a best practice and you'll need it for complex data types.</p>
<p>I also want two ports on this elm app, again representing the
bidirectional flow of data into and out of this elm app.</p>
<p><em>Ports.elm</em></p>
<pre class="language-elm"><code class="language-elm"><span class="token hvariable">port</span> <span class="token keyword">module</span> <span class="token constant">Ports</span> <span class="token keyword">exposing</span> <span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span><br /><br /><span class="token hvariable">port</span> <span class="token hvariable">toReason</span> <span class="token operator">:</span> <span class="token constant">String</span> <span class="token operator">-></span> <span class="token constant">Cmd</span> <span class="token hvariable">msg</span><br /><br /><span class="token hvariable">port</span> <span class="token hvariable">toElm</span> <span class="token operator">:</span> <span class="token punctuation">(</span><span class="token constant">String</span> <span class="token operator">-></span> <span class="token hvariable">msg</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token constant">Sub</span> <span class="token hvariable">msg</span></code></pre>
<p>And now draw the rest of the owl.</p>
<p><em>Main.elm</em></p>
<pre class="language-elm"><code class="language-elm"><span class="token keyword">module</span> <span class="token constant">Main</span> <span class="token keyword">exposing</span> <span class="token punctuation">(</span><span class="token hvariable">main</span><span class="token punctuation">)</span><br /><br /><span class="token import-statement"><span class="token keyword">import</span> Browser</span><br /><span class="token import-statement"><span class="token keyword">import</span> Html <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span><br /><span class="token import-statement"><span class="token keyword">import</span> Html.Attributes <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span><br /><span class="token import-statement"><span class="token keyword">import</span> Html.Events <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span><br /><span class="token import-statement"><span class="token keyword">import</span> Http</span><br /><span class="token import-statement"><span class="token keyword">import</span> Json.Decode</span><br /><span class="token import-statement"><span class="token keyword">import</span> Models <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token constant">Model</span><span class="token punctuation">)</span><br /><span class="token import-statement"><span class="token keyword">import</span> Msg <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span><br /><span class="token import-statement"><span class="token keyword">import</span> Ports</span><br /><br /><span class="token hvariable">main</span> <span class="token operator">:</span> <span class="token constant">Program</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token constant">Model</span> <span class="token constant">Msg</span><br /><span class="token hvariable">main</span> <span class="token operator">=</span> <span class="token hvariable">Browser.element</span><br /> <span class="token punctuation">{</span> <span class="token hvariable">init</span> <span class="token operator">=</span> <span class="token hvariable">init</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">subscriptions</span> <span class="token operator">=</span> <span class="token hvariable">subscriptions</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">update</span> <span class="token operator">=</span> <span class="token hvariable">update</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">view</span> <span class="token operator">=</span> <span class="token hvariable">view</span><br /> <span class="token punctuation">}</span><br /><br /><span class="token comment">------------------------</span><br /><span class="token hvariable">init</span> <span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token punctuation">(</span><span class="token constant">Model</span><span class="token punctuation">,</span> <span class="token constant">Cmd</span> <span class="token constant">Msg</span><span class="token punctuation">)</span><br /><span class="token hvariable">init</span> _ <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token hvariable">Models.init</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">Cmd.none</span><br /> <span class="token punctuation">)</span><br /><br /> <br /><span class="token hvariable">subscriptions</span> <span class="token operator">:</span> <span class="token constant">Model</span> <span class="token operator">-></span> <span class="token constant">Sub</span> <span class="token constant">Msg</span><br /><span class="token hvariable">subscriptions</span> <span class="token hvariable">model</span> <span class="token operator">=</span><br /> <span class="token hvariable">Sub.batch</span> <span class="token punctuation">[</span> <span class="token hvariable">Ports.toElm</span> <span class="token constant">UpdateString</span><br /> <span class="token punctuation">]</span><br /><br /><span class="token hvariable">update</span> <span class="token operator">:</span> <span class="token constant">Msg</span> <span class="token operator">-></span> <span class="token constant">Model</span> <span class="token operator">-></span> <span class="token punctuation">(</span><span class="token constant">Model</span><span class="token punctuation">,</span> <span class="token constant">Cmd</span> <span class="token constant">Msg</span><span class="token punctuation">)</span><br /><span class="token hvariable">update</span> <span class="token hvariable">msg</span> <span class="token hvariable">model</span> <span class="token operator">=</span><br /> <span class="token keyword">case</span> <span class="token hvariable">msg</span> <span class="token keyword">of</span><br /> <span class="token constant">SendString</span> <span class="token hvariable">str</span> <span class="token operator">-></span> <span class="token punctuation">{</span> <span class="token hvariable">model</span> <span class="token operator">|</span> <span class="token hvariable">str</span> <span class="token operator">=</span> <span class="token hvariable">str</span> <span class="token punctuation">}</span><br /> <span class="token operator">|></span> \<span class="token hvariable">m</span> <span class="token operator">-></span> <span class="token punctuation">(</span> <span class="token hvariable">m</span><span class="token punctuation">,</span> <span class="token hvariable">Ports.toReason</span> <span class="token hvariable">m</span><span class="token punctuation">.</span><span class="token hvariable">str</span> <span class="token punctuation">)</span><br /> <span class="token constant">UpdateString</span> <span class="token hvariable">val</span> <span class="token operator">-></span> <span class="token punctuation">{</span> <span class="token hvariable">model</span> <span class="token operator">|</span> <span class="token hvariable">str</span> <span class="token operator">=</span> <span class="token hvariable">val</span> <span class="token punctuation">}</span><br /> <span class="token operator">|></span> \<span class="token hvariable">m</span> <span class="token operator">-></span> <span class="token punctuation">(</span><span class="token hvariable">m</span><span class="token punctuation">,</span> <span class="token hvariable">Cmd.none</span><span class="token punctuation">)</span><br /> <br /><span class="token hvariable">view</span> <span class="token operator">:</span> <span class="token constant">Model</span> <span class="token operator">-></span> <span class="token constant">Html</span> <span class="token constant">Msg</span><br /><span class="token hvariable">view</span> <span class="token hvariable">model</span> <span class="token operator">=</span><br /> <span class="token hvariable">div</span> <span class="token punctuation">[</span> <span class="token hvariable">class</span> <span class="token string">"elm-parent"</span> <span class="token punctuation">]</span><br /> <span class="token punctuation">[</span> <span class="token hvariable">h2</span> <span class="token punctuation">[</span> <span class="token hvariable">class</span> <span class="token string">"h2"</span> <span class="token punctuation">]</span> <span class="token punctuation">[</span> <span class="token hvariable">text</span> <span class="token string">"Controlled by Elm"</span> <span class="token punctuation">]</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">input</span> <span class="token punctuation">[</span> <span class="token hvariable">placeholder</span> <span class="token string">"enter some text"</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">type_</span> <span class="token string">"text"</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">onInput</span> <span class="token constant">SendString</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">value</span> <span class="token hvariable">model</span><span class="token punctuation">.</span><span class="token hvariable">str</span><br /> <span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><br /> <span class="token punctuation">]</span><br /></code></pre>
<p>Again, I'm not going to go through every inch of this--I just want it here for
reference. As you can see, the Messages are wired up in the <code>update</code> function
and the <code>onInput</code> event, and the incoming port is wired up in the
<code>subscriptions</code>.</p>
<h3>ReasonML project setup</h3>
<p>Next up, initialize a
<a href="https://webbureaucrat.gitlab.io/posts/2020-06-10-reasonml-journey-part-i-getting-started-with-bucklescript/">new BuckleScript project</a>,
and go ahead and install
<a href="https://npmjs.org/package/bs-elm-es6/">bs-elm-es6</a> and add it to the
<code>bs-dependencies</code>.</p>
<p>Finally, open an <em><a href="http://index.re/">Index.re</a></em> file and expose the module.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">open</span> ElmES6<span class="token punctuation">;</span></code></pre>
<h4>For completeness</h4>
<p>Next, I'm going to define the logic surrounding the ports. I'll compose my
ports from these functions.</p>
<p>Explaining this code in detail is out of scope for this post. Basically, all
I'm doing is defining bindings for the basic DOM functionality I need like
getting and setting the value of an input and getting the <code>target</code> from a
JavaScript <code>event</code>.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token operator">/*</span> setup<span class="token punctuation">:</span> simple JS dom interop <span class="token operator">*/</span><br /><span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span><span class="token keyword">val</span><span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>scope <span class="token string">"document"</span><span class="token punctuation">]</span><br /><span class="token keyword">external</span> getElementById<span class="token punctuation">:</span> string <span class="token operator">=></span> Dom<span class="token punctuation">.</span>element <span class="token operator">=</span> <span class="token string">"getElementById"</span><span class="token punctuation">;</span><br /><br /><span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>get<span class="token punctuation">]</span> <span class="token keyword">external</span> getValue<span class="token punctuation">:</span> Dom<span class="token punctuation">.</span>element <span class="token operator">=></span> string <span class="token operator">=</span> <span class="token string">"value"</span><span class="token punctuation">;</span><br /><span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>set<span class="token punctuation">]</span> <span class="token keyword">external</span> setValue<span class="token punctuation">:</span> <span class="token punctuation">(</span>Dom<span class="token punctuation">.</span>element<span class="token punctuation">,</span> string<span class="token punctuation">)</span> <span class="token operator">=></span> unit <span class="token operator">=</span> <span class="token string">"value"</span><span class="token punctuation">;</span><br /><br /><span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>set<span class="token punctuation">]</span><br /><span class="token keyword">external</span> setOnInput<span class="token punctuation">:</span> <span class="token punctuation">(</span>Dom<span class="token punctuation">.</span>element<span class="token punctuation">,</span> Dom<span class="token punctuation">.</span>event <span class="token operator">=></span> unit<span class="token punctuation">)</span><br /> <span class="token operator">=></span> unit<br /> <span class="token operator">=</span> <span class="token string">"oninput"</span><span class="token punctuation">;</span><br /><br /><span class="token punctuation">[</span><span class="token operator">@</span>bs<span class="token punctuation">.</span>get<span class="token punctuation">]</span><br /><span class="token keyword">external</span> getTarget<span class="token punctuation">:</span> Dom<span class="token punctuation">.</span>event <span class="token operator">=></span> Dom<span class="token punctuation">.</span>element <span class="token operator">=</span> <span class="token string">"target"</span><span class="token punctuation">;</span><br /><br /><br /><span class="token operator">/*</span> get input element <span class="token operator">*/</span><br /><span class="token keyword">let</span> inputReason<span class="token punctuation">:</span> Dom<span class="token punctuation">.</span>element <span class="token operator">=</span> getElementById<span class="token punctuation">(</span><span class="token string">"input-reason"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /></code></pre>
<h3>Declare the ports as fields in a record</h3>
<p>Initializing the elm app requires a type parameter in the form of a record
in which each field represents a port in our elm app. The <code>ElmES6</code> package
includes two types <code>Elm.sendable('t)</code> and <code>Elm.subscribable('t)</code> so that we
can send information to our elm app and subscribe to information from it.</p>
<p>This app is a simple case with just two ports, but I'm going to take the
liberty of defining a module for this type so I can move it to a new file later
if need be.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">module</span> Ports <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token keyword">type</span> t <span class="token operator">=</span> <span class="token punctuation">{</span><br /> toElm<span class="token punctuation">:</span> Elm<span class="token punctuation">.</span>sendable<span class="token punctuation">(</span>string<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> toReason<span class="token punctuation">:</span> Elm<span class="token punctuation">.</span>subscribable<span class="token punctuation">(</span>string<span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /></code></pre>
<h3>Get a reference to the elm app</h3>
<p>Now that we have our type, we can get our app. This is should look familiar
to anyone who's written elm (v 0.19) ports in JavaScript. The <code>init</code> function
takes a record which has a single field <code>node</code> of type <code>Dom.element</code>.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token operator">/*</span> get app <span class="token operator">*/</span><br /><br /><span class="token keyword">let</span> app<span class="token punctuation">:</span> Elm<span class="token punctuation">.</span>app<span class="token punctuation">(</span>Ports<span class="token punctuation">.</span>t<span class="token punctuation">)</span> <span class="token operator">=</span><br /> Elm<span class="token punctuation">.</span>Main<span class="token punctuation">.</span>init<span class="token punctuation">(</span><span class="token punctuation">{</span> node<span class="token punctuation">:</span> getElementById<span class="token punctuation">(</span><span class="token string">"elm-target"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>The result is an <code>Elm.app</code> that gives us access to our ports, so let's use
them.</p>
<h3>Wiring up the events</h3>
<p>This looks like a lot, but all we're doing is taking the <code>Dom.element</code> named
<code>inputReason</code> and settings its <code>oninput</code> event to a function of a <code>Dom.event</code>.</p>
<p>The <code>app</code> we got earlier has a member called <code>ports</code> (just like in
elm-to-JavaScript ports), and the <code>ElmES6</code> package has a <code>send</code> binding, so
we <code>send</code> <code>event.target.value</code>, just like we would in JavaScript.</p>
<pre class="language-ocaml"><code class="language-ocaml"><br />inputReason<br /> <span class="token operator">-></span> setOnInput<span class="token punctuation">(</span>event <span class="token operator">=></span> app<span class="token punctuation">.</span>ports<span class="token punctuation">.</span>toElm<br /> <span class="token operator">-></span> Elm<span class="token punctuation">.</span>send<span class="token punctuation">(</span>event<br /> <span class="token operator">-></span> getTarget<br /> <span class="token operator">-></span> getValue<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>This next one is a little easier to follow. Here, I'm using the
<code>subscribe</code> binding to set the value of <code>inputReason</code> whenever our elm app
sends a value through the port.</p>
<pre class="language-ocaml"><code class="language-ocaml"><br />app<span class="token punctuation">.</span>ports<span class="token punctuation">.</span>toReason<br /> <span class="token operator">-></span> Elm<span class="token punctuation">.</span>subscribe<span class="token punctuation">(</span>str <span class="token operator">=></span> setValue<span class="token punctuation">(</span>inputReason<span class="token punctuation">,</span> str<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /></code></pre>
<p>Now compile to get <em>Index.bs.js</em>.</p>
<h3>Put it all together in the HTML markup</h3>
<p>Now all that's left to do is to put it all together in our HTML markup.</p>
<pre class="language-html"><code class="language-html"> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>div-reason-demo<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h2</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>h2<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Controlled by ReasonML<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h2</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>input<span class="token punctuation">"</span></span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>input-reason<span class="token punctuation">"</span></span><br /> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>enter some text<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>elm-target<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token comment"><!--end container div--></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>scripts/elm/index.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>scripts/reason/src/Index.bs.js<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>module<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span></code></pre>
<p>This gives us everything our app is expecting: 1) an "input-reason" text box,
2) an "elm-target" div, and 3) references to our scripts.</p>
<p>That finishes our project! Again, a completed example can be found
<a href="https://elmandbucklescript.gitlab.io/">here</a>, and
<a href="https://gitlab.com/elmandbucklescript/elmandbucklescript.gitlab.io">full source here</a>.</p>
<p>Let me know if you have any questions!</p>
ReScript: The Module or File Can't Be Found, Unabridged2020-09-10T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/rescript-the-module-or-file-cant-be-found/<p>If you work with BuckleScript, now ReScript, you'll likely come across the
common build error message, "The module or file $name can't be found" followed
by a few helpful suggestions for how to properly install a ReScript module.
It's a good error message, but I've found it underestimates me in my ability to
get things wrong, especially if I'm the one who wrote the missing module to
begin with. I'm going to write a guide while one of these problems is still
fresh in my mind so that I have a checklist to go through the next time I get
frustrated.</p>
<p>I'm going to start with the supply side of the module. If you're referencing
someone else's third party and can assume that their code works on a basic
level, feel free to skip ahead to "Consuming your ReScript package."</p>
<h2>If you wrote the ReScript module you're having trouble adding</h2>
<h3>Check the <code>name</code> property in <em>package.json</em></h3>
<p>Make sure your package is what you expected it to be in npm.</p>
<h3>Check the <code>namespace</code> property in <em>bsconfig.json</em></h3>
<p>The naming convention for BuckleScript is to begin the package name with
<code>bs-</code>, and the emerging naming convention for ReScript is to begin the package
name with <code>res-</code>. Meanwhile, the naming convention for modules is to drop the
prefix. (E. g., <code>bs-service-worker</code> and then <code>open ServiceWorker;</code>.) However,
when scaffolding the <code>namespace</code> attribute of <em>bsconfig.json</em> often defaults
to <code>true</code> while scaffolding, which strips special characters like "-" and
Pascal-cases the name, but otherwise leaves the prefix attached.</p>
<p>The solution is to set the namespace explicitly. Instead of <code>true</code>, use the
module name that fits the module, like <code>"ServiceWorker"</code> for
<code>bs-service-worker</code>.</p>
<h3>Make sure you've run <code>npm publish</code> recently.</h3>
<p>Consider setting up CI/CD on <code>git push</code>.</p>
<h2>Consuming your ReScript package</h2>
<h3>Make sure your package is properly installed, both places.</h3>
<p>Make sure the package is installed by npm into your <em>node_modules</em>, and make
sure the package is listed in your bsconfig.json as a dependency.</p>
<h3>Check the namespace of your package</h3>
<p>Your package's namespace is deteremined by the <code>namespace</code> attribute of the
<em>bsconfig.json</em> (as described above). You need to know it to open it.</p>
<h3>Make sure you haven't already opened the package at the top of the file</h3>
<p>Yes, this is also something I have done.</p>
<p>This closes out the list of possible causes I'm aware of so far, but I believe
that if I work hard and believe in myself I'm bound to find more ways into
this compiler error, and when I do, I'll revisit this post.</p>
Error-Free C# Part I: The Maybe Monad2020-09-15T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/error-free-csharp-part-i-the-maybe-monad/<p>Most people would probably be surprised to find that I consider myself both
a professional C# developer and a professional functional programmer. C# is
mainly an object-oriented language. The syntax isn't optimized for functional
programming. This is entirely true, but just because Microsoft is behind the
curve in supporting the new best practices in programming doesn't mean I have
to be. This series will walk through how to build and use C# code that is
guaranteed to run without any runtime errors.</p>
<p>The first class of errors to eliminate is the
<a href="https://docs.microsoft.com/en-us/dotnet/api/system.nullreferenceexception?view=netcore-3.1">NullReferenceException</a>. I want to emphasize: just because you
use a mainly object-oriented language doesn't mean you have to live with
tedious, manual null checking and
occasional <code>NullReferenceException</code>s. You don't have to, especially
if your language supports anonymous functions, and indeed C# does. We can
eliminate the possibility of NullReferenceException by wrapping nullable
values in a maybe monad.</p>
<p>In functional languages, a "maybe" is an interface over two possibilities: a
"some" which has a value and a "none" which does not. Let's start there.</p>
<p><em>Maybe.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">IMaybe<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><br /><span class="token punctuation">{</span><br /><span class="token punctuation">}</span></code></pre>
<p>Now we need to decide how to control the construction of <code>Some</code>. Often, C#
encourages throwing a
<a href="https://docs.microsoft.com/en-us/dotnet/api/system.argumentnullexception?view=netcore-3.1">NullArgumentException</a>.
However, this isn't a good practice as long as we have any other options, and
we do. We are going to use the <code>internal</code> keyword here, thus following both the
object-oriented best-practice of hiding implementations and the functional
best-practice of making invalid states unrepresentable.</p>
<p><em>Some.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Some<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IMaybe<span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">private</span> <span class="token class-name">T</span> member<span class="token punctuation">;</span><br /> <br /> <span class="token keyword">internal</span> <span class="token function">Some</span><span class="token punctuation">(</span><span class="token class-name">T</span> member<span class="token punctuation">)</span><br /> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>member <span class="token operator">=</span> member<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p><em>None.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">None<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IMaybe<span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><br /><span class="token punctuation">{</span><br /><span class="token punctuation">}</span></code></pre>
<p>Now we can add the following static class to our <em>Maybe.cs</em>. You can think of
this as the
<a href="https://dotnetcodr.com/2013/10/17/the-dont-repeat-yourself-dry-design-principle-in-net-part-1/">DRY principle</a>
of null checks. We will do this once here and never
repeat our null check anyplace else in our code.</p>
<p><em>Maybe.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">IMaybe<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><br /><span class="token punctuation">{</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Maybe</span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">public</span> <span class="token return-type class-name">IMaybe<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> <span class="token function">Factory</span><span class="token punctuation">(</span><span class="token class-name">T</span> member<span class="token punctuation">)</span><br /> <span class="token operator">=></span> member <span class="token operator">==</span> <span class="token keyword">null</span> <span class="token punctuation">?</span> <span class="token punctuation">(</span>IMaybe<span class="token operator"><</span>T<span class="token operator">></span><span class="token punctuation">)</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">None<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">?</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Some<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><span class="token punctuation">(</span>member<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Congratulations, we've just written the last-ever null check.</p>
<p>Our "maybe" interface can prevent us from trying to interact with an object
that isn't there. Let's add a method to allow for this interaction.</p>
<p><em>Maybe.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">IMaybe<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><br /><span class="token punctuation">{</span><br /> <span class="token comment">/// <summary></span><br /> <span class="token comment">/// applies `func` if and only if object exists</span><br /> <span class="token comment">/// </summary></span><br /> <span class="token comment">/// <returns>a new Some of the result, or None if this is None</returns></span><br /> <span class="token return-type class-name">IMaybe<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Map</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> func<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Maybe</span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">public</span> <span class="token return-type class-name">IMaybe<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> <span class="token function">Factory</span><span class="token punctuation">(</span><span class="token class-name">T</span> member<span class="token punctuation">)</span><br /> <span class="token operator">=></span> member <span class="token operator">==</span> <span class="token keyword">null</span> <span class="token punctuation">?</span> <span class="token punctuation">(</span>IMaybe<span class="token operator"><</span>T<span class="token operator">></span><span class="token punctuation">)</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">None<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">?</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Some<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><span class="token punctuation">(</span>member<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /></code></pre>
<p><em>Some.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Some<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IMaybe<span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">private</span> <span class="token class-name">T</span> member<span class="token punctuation">;</span><br /> <br /> <span class="token keyword">internal</span> <span class="token function">Some</span><span class="token punctuation">(</span><span class="token class-name">T</span> member<span class="token punctuation">)</span><br /> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>member <span class="token operator">=</span> member<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">IMaybe<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Map</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> func<span class="token punctuation">)</span><br /> <span class="token operator">=></span> Maybe<span class="token punctuation">.</span><span class="token function">Factory</span><span class="token punctuation">(</span><span class="token function">func</span><span class="token punctuation">(</span>member<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p><em>None.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">None<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IMaybe<span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><br /><span class="token punctuation">{</span><br /> <span class="token return-type class-name">IMaybe<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Map</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> func<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">None<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Now we can see how monads protect us. Every time we have an <code>IMaybe</code>, we can
interact with it by calling <code>.Map()</code>, and if it turns out to be a <code>None</code>, it
fails silently.</p>
<p>But what if we <em>need</em> to unwrap the value? We can do this safely by providing
a fallback function. Let's implement a new method called <code>Match</code> (because it
functions like a pattern match in functional programming).</p>
<p><em>Maybe.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">IMaybe<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><br /><span class="token punctuation">{</span><br /> <span class="token comment">/// <summary></span><br /> <span class="token comment">/// applies `func` if and only if object exists</span><br /> <span class="token comment">/// </summary></span><br /> <span class="token comment">/// <returns>a new Some of the result, or None if this is None</returns></span><br /> <span class="token return-type class-name">IMaybe<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Map</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> func<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token comment">/// <summary></span><br /> <span class="token comment">/// applies `some` if value is present or `none` if no value.</span><br /> <span class="token comment">/// </summary></span><br /> <span class="token comment">/// <returns> an unwrapped value.</returns></span><br /> <span class="token return-type class-name">TNext</span> <span class="token generic-method"><span class="token function">Match</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> some<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> none<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Maybe</span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">public</span> <span class="token return-type class-name">IMaybe<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> <span class="token function">Factory</span><span class="token punctuation">(</span><span class="token class-name">T</span> member<span class="token punctuation">)</span><br /> <span class="token operator">=></span> member <span class="token operator">==</span> <span class="token keyword">null</span> <span class="token punctuation">?</span> <span class="token punctuation">(</span>IMaybe<span class="token operator"><</span>T<span class="token operator">></span><span class="token punctuation">)</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">None<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">?</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Some<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><span class="token punctuation">(</span>member<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /></code></pre>
<p><em>Some.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Some<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IMaybe<span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">private</span> <span class="token class-name">T</span> member<span class="token punctuation">;</span><br /> <br /> <span class="token keyword">internal</span> <span class="token function">Some</span><span class="token punctuation">(</span><span class="token class-name">T</span> member<span class="token punctuation">)</span><br /> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>member <span class="token operator">=</span> member<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">IMaybe<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Map</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> func<span class="token punctuation">)</span><br /> <span class="token operator">=></span> Maybe<span class="token punctuation">.</span><span class="token function">Factory</span><span class="token punctuation">(</span><span class="token function">func</span><span class="token punctuation">(</span>member<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">TNext</span> <span class="token generic-method"><span class="token function">Match</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> some<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> none<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token function">some</span><span class="token punctuation">(</span>member<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p><em>None.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">None<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IMaybe<span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">public</span> <span class="token return-type class-name">IMaybe<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Map</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> func<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">None<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">TNext</span> <span class="token generic-method"><span class="token function">Match</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> some<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> none<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token function">none</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Now, because we started with an <code>IMaybe</code>, we don't need to worry about whether
or not we remembered to include a default value every time we unwrapped our
unsafe value--the C# compiler will simply not allow us to write this kind of
bug anymore.</p>
<p>This code can be a little clunky to work with, however, if we're safely
wrapping every nullable value. We would end up with nested <code>IMaybe</code> monads
that quickly become difficult to read and understand. We can greatly simplify
our code if we include an option to <code>FlatMap</code> our <code>IMaybe</code>s together, like so:</p>
<p><em>Maybe.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">IMaybe<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><br /><span class="token punctuation">{</span><br /> <span class="token comment">/// <summary> </span><br /> <span class="token comment">/// applies `func` and then flattens the result if the value</span><br /> <span class="token comment">/// exists.</span><br /> <span class="token comment">/// </summary></span><br /> <span class="token return-type class-name">IMaybe<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">FlatMap</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> IMaybe<span class="token punctuation"><</span>TNext<span class="token punctuation">></span><span class="token punctuation">></span></span> func<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token comment">/// <summary></span><br /> <span class="token comment">/// applies `func` if and only if object exists</span><br /> <span class="token comment">/// </summary></span><br /> <span class="token comment">/// <returns>a new Some of the result, or None if this is None</returns></span><br /> <span class="token return-type class-name">IMaybe<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Map</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> func<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token comment">/// <summary></span><br /> <span class="token comment">/// applies `some` if value is present or `none` if no value.</span><br /> <span class="token comment">/// </summary></span><br /> <span class="token comment">/// <returns> an unwrapped value.</span><br /> <span class="token return-type class-name">TNext</span> <span class="token generic-method"><span class="token function">Match</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> some<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> none<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Maybe</span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">public</span> <span class="token return-type class-name">IMaybe<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> <span class="token function">Factory</span><span class="token punctuation">(</span><span class="token class-name">T</span> member<span class="token punctuation">)</span><br /> <span class="token operator">=></span> member <span class="token operator">==</span> <span class="token keyword">null</span> <span class="token punctuation">?</span> <span class="token punctuation">(</span>IMaybe<span class="token operator"><</span>T<span class="token operator">></span><span class="token punctuation">)</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">None<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">?</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Some<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><span class="token punctuation">(</span>member<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /></code></pre>
<p><em>Some.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Some<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IMaybe<span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">private</span> <span class="token class-name">T</span> member<span class="token punctuation">;</span><br /> <br /> <span class="token keyword">internal</span> <span class="token function">Some</span><span class="token punctuation">(</span><span class="token class-name">T</span> member<span class="token punctuation">)</span><br /> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>member <span class="token operator">=</span> member<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">IMaybe<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">FlatMap</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> IMaybe<span class="token punctuation"><</span>TNext<span class="token punctuation">></span><span class="token punctuation">></span></span> func<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token function">func</span><span class="token punctuation">(</span>member<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">IMaybe<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Map</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> func<span class="token punctuation">)</span><br /> <span class="token operator">=></span> Maybe<span class="token punctuation">.</span><span class="token function">Factory</span><span class="token punctuation">(</span><span class="token function">func</span><span class="token punctuation">(</span>member<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">TNext</span> <span class="token generic-method"><span class="token function">Match</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> some<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> none<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token function">some</span><span class="token punctuation">(</span>member<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p><em>None.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">None<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IMaybe<span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">public</span> <span class="token return-type class-name">IMaybe<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">FlatMap</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> IMaybe<span class="token punctuation"><</span>TNext<span class="token punctuation">></span><span class="token punctuation">></span></span> func<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">None<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">IMaybe<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Map</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> func<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">None<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">TNext</span> <span class="token generic-method"><span class="token function">Match</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> some<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> none<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token function">none</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Now we can easily handle multiple uncertain operations in a row. Now, as long
as you make a habit of wrapping these values in the <code>IMaybe</code> monad, you will be
certain to avoid <code>NullReferenceException</code>. In my next post, I will
<a href="https://webbureaucrat.gitlab.io/posts/error-free-csharp-part-ii-functional-data-processing/">use C#
extension methods to show how to use monads in list processing.</a></p>
Error-Free C# Part II: Functional Data Processing2020-09-17T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/error-free-csharp-part-ii-functional-data-processing/<p>Mutability bugs and thread-unsafety are big problems in data processing.
Fortunately, the .NET Framework has strong support for immutable collections,
eliminating entire categories of bugs. This post will show how to use
<a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods">extension methods</a>
to create <em>even safer ways</em> to interact with with lists in C# by
building on the
<a href="https://webbureaucrat.gitlab.io/posts/error-free-csharp-part-i-the-maybe-monad/">IMaybe monad type</a> we
created in
the previous post in this series.</p>
<h3>Prerequisite: System.Collections.Immutable</h3>
<p>This post builds on the lovely
<a href="https://docs.microsoft.com/en-us/dotnet/api/system.collections.immutable?view=netcore-3.1">Systems.Collections.Immutable</a>
library in C#. Much digital ink has already been spilt in praise of the concept
of immutable collections, and I don't have anything to add, but if you're not
yet familiar, I recommend reading this
<a href="https://docs.microsoft.com/en-us/archive/msdn-magazine/2017/march/net-framework-immutable-collections">detailed post on the benefits thereof</a>.</p>
<p>This post will also use extension methods. You don't necessarily need to
already be familiar with extension methods, but if you need a supplement,
I recommend
<a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods">this Microsoft article</a>.</p>
<h3>The need for safety in data processing</h3>
<p>C#'s immutable collections lead us well on our way on our functional
programming journey, but it gives us little protection at the margins. How do
we protect ourselves in case of empty lists? How can you be sure that you
have enough <code>try</code>/<code>catch</code> blocks to handle the dreaded
<a href="https://docs.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception?view=netcore-3.1">ArgumentOutOfRangeException</a>?</p>
<p>These issues are every bit as
<a href="https://webbureaucrat.gitlab.io/posts/error-free-csharp-part-i-the-maybe-monad/">avoidable</a>
as <code>NullReferenceException</code>, and with a little extension method
magic, we'll soon forget we ever had them.</p>
<h3>Set up the extension methods class</h3>
<p>Start by instantiating a new class. Extension methods must live in static
classes, and, by convention, that static class should end in "Extensions". I'm
going to call mine <em>EnumerableExtensions</em> and apply it to the broad
<code>IEnumerable<T></code> interface (of which <code>IImmutableList<T></code> is an implementation).</p>
<p><em>EnumerableExtensions.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">EnumerableExtensions</span><br /><span class="token punctuation">{</span><br /><span class="token punctuation">}</span></code></pre>
<h3>Extension method signatures</h3>
<p>Next, let's write the signature for our extension methods. Extension method
signatures use the <code>this</code> keyword in the parameter list, which allows us to use
it as a normal object method instead of a static method.</p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">EnumerableExtensions</span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IMaybe<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">MaybeFirst</span><span class="token generic class-name"><span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">IEnumerable<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> xs<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">NotImplementedException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IMaybe<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">MaybeLast</span><span class="token generic class-name"><span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">IEnumerable<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> xs<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">NotImplementedException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<h3>The safest way to wrap methods</h3>
<p>Now let's implement methods, performing checks here so we'll be sure to have
safely wrapped monads.</p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">EnumerableExtensions</span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IMaybe<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">MaybeFirst</span><span class="token generic class-name"><span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">IEnumerable<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> xs<span class="token punctuation">)</span><br /> <span class="token operator">=></span> xs<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">?</span> Maybe<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Factory</span><span class="token generic class-name"><span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>xs<span class="token punctuation">.</span><span class="token function">First</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">None<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IMaybe<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">MaybeLast</span><span class="token generic class-name"><span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">IEnumerable<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> xs<span class="token punctuation">)</span><br /> <span class="token operator">=></span> xs<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">?</span> Maybe<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Factory</span><span class="token generic class-name"><span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>xs<span class="token punctuation">.</span><span class="token function">Last</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">None<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>There are a few ways we could have handled these two cases. We could have used
<code>Count()</code> to see if the count is greater than one, but if the <code>IEnumerable</code>
is very
massive, it could take a long time to execute. We could also have wrapped the
operation in a <code>try</code> and caught <code>System.InvalidOperationException</code>, but
throwing an exception just to catch it is somewhat computationally expensive.</p>
<p>We could also have called <code>Maybe.Factory(...)</code> directly using
<code>FirstOrDefault()</code>, but this would have produced unexpected results if the
default value of the type isn't <code>null</code>, as is the case with primitive-like
types like <code>DateTime</code> and <code>int</code>.</p>
<p><code>Any()</code>, then, is the best practice here. So as long as we stick to our
"Maybe" extension methods and avoid <code>First()</code> and <code>Last()</code>, we can feel
confident that we are always using the best practice.
We've just eliminated <code>System.InvalidOperationException</code>.</p>
<h3>Eliminating <code>System.ArgumentOutOfRangeException</code> from <code>ElementAt()</code></h3>
<p>While we're here, let's add one more extension method to eliminate exceptions
coming from <code>ElementAt()</code>. <code>ElementAt()</code> fails if there are fewer elements than
the requested index.</p>
<p>In this case, <code>Any()</code> doesn't help us, and <code>Count()</code> is still pretty
computationally expensive, so for this, simply catching the exception will
suffice.</p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">EnumerableExtensions</span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IMaybe<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">MaybeElementAt</span><span class="token generic class-name"><span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">IEnumerable<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> xs<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> i<span class="token punctuation">)</span><br /> <span class="token punctuation">{</span><br /> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> Maybe<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Factory</span><span class="token generic class-name"><span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>xs<span class="token punctuation">.</span><span class="token function">ElementAt</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><br /> <span class="token keyword">catch</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">None<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /> <br /> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IMaybe<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">MaybeFirst</span><span class="token generic class-name"><span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">IEnumerable<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> xs<span class="token punctuation">)</span><br /> <span class="token operator">=></span> xs<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">?</span> Maybe<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Factory</span><span class="token generic class-name"><span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>xs<span class="token punctuation">.</span><span class="token function">First</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">None<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IMaybe<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">MaybeLast</span><span class="token generic class-name"><span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">IEnumerable<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> xs<span class="token punctuation">)</span><br /> <span class="token operator">=></span> xs<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">?</span> Maybe<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Factory</span><span class="token generic class-name"><span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>xs<span class="token punctuation">.</span><span class="token function">Last</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">None<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<h3>Never worry about missing data again.</h3>
<p>As long as we stick to our extension methods, we always know we're using the
safest, most efficient, and most correct way to get specific
elements of a list.</p>
<p>This concludes the part of the series on IMaybe monads and eliminating
exceptions
relating to missing expected data. In my
<a href="https://webbureaucrat.gitlab.io/posts/safer-data-parsing-with-try-monads/">next post</a>,
I'll work on eliminating
other kinds of runtime exceptions using a special kind of monad called
a <em>try</em> monad.</p>
Safer Data Parsing with Try Monads2020-09-25T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/safer-data-parsing-with-try-monads/<p>I have written previously on
<a href="https://webbureaucrat.gitlab.io/posts/error-free-csharp-part-i-the-maybe-monad/">maybe monads</a> and how to
<a href="https://webbureaucrat.gitlab.io/posts/error-free-csharp-part-ii-functional-data-processing/">use them with lists</a> to eliminate the possibility of null references in an object-oriented
programming language. This standalone post
walks through how to use a more generalized kind of monad to prevent all other
kinds of unhandled exceptions, using data parsing exceptions as an example.</p>
<p>(Note: I had originally planned to include this in the previous series, but
ultimately found the information difficult to organize from that perspective.
Presenting this as a standalone article allows the reader to write a try monad
from scratch without starting from knowing anything about the previous maybe
monad, but an unfortunate side effect is that this post may feel somewhat
repetitive after the previous two posts.)</p>
<h3>The need for safer exception handling</h3>
<p>Exception handling in procedural code is both awkward and unreliable. It's
awkward like other block-level programming concepts--very verbose but also not
very easy to read. It's also unreliable because it can be difficult to predict
which operations will throw exceptions that need to be handled.</p>
<p>The Java compiler provides some help through checked exceptions, but checked
exceptions make for lengthy, awkward method signatures, and making an exception
a checked exception is optional, so there are still no guarantees.</p>
<p>For those reasons, C# avoids checked exceptions altogether, but this makes
execution unpredictable because without looking at the source, you have no way
of knowing for sure which C# functions contain unsafe operations. Even if the
exceptions are very well documented, the compiler does nothing to ensure that
you handle exceptions properly. Hence, we get absurd constructs like
<a href="https://docs.microsoft.com/en-us/dotnet/api/system.int32.tryparse?view=netcore-3.1"><code>TryParse</code></a>
which require both an out parameter and a null check.</p>
<pre class="language-csharp"><code class="language-csharp"><span class="token comment">/* SO MANY LINES OF CODE */</span><br /><span class="token class-name"><span class="token keyword">bool</span></span> success <span class="token operator">=</span> Int32<span class="token punctuation">.</span><span class="token function">TryParse</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">,</span> <span class="token keyword">out</span> number<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token keyword">if</span> <span class="token punctuation">(</span>success<span class="token punctuation">)</span><br /><span class="token punctuation">{</span><br /> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"Converted '{0}' to {1}."</span><span class="token punctuation">,</span> <span class="token keyword">value</span><span class="token punctuation">,</span> number<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><span class="token keyword">else</span><br /><span class="token punctuation">{</span><br /> Console<br /> <span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"Attempted conversion of '{0}' failed."</span><span class="token punctuation">,</span> <span class="token keyword">value</span> <span class="token operator">??</span> <span class="token string">"<null"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>So object-oriented programmers generally just accept that sometimes their
code throws
unexpected exceptions, even if you try very hard to handle them all. This post
will show you that you do not have to accept unexpected program behavior. It
is very
possible to neatly handle every exception by introducing functional constructs
into your object-oriented code.</p>
<h3>The appeal of monads as a solution.</h3>
<p>Monads provide a wrapper around a value that may or may not exist. We can
decide to handle the exception specifically or fail silently, and our choice
will be concise, explicit, and readable.</p>
<p>In functional programming, monads are union types. Object-oriented languages
do not have union types per se, but they do have <em>interfaces</em> which can be
equivalent to union types. Interfaces are also an object-oriented best-practice
for hiding details, so we know we're getting the best of both worlds.</p>
<h3>Modeling possible states</h3>
<p>Let's start with our empty interface. This will represent both possible states:
either we have our value, or we have an exception.</p>
<p><em>Try.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">ITry<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><br /><span class="token punctuation">{</span><br /><span class="token punctuation">}</span></code></pre>
<p>Now we can implement it with our two classes. The <code>Success</code> type actually
contains the data.</p>
<p><em>Success.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Success<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">private</span> <span class="token class-name">T</span> member<span class="token punctuation">;</span><br /> <span class="token keyword">public</span> <span class="token function">Success</span><span class="token punctuation">(</span><span class="token class-name">T</span> member<span class="token punctuation">)</span><br /> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>member <span class="token operator">=</span> member<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Then, our failure type contains only an exception.</p>
<p><em>Failure.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Failure<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">private</span> <span class="token class-name">Exception</span> ex<span class="token punctuation">;</span><br /> <span class="token keyword">public</span> <span class="token function">Failure</span><span class="token punctuation">(</span><span class="token class-name">Exception</span> ex<span class="token punctuation">)</span><br /> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>ex <span class="token operator">=</span> ex<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Now that we've defined both constructors, we can write our error-trapping code.</p>
<p><em>Try.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">ITry<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><br /><span class="token punctuation">{</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Try</span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">public</span> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Factory</span><span class="token generic class-name"><span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> unsafeOperation<span class="token punctuation">)</span><br /> <span class="token punctuation">{</span><br /> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Success<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token function">unsafeOperation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><br /> <span class="token keyword">catch</span><span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Failure</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Now, we can use this <code>Factory</code> to wrap our methods that might throw exceptions
and return instances of <code>ITry<T></code> instead of <code>T</code>, which will</p>
<ol>
<li>improve the readability of our code by clearly communicating which
methods are unsafe and</li>
<li>force the calling code to decide how to handle the exception case.</li>
<li>compress our error handling into a single readable line like so:</li>
</ol>
<pre class="language-csharp"><code class="language-csharp">ITry<span class="token operator"><</span><span class="token keyword">int</span><span class="token operator">></span> <span class="token class-name"><span class="token keyword">int</span></span> n <span class="token operator">=</span> Try<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Factory</span><span class="token generic class-name"><span class="token punctuation"><</span><span class="token keyword">int</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">int</span><span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span><span class="token string">"hello, world."</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<h3>Mapping and FlatMapping</h3>
<p>Now, though, we need a way of interacting with the returned value if the
operation was successful. <code>member</code> is private to <code>Success</code>, but we can do this
by passing functions. Let's add two
methods to the interface like so:</p>
<p><em>Try.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">ITry<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><br /><span class="token punctuation">{</span><br /> <span class="token comment">/// <summary> applies func to the value if `this` was a `Success`, else</span><br /> <span class="token comment">/// fails silently</span><br /> <span class="token comment">/// </summary></span><br /> <span class="token comment">/// <returns> a new `Success` of the result of `func(t)` or a Failure</span><br /> <span class="token comment">/// </returns></span><br /> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Map</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> func<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token comment">/// <summary> applies func if `this` was a `Success` or fails silently.</span><br /> <span class="token comment">/// </summary></span><br /> <span class="token comment">/// <returns> a new `Success` if both `this` and `func` were successful</span><br /> <span class="token comment">/// or a failure if either failed.</span><br /> <span class="token comment">/// </returns></span><br /> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">FlatMap</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span><span class="token punctuation">></span></span> func<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Try</span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">public</span> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Factory</span><span class="token generic class-name"><span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> unsafeOperation<span class="token punctuation">)</span><br /> <span class="token punctuation">{</span><br /> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Success<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token function">unsafeOperation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><br /> <span class="token keyword">catch</span><span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Failure<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>This allows us to use the value if we possibly can while carrying forward
our protective cover on the value.</p>
<p>Now we can implement them safely like so:</p>
<p><em>Success.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Success<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">private</span> <span class="token class-name">T</span> member<span class="token punctuation">;</span><br /> <span class="token keyword">public</span> <span class="token function">Success</span><span class="token punctuation">(</span><span class="token class-name">T</span> member<span class="token punctuation">)</span><br /> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>member <span class="token operator">=</span> member<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Map</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> func<span class="token punctuation">)</span><br /> <span class="token operator">=></span> Try<span class="token punctuation">.</span><span class="token function">Factory</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">func</span><span class="token punctuation">(</span>member<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">FlatMap</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span><span class="token punctuation">></span></span> func<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token function">func</span><span class="token punctuation">(</span>member<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p><em>Failure.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Failure<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">private</span> <span class="token class-name">Exception</span> ex<span class="token punctuation">;</span><br /> <span class="token keyword">public</span> <span class="token function">Failure</span><span class="token punctuation">(</span><span class="token class-name">Exception</span> ex<span class="token punctuation">)</span><br /> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>ex <span class="token operator">=</span> ex<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Map</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> func<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Failure<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span><span class="token punctuation">(</span>ex<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">FlatMap</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span><span class="token punctuation">></span></span> func<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Falure<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span><span class="token punctuation">(</span>ex<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>If you're familiar with object-oriented design patterns, you might recognize
this as the null-object pattern--failures are represented by a dummy object
that silently ignores its methods, like this:</p>
<pre class="language-csharp"><code class="language-csharp"><span class="token class-name">ITry<span class="token punctuation"><</span><span class="token keyword">int</span><span class="token punctuation">></span></span> doubled <span class="token operator">=</span> Try<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Factory</span><span class="token generic class-name"><span class="token punctuation"><</span><span class="token keyword">int</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">int</span><span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span><span class="token string">"5"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Map</span><span class="token punctuation">(</span>i <span class="token operator">=></span> i <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>This is all well and good, but at a certain point, we may need to unwrap
our member value. There are two ways of doing so safely. Let's look at both
of those now.</p>
<h3>Safely unwrapping a try monad using a fallback</h3>
<p>We can unwrap our <code>ITry<T></code> and get a <code>T</code> as long as we provide a fallback.
The most obvious way of doing so is by providing the value directly, through a
method called <code>GetSafe()</code>.</p>
<p><em>Try.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">ITry<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><br /><span class="token punctuation">{</span><br /> <span class="token comment">/// <returns> the member of `this` if `this` was a `Success`, or the </span><br /> <span class="token comment">/// fallback if it was a `Failure`.</span><br /> <span class="token comment">/// </returns></span><br /> <span class="token return-type class-name">T</span> <span class="token function">GetSafe</span><span class="token punctuation">(</span><span class="token class-name">T</span> fallback<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token comment">/// <summary> applies func to the value if `this` was a `Success`, else</span><br /> <span class="token comment">/// fails silently</span><br /> <span class="token comment">/// </summary></span><br /> <span class="token comment">/// <returns> a new `Success` of the result of `func(t)` or a Failure</span><br /> <span class="token comment">/// </returns></span><br /> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Map</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> func<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token comment">/// <summary> applies func if `this` was a `Success` or fails silently.</span><br /> <span class="token comment">/// </summary></span><br /> <span class="token comment">/// <returns> a new `Success` if both `this` and `func` were successful</span><br /> <span class="token comment">/// or a failure if either failed.</span><br /> <span class="token comment">/// </returns></span><br /> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">FlatMap</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span><span class="token punctuation">></span></span> func<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Try</span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">public</span> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Factory</span><span class="token generic class-name"><span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> unsafeOperation<span class="token punctuation">)</span><br /> <span class="token punctuation">{</span><br /> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Success<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token function">unsafeOperation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><br /> <span class="token keyword">catch</span><span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Failure<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p><em>Success.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Success<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">private</span> <span class="token class-name">T</span> member<span class="token punctuation">;</span><br /> <span class="token keyword">public</span> <span class="token function">Success</span><span class="token punctuation">(</span><span class="token class-name">T</span> member<span class="token punctuation">)</span><br /> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>member <span class="token operator">=</span> member<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">T</span> <span class="token function">GetSafe</span><span class="token punctuation">(</span><span class="token class-name">T</span> fallback<span class="token punctuation">)</span> <span class="token operator">=></span> member<span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Map</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> func<span class="token punctuation">)</span><br /> <span class="token operator">=></span> Try<span class="token punctuation">.</span><span class="token function">Factory</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">func</span><span class="token punctuation">(</span>member<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">FlatMap</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span><span class="token punctuation">></span></span> func<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token function">func</span><span class="token punctuation">(</span>member<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p><em>Failure.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Failure<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">private</span> <span class="token class-name">Exception</span> ex<span class="token punctuation">;</span><br /> <span class="token keyword">public</span> <span class="token function">Failure</span><span class="token punctuation">(</span><span class="token class-name">Exception</span> ex<span class="token punctuation">)</span><br /> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>ex <span class="token operator">=</span> ex<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">T</span> <span class="token function">GetSafe</span><span class="token punctuation">(</span><span class="token class-name">T</span> fallback<span class="token punctuation">)</span> <span class="token operator">=></span> fallback<span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Map</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> func<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Failure<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span><span class="token punctuation">(</span>ex<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">FlatMap</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span><span class="token punctuation">></span></span> func<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Falure<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span><span class="token punctuation">(</span>ex<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>This is straightforward--<code>Success</code> discards the fallback and <code>Failure</code>
relies on it. This gives us error trapping and recovery in a single line, like
this:</p>
<pre class="language-csharp"><code class="language-csharp"><span class="token comment">//evaluates to zero.</span><br /><span class="token class-name"><span class="token keyword">int</span></span> i <span class="token operator">=</span> Try<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Factory</span><span class="token generic class-name"><span class="token punctuation"><</span><span class="token keyword">int</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">int</span><span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span><span class="token string">"hello, world"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetSafe</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<h3>Functional fallbacks mimicking pattern matching</h3>
<p>Suppose, however, that the fallback value was computationally expensive to
evaluate or retrieve. We would not want to compute that value and discard it
every time an operation succeeded. We can instead compute it conditionally
using functional programming.</p>
<p>Let's call our new method <code>Match</code>, like pattern matching constructs in C#. This
function will take two function parameters and execute the appropriate one.</p>
<p><em>Try.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">ITry<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><br /><span class="token punctuation">{</span><br /> <span class="token comment">/// <returns> the member of `this` if `this` was a `Success`, or the </span><br /> <span class="token comment">/// fallback if it was a `Failure`.</span><br /> <span class="token comment">/// </returns></span><br /> <span class="token return-type class-name">T</span> <span class="token function">GetSafe</span><span class="token punctuation">(</span><span class="token class-name">T</span> fallback<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token comment">/// <summary> applies func to the value if `this` was a `Success`, else</span><br /> <span class="token comment">/// fails silently</span><br /> <span class="token comment">/// </summary></span><br /> <span class="token comment">/// <returns> a new `Success` of the result of `func(t)` or a Failure</span><br /> <span class="token comment">/// </returns></span><br /> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Map</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> func<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token comment">/// <summary> applies func if `this` was a `Success` or fails silently.</span><br /> <span class="token comment">/// </summary></span><br /> <span class="token comment">/// <returns> a new `Success` if both `this` and `func` were successful</span><br /> <span class="token comment">/// or a failure if either failed.</span><br /> <span class="token comment">/// </returns></span><br /> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">FlatMap</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span><span class="token punctuation">></span></span> func<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token comment">/// <summary>applies `success` if `this was a `Success` or applies</span><br /> <span class="token comment">/// `failure` if `this` was a failure.</span><br /> <span class="token comment">/// </summary></span><br /> <span class="token comment">/// <returns> the result of whichever function executed.</returns></span><br /> <span class="token return-type class-name">TNext</span> <span class="token function">Match</span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> success<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation"><</span>Exception<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> failure<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Try</span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">public</span> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Factory</span><span class="token generic class-name"><span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> unsafeOperation<span class="token punctuation">)</span><br /> <span class="token punctuation">{</span><br /> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Success<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token function">unsafeOperation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><br /> <span class="token keyword">catch</span><span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Failure<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Now we can implement the function by applying the appropriate function and
discarding the other in each interface.</p>
<p><em>Success.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Success<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">private</span> <span class="token class-name">T</span> member<span class="token punctuation">;</span><br /> <span class="token keyword">public</span> <span class="token function">Success</span><span class="token punctuation">(</span><span class="token class-name">T</span> member<span class="token punctuation">)</span><br /> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>member <span class="token operator">=</span> member<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">T</span> <span class="token function">GetSafe</span><span class="token punctuation">(</span><span class="token class-name">T</span> fallback<span class="token punctuation">)</span> <span class="token operator">=></span> member<span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Map</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> func<span class="token punctuation">)</span><br /> <span class="token operator">=></span> Try<span class="token punctuation">.</span><span class="token function">Factory</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">func</span><span class="token punctuation">(</span>member<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">FlatMap</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span><span class="token punctuation">></span></span> func<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token function">func</span><span class="token punctuation">(</span>member<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">TNext</span> <span class="token function">Match</span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> success<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation"><</span>Exception<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> failure<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token function">success</span><span class="token punctuation">(</span>member<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p><em>Failure.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Failure<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">private</span> <span class="token class-name">Exception</span> ex<span class="token punctuation">;</span><br /> <span class="token keyword">public</span> <span class="token function">Failure</span><span class="token punctuation">(</span><span class="token class-name">Exception</span> ex<span class="token punctuation">)</span><br /> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>ex <span class="token operator">=</span> ex<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">T</span> <span class="token function">GetSafe</span><span class="token punctuation">(</span><span class="token class-name">T</span> fallback<span class="token punctuation">)</span> <span class="token operator">=></span> fallback<span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Map</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> func<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Failure<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span><span class="token punctuation">(</span>ex<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">FlatMap</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span><span class="token punctuation">></span></span> func<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Falure<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span><span class="token punctuation">(</span>ex<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">TNext</span> <span class="token function">Match</span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> success<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation"><</span>Exception<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> failure<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token function">failure</span><span class="token punctuation">(</span>ex<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Note that the second function, <code>failure</code>, allows us to interact with the
exception itself, so we can do things like log its stack trace or send alerts
just like we would in a <code>try</code>/<code>catch</code> block.</p>
<pre class="language-csharp"><code class="language-csharp"><span class="token comment">/* The long running calculation doesn't execute as long as the parse <br /> * succeeds.*/</span><br /><span class="token class-name"><span class="token keyword">int</span></span> n <span class="token operator">=</span> Try<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Factory</span><span class="token generic class-name"><span class="token punctuation"><</span><span class="token keyword">int</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">int</span><span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span><span class="token string">"5"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">Match</span><span class="token punctuation">(</span>i <span class="token operator">=></span> i<span class="token punctuation">,</span> ex <span class="token operator">=></span> <span class="token function">longRunningCalculation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<h3>Rethrowing the exception</h3>
<p>Of course, there are some errors from which we cannot or should not try to
recover. For example, if the database becomes unresponsive, the right thing
for a back-end service to do is to throw an exception so the framework will
respond to the front-end with a 500-level HTTP response. For this reason, it
is a good idea to provide an escape hatch, which we'll call <code>GetUnsafe()</code>.</p>
<p><em>Try.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">ITry<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><br /><span class="token punctuation">{</span><br /> <span class="token comment">/// <returns> the member of `this` if `this` was a `Success`, or the </span><br /> <span class="token comment">/// fallback if it was a `Failure`.</span><br /> <span class="token comment">/// </returns></span><br /> <span class="token return-type class-name">T</span> <span class="token function">GetSafe</span><span class="token punctuation">(</span><span class="token class-name">T</span> fallback<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token comment">/// <returns> the member of `this` if `this was a `Success` or throws</span><br /> <span class="token comment">/// the exception if `this` was a failure.</span><br /> <span class="token comment">/// </returns></span><br /> <span class="token return-type class-name">T</span> <span class="token function">GetUnsafe</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token comment">/// <summary> applies func to the value if `this` was a `Success`, else</span><br /> <span class="token comment">/// fails silently</span><br /> <span class="token comment">/// </summary></span><br /> <span class="token comment">/// <returns> a new `Success` of the result of `func(t)` or a Failure</span><br /> <span class="token comment">/// </returns></span><br /> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Map</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> func<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token comment">/// <summary> applies func if `this` was a `Success` or fails silently.</span><br /> <span class="token comment">/// </summary></span><br /> <span class="token comment">/// <returns> a new `Success` if both `this` and `func` were successful</span><br /> <span class="token comment">/// or a failure if either failed.</span><br /> <span class="token comment">/// </returns></span><br /> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">FlatMap</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span><span class="token punctuation">></span></span> func<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token comment">/// <summary>applies `success` if `this was a `Success` or applies</span><br /> <span class="token comment">/// `failure` if `this` was a failure.</span><br /> <span class="token comment">/// </summary></span><br /> <span class="token comment">/// <returns> the result of whichever function executed.</returns></span><br /> <span class="token return-type class-name">TNext</span> <span class="token function">Match</span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> success<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation"><</span>Exception<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> failure<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Try</span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">public</span> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Factory</span><span class="token generic class-name"><span class="token punctuation"><</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">></span></span> unsafeOperation<span class="token punctuation">)</span><br /> <span class="token punctuation">{</span><br /> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Success<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token function">unsafeOperation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><br /> <span class="token keyword">catch</span><span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Failure<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p><em>Success.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Success<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">private</span> <span class="token class-name">T</span> member<span class="token punctuation">;</span><br /> <span class="token keyword">public</span> <span class="token function">Success</span><span class="token punctuation">(</span><span class="token class-name">T</span> member<span class="token punctuation">)</span><br /> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>member <span class="token operator">=</span> member<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">T</span> <span class="token function">GetSafe</span><span class="token punctuation">(</span><span class="token class-name">T</span> fallback<span class="token punctuation">)</span> <span class="token operator">=></span> member<span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">T</span> <span class="token function">GetUnsafe</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> member<span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Map</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> func<span class="token punctuation">)</span><br /> <span class="token operator">=></span> Try<span class="token punctuation">.</span><span class="token function">Factory</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">func</span><span class="token punctuation">(</span>member<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">FlatMap</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span><span class="token punctuation">></span></span> func<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token function">func</span><span class="token punctuation">(</span>member<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">TNext</span> <span class="token function">Match</span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> success<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation"><</span>Exception<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> failure<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token function">success</span><span class="token punctuation">(</span>member<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p><em>Failure.cs</em></p>
<pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Failure<span class="token punctuation"><</span>T<span class="token punctuation">></span></span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">private</span> <span class="token class-name">Exception</span> ex<span class="token punctuation">;</span><br /> <span class="token keyword">public</span> <span class="token function">Failure</span><span class="token punctuation">(</span><span class="token class-name">Exception</span> ex<span class="token punctuation">)</span><br /> <span class="token punctuation">{</span><br /> <span class="token keyword">this</span><span class="token punctuation">.</span>ex <span class="token operator">=</span> ex<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">T</span> <span class="token function">GetSafe</span><span class="token punctuation">(</span><span class="token class-name">T</span> fallback<span class="token punctuation">)</span> <span class="token operator">=></span> fallback<span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">T</span> <span class="token function">GetUnsafe</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <br /> <span class="token operator">=></span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Exception</span><span class="token punctuation">(</span><span class="token string">"GetUnsafe failure."</span><span class="token punctuation">,</span> ex<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Map</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> func<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Failure<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span><span class="token punctuation">(</span>ex<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">FlatMap</span><span class="token generic class-name"><span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> ITry<span class="token punctuation"><</span>TNext<span class="token punctuation">></span><span class="token punctuation">></span></span> func<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Falure<span class="token punctuation"><</span>TNext<span class="token punctuation">></span></span><span class="token punctuation">(</span>ex<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token keyword">public</span> <span class="token return-type class-name">TNext</span> <span class="token function">Match</span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation"><</span>T<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> success<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation"><</span>Exception<span class="token punctuation">,</span> TNext<span class="token punctuation">></span></span> failure<span class="token punctuation">)</span><br /> <span class="token operator">=></span> <span class="token function">failure</span><span class="token punctuation">(</span>ex<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>(Side note: don't just rethrow the old <code>ex</code> without creating a new exception--
rethrowing the old will destroy important information in the stack trace you'll
want to preserve for logging.)</p>
<h4>But why tho?</h4>
<p>It's worth asking: what's the point of trapping exceptions if we're just going
to throw them again? Wouldn't it be better in these cases to let the exceptions
bubble up?</p>
<p>I bring this up to highlight the importance of readability. Unsafe code should,
at the very least, always come with a clear warning label. Throwing
an exception should never be an accident or a surprise, and if the calling code
needs to throw an exception, it should be required to do so with a function
called <code>GetUnsafe</code>.</p>
<h3>In conclusion</h3>
<p>Adopting a few functional practices can make your object-oriented life a whole
lot easier. If you like generic, highly abstract, extremely safe code like
this, keep studying functional programming and don't let object-orientation
hold you back. If you'd like to stay on this path with me, feel free to
<a href="https://webbureaucrat.gitlab.io/feed/feed.xml">subscribe to my RSS feed</a>, and in return I promise not to
write a single post this long ever again.</p>
Tracking COVID-19 in Chicago: Release Notes2020-10-19T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/chicago-test-out-moving-to-by-test-data/<p><a href="https://chicagotestout.netlify.app/">Chicago Test Out</a> is a project that use
the same datasets as the
<a href="https://www.chicago.gov/city/en/sites/covid-19/home/covid-dashboard.html">city data Daily Dashboard</a>
but features more detailed
line charts, mobile compatibility, and much faster load
times. This week, the project is undergoing several major changes, and I want
to take the opportunity to explain these changes for users.</p>
<h3>Problems with the original testing tab</h3>
<p>When I originally wrote
<a href="https://chicagotestout.netlify.app/">Chicago Test Out</a>, the city was using the
<a href="https://data.cityofchicago.org/Health-Human-Services/COVID-19-Daily-Testing-By-Person/t4hh-4ku9"><em>COVID-19 Daily Testing - By Person</em></a>
dataset, which was at the time just called <em>COVID-19 Daily Testing</em>. This
dataset counts PCR tests in the city among unique individuals. A person will
show up in the dataset once for every time the person tests negative <em>until</em>
that person tests positive. That positive test will appear in the dataset once,
and then all subsequent tests of that individual will be filtered from the
dataset going forward. This is the dataset that runs the "Testing" tab in
Chicago Test Out.</p>
<h4>The city added the <em>By Test</em> dataset</h4>
<p>However, later, the city added a second, similarly-structured dataset called
<a href="https://data.cityofchicago.org/Health-Human-Services/COVID-19-Daily-Testing-By-Test/gkdw-2tgv"><em>COVID-19 Daily Testing - By Test</em></a>
and added the "By Person" phrase to the original dataset. The newer <em>By Test</em>
dataset includes almost every PCR test, even if the person had previously
tested positive.
<a href="http://dev.cityofchicago.org/open%20data/data%20portal/2020/07/22/covid-19-test-datasets.html">According to the city</a>,
this conforms to the way that other
jurisdictions are measuring case volumes and the test positivity rate, so the
7-day rolling average of the <em>By Test</em> dataset is the official metric used by
<a href="https://www.chicago.gov/city/en/sites/covid-19/home/covid-dashboard.html">the city's daily dashboard</a>.</p>
<p><em>(Important side note: I have precisely zero qualifications in epidemiology
and medicine. I have summarized my understanding here because I believe that
is useful for users of the site, but I strongly urge the reader to follow
the above links to the official sources and let the city speak for itself.)</em></p>
<h4>The raw data chart becomes less useful</h4>
<p>The other main problem is the line chart that I initially designed in the
spring has outlived its usefulness. My original chart charted the raw
(<em>By Person</em>) positive and total PCR tests. This allowed the viewer to pretty
easily grok both the rough change in percent positive over time (by looking at
the space between those lines grow or shrink) as well as the absolute level
of cases.</p>
<p>The problem is that, fortunately, we're now doing enough tests that the total
tests dwarfs the positive tests. The two lines are now, quite consistently,
very far apart, making the change in the ratio between them difficult to
discern. It also makes the absolute change in cases difficult to read. For
example, if new cases double from 200/day to 400/day, the chart will still
mostly just show a very flat line pretty close to zero because the y-axis is
so distorted by the high test volume.</p>
<p>This is an excellent problem for Chicago to have! But it does require some
revision in the way I display these charts if they're supposed to be useful
at a high test volume.</p>
<h3>The new "Testing v2" tab</h3>
<p>Because of these issues, I've made a few judgement calls and have decided to
completely rewrite the testing tab with new datasets and newly organized
charts.</p>
<h4>Switching to the <em>By Test</em> dataset</h4>
<p>The decision to switch to the <em>By Test</em> dataset was an easy one. It's the
dataset that drives the official decisions by the city and, according to the
city, it's the dataset that's most comparable to other testing datasets
around the country. To me, it appears that the old <em>By Person</em> dataset is
mainly maintained for backwards compatibility purposes (for which I have been
grateful so far).</p>
<p>Because they both have positive tests, total tests, and a positive percentage,
I'm going to leave the old <em>By Person</em> tab up for a time so that readers will
understand the difference.</p>
<h4>Dividing up the charts</h4>
<p>Under this <em>By Test</em> dataset though, I would still face the same charting
challenges if I tried to move the old line chart as-is, so I'm taking the
opportunity to redo the charting as well.</p>
<p>The new <em>By Test</em> tab will still try to show both the positive percent and the
volume of new cases, but will do so on different charts. The first chart will
be a line chart of the percent itself, over time, and the second will show
just cases.</p>
<p>However, I'm taking the opportunity to make the cases chart just a little bit
more useful as well. Instead of showing the raw number of cases, I'm going
to move to the <em>case rate</em> per 100,000 Chicagoans. This is a more useful
metric because the case rate per capita can be used to compare the rate of
infections across other populations. For example,
<a href="https://www.chicago.gov/city/en/sites/covid-19/home/emergency-travel-order.html">Chicago's travel quarantine order</a>
applies to states based on state with a case rate over 15/100k, and the
<a href="https://covidtracking.com/">COVID Tracking Project</a>
from <em>The Atlantic</em> can show data per million (which
is an easy conversion).</p>
<p>Unfortunately, I think I am bound here to show the seven day rolling average
case rate because I can't find a case rate per 100k by the day. I don't love
having one metric on a 7 day rolling average and not others on a 7 day rolling
average, but I hope that the way I have labeled the data will suffice so that
it won't be too confusing.</p>
<h3>Even more changes to come</h3>
<p>My next move will be to better explain the data with a quick sentence and a
link to the city data portal dataset from which each chart draws its data
because readers can't, and most certainly shouldn't, take pandemic information
as gospel from some rando on the Internet like myself. I'm also planning on
linking to the source code (available
<a href="https://gitlab.com/chicagotestout/chicagotestout.gitlab.io/">on GitLab</a> either
on the footer or on a new "About" tab.</p>
<p>After the data have been sufficiently contextualized by official sources, I'm
going to go ahead and delete the old <em>By Person</em> Testing v1 tab. This will
have the added benefit of a faster load time since it the app will have one
fewer dataset to fetch.</p>
<p>Then the next priority will be an important stability enhancement--namely, a
proper JSON parser for the Hospitalizations tab, the code for which is a
horrible, hacky mess that fails if even one single number from the city is
improperly formatted. (Don't read that code. It will burn your eyes.) This
will be an important stability improvement as well as a good opportunity to
write a new post on JSON parsing. If you can't tell by the content of this
site so far, I absolutely <strong>love</strong> writing parsers.</p>
How to Upgrade from BuckleScript to ReScript2020-11-04T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/convert-to-rescript/<p>I have a weakness: I have never seen an update I didn't want to adopt
immediately. Betas? No thanks. Give me the nightly build. New major version of
the Linux kernel? I'll just run the installation without stopping to grab my
charger. So when BuckleScript announced a whole new language syntax (along
with a rebrand to "ReScript"), I got
excited and immediately wanted to transpile everything. This post
will document the simple process step-by-step.</p>
<h3>The need for the upgrade</h3>
<p>To be honest, there isn't much of one. As the ReScript package maintainers have
emphasized, the new compiler will not break code in the old syntax, as long
as that old syntax is still in <em>.re</em> files.</p>
<p>I have as good a reason as any, though. I have a BuckleScript package
called <a href="https://npmjs.com/package/bs-elm-es6">bs-elm-es6</a>, and I
want new users who are unfamiliar with the old syntax to be able to read it, so
I transpiled it to the new ReScript syntax and pushed it to a new package
called <a href="https://npmjs.com/package/res-elm">res-elm</a>. I also have a
<a href="https://elmandbucklescript.gitlab.io/">demo website</a>
for <code>bs-elm-es6</code> that functions as a project template and is also an important
part of its documentation. I want to continue this template/documentations for
users of <code>res-elm</code> (like myself), so that's what I'm going to start with here.</p>
<h3>Upgrading to the ReScript compiler</h3>
<p>I'd like to leave the BuckleScript version for historical reasons just in case
there are users who aren't ready to make the switch, so I forked the repository
rather than push drastic changes to the old one. To make the page work in
GitLab pages, I created a new group called
<a href="https://gitlab.com/elmandrescript">elmandrescript</a> and created a new
repository to match the domain,
<a href="https://elmandrescript.gitlab.io/">elmandrescript.gitlab.io</a> Then I cloned
that repository to my local workbench.</p>
<p>The next step, for me, is to navigate to my local BuckleScript repository and
do a quick sanity check.</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> run build</code></pre>
<p>This should succeed, but it never hurts to make sure it does before you make
changes.</p>
<p>Next, it's time to upgrade the compiler. For me, the easiest way to
upgrade an npm
package is just to uninstall and reinstall (without making any other changes in
between.</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> r bs-platform<br /><span class="token function">npm</span> i bs-platform</code></pre>
<p>This should upgrade you to the latest version. Note: as long as both commands
succeeded, this is not a breaking change. You should make sure that
<code>bs-platform</code> is properly installed by running your build command once again.</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> run build</code></pre>
<p>This should succeed, which is a good reminder: we don't have to migrate
everything all at once. If we want, we can use the old compiler for <em>.re</em>
files that use the old syntax.</p>
<h3>Upgrading old BuckleScript code to ReScript Syntax</h3>
<p>The <code>bsc -format</code> command will translate our new code automatically. Run it on
each of your scripts.</p>
<pre class="language-bash"><code class="language-bash">npx bsc <span class="token parameter variable">-format</span> src/Index.re <span class="token operator">></span> src/Index.res</code></pre>
<p>If you build after this command using <code>npm run build</code> again, you will get a
compiler error because you now have two modules that have the same name. It is
safe to remove the old <em>.re</em> file.</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">rm</span> src/Index.re<br /><span class="token function">npm</span> run build</code></pre>
<p>Now your project should build successfully.</p>
<h3>Review the new <em>.res</em> file and revise</h3>
<p>It's worth reviewing your new code and looking at the changes. In particular,
the transpiler sometimes deletes whitespace that I prefer to have. (I'm a
stickler for 80-character lines, which is a challenge in ReScript). It also
deletes semicolons at the ends of lines because semicolons are optional in
ReScript.</p>
<p>I like semicolons, and the emacs <code>reason-mode</code> still needs them to determine
tab width, so I'm going to put at least some of them back.</p>
<p>I'm also going to splurge and update some other code dependencies. In
particular, I want to take this opportunity to upgrade from
<a href="https://npmjs.com/package/bs-elm-es6"><code>bs-elm-es6</code></a> to
<a href="https://npmjs.com/package/res-elm/"><code>res-elm</code></a> for a consistently readable
syntax throughout the codebase. (And mostly because <code>res-elm</code> is mine and I
really need to test it!)</p>
<h3>Enjoy the new ReScript syntax</h3>
<p>If the above steps succeeded, you have now moved successfully from BuckleScript
7 to ReScript 8. I'll update this post with the new npm package location as
soon as its available.</p>
Parsing JSON in ReScript Part I: Prerequisites and Requirements2020-11-15T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/parsing-json-in-rescript-part-i-prerequisites-and-requirements/<p>There are few things more satisfying than a slick, readable, and safe JSON
parser. It's one of the joys of functional programming. Using a good JSON
parsing pipeline can feel like magic. This series seeks to lift the veil and
empower readers (and, importantly, my future self) to build their own
customizable and extensible parsing libraries. This article, the first of
several, will be a skimmable introduction to the subject as I see it.</p>
<h3>Prerequisites</h3>
<p>Unfortunately, I'm probably not yet a skilled enough writer to write this
article for a very
junior audience. In order to follow this series, you should be fairly familiar
with</p>
<ul>
<li>ReScript syntax</li>
<li>functions as parameters</li>
<li><a href="https://rescript-lang.org/docs/manual/latest/api/belt/result"><code>Result</code></a>
monads</li>
<li>the ReScript
<a href="https://rescript-lang.org/docs/manual/latest/api/js/json">Js.Json</a>
library</li>
</ul>
<p>If you aren't familiar with all of these, feel free to read on, and if you
get stuck on something, as always, feel free to
<a href="https://gitlab.com/webbureaucrat/webbureaucrat.gitlab.io/-/issues">open an issue</a>
or <a href="https://twitter.com/webbureaucrat/">@ me</a>, and I'll take another swing at
it.</p>
<h3>Examining the need for a custom parsing solution in ReScript</h3>
<p>As always, it's reasonable to ask why I'm reinventing the wheel here. I have
a few reasons for wanting a custom solution for my use case which I will
enumerate here, but while I'm at it, I'd like to just say: I <em>really</em> love
parsers. They're fun, and you may find you like them, too.</p>
<h4>Why not just use the built-in <code>Js.Json</code> parsing methods?</h4>
<p>Let me say, first of all, that I'm going to use <code>Js.Json</code>, but if you try to
build a nontrivial parser out of the default <code>Js.Json</code> module, you'll
end up with
quite a bit of nesting, and that quickly gets difficult to manage. I would want
an additional wrapper around <code>Js.Json</code> if my objects had more than just a
couple of properties.</p>
<h4>Why not use an existing solution?</h4>
<p>A <a href="https://www.npmjs.com/search?q=bucklescript%20json">casual search of npm</a>
shows there are plenty of handy JSON parsing helpers in ReScript
(<a href="https://rescript-lang.org/blog/bucklescript-is-rebranding">formerly BuckleScript/ReasonML</a>).
They're all good! If they suit your use case, you should use one of them.
However, there may be times when it doesn't suit your use case. I have had one
of those use cases recently.</p>
<ol>
<li>The API I'm calling used dates in ISO-8601, and I needed them in both
ISO-8601 and in posix time. Libraries tend to pick one or the other.</li>
<li>The API I'm calling <a href="https://dev.socrata.com/docs/datatypes/number.html">uses numeric strings</a> instead of numbers.</li>
<li>Most libraries parse data into <code>option</code> monads, but I'd like
to have some fairly granular logging so I can quickly tell
what failed if my parser isn't
configured right or if the API ships a breaking change, so I'd like to use a
<code>Result</code> type with a string error message.</li>
</ol>
<p>The first two of these problems could be resolved if I separated my concerns
more--I could have a separate record type that contains all the fields from
the API as strings, the way the API presents it, and then define some
additional translation layer to go from API models to my data models.</p>
<p>While I understand the argument for doing something like this, I don't think
this is always the best route. Parsing date strings and numeric strings into
dates and numbers is logic that belongs in the parsing layer, not in some
additional, separate business layer.</p>
<h3>Defining the requirements of our parsing library</h3>
<p>The main point of any parsing library is to use functions to flatten the
nested logic required to cover the success and failure cases of the
decoding cases of each property. Secondly, as I've said above, I want this
library to return a Result type with a nice error message I can decide to
log if the parse fails.</p>
<p>I also want it to conform to the ReScript convention of a pipe-first
structure, and unlike many pipelines, I'd like to start with some defaults
and build the pipeline incrementally.</p>
<p>For reference, here's an example of how I'm using my own library. This is
just an abbreviated version of
<a href="https://gitlab.com/chicagotestout/chicagotestout.gitlab.io/-/blob/master/public/scripts/rescript/src/Parsing.res"><em>Parsing.res</em></a>
which defines parsers for a few models.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">open</span> Belt<span class="token punctuation">.</span>Float<span class="token punctuation">;</span> <span class="token operator">//</span> <span class="token keyword">for</span> <span class="token operator">*</span> multiplication <span class="token operator">and</span> <span class="token operator">/</span> division<br /><br /><span class="token keyword">let</span> initializeRollingCaseRate<span class="token punctuation">:</span> Models<span class="token punctuation">.</span>rawRollingCaseRate <span class="token operator">=</span> <span class="token punctuation">{</span><br /> dateStr<span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">,</span><br /> posix<span class="token punctuation">:</span> <span class="token number">0.</span><span class="token punctuation">,</span><br /> caseRate<span class="token punctuation">:</span> <span class="token number">0.</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">let</span> parseRollingCaseRate <span class="token operator">=</span><br /> <span class="token punctuation">(</span>json<span class="token punctuation">:</span> Js<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">:</span> Belt<span class="token punctuation">.</span>Result<span class="token punctuation">.</span>t<span class="token operator"><</span>Models<span class="token punctuation">.</span>rawRollingCaseRate<span class="token punctuation">,</span> string<span class="token operator">></span><br /> <span class="token operator">=></span> switch Js<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>classify<span class="token punctuation">(</span>json<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token operator">|</span> Js<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>JSONObject<span class="token punctuation">(</span>dict<span class="token punctuation">)</span> <span class="token operator">=></span> Belt<span class="token punctuation">.</span>Result<span class="token punctuation">.</span>Ok<span class="token punctuation">(</span>initializeRollingCaseRate<span class="token punctuation">)</span><br /> <span class="token operator">-></span> Decode<span class="token punctuation">.</span>req<span class="token punctuation">(</span><span class="token string">"date"</span><span class="token punctuation">,</span> Decode<span class="token punctuation">.</span>str<span class="token punctuation">,</span> dict<span class="token punctuation">,</span> <span class="token punctuation">(</span>obj<span class="token punctuation">,</span> dateStr<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token operator">..</span><span class="token punctuation">.</span>obj<span class="token punctuation">,</span><br /> dateStr<span class="token punctuation">:</span> dateStr <span class="token operator">|></span> Js<span class="token punctuation">.</span>String<span class="token punctuation">.</span>substring<span class="token punctuation">(</span><span class="token label property">~from</span><span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token label property">~to_</span><span class="token operator">=</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">//</span>strip time<span class="token punctuation">.</span><br /> <span class="token operator">-></span> Decode<span class="token punctuation">.</span>req<span class="token punctuation">(</span><span class="token string">"date"</span><span class="token punctuation">,</span> Decode<span class="token punctuation">.</span>posix<span class="token punctuation">,</span> dict<span class="token punctuation">,</span> <span class="token punctuation">(</span>obj<span class="token punctuation">,</span> posix<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token operator">..</span><span class="token punctuation">.</span>obj<span class="token punctuation">,</span> posix<span class="token punctuation">:</span> posix <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token operator">-></span> Decode<span class="token punctuation">.</span>req<span class="token punctuation">(</span><span class="token string">"cases_rate_total"</span><span class="token punctuation">,</span> Decode<span class="token punctuation">.</span>numeric<span class="token punctuation">,</span> dict<span class="token punctuation">,</span> <span class="token punctuation">(</span>obj<span class="token punctuation">,</span> caseRate<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token operator">..</span><span class="token punctuation">.</span>obj<span class="token punctuation">,</span><br /> caseRate<span class="token punctuation">:</span> caseRate <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token operator">|</span> <span class="token punctuation">_</span> <span class="token operator">=></span> Belt<span class="token punctuation">.</span>Result<span class="token punctuation">.</span>Error<span class="token punctuation">(</span><span class="token string">"Parse error: not an object. "</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">let</span> flatLog <span class="token operator">=</span> <span class="token punctuation">(</span>accumulator<span class="token punctuation">:</span> array<span class="token operator"><</span><span class="token type-variable function">'t</span><span class="token operator">></span><span class="token punctuation">,</span> item<span class="token punctuation">:</span> Belt<span class="token punctuation">.</span>Result<span class="token punctuation">.</span>t<span class="token operator"><</span><span class="token type-variable function">'t</span><span class="token punctuation">,</span> <span class="token type-variable function">'error</span><span class="token operator">></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> switch item <span class="token punctuation">{</span><br /> <span class="token operator">|</span> Belt<span class="token punctuation">.</span>Result<span class="token punctuation">.</span>Ok<span class="token punctuation">(</span>ok<span class="token punctuation">)</span> <span class="token operator">=></span> accumulator <span class="token operator">|></span> Js<span class="token punctuation">.</span>Array<span class="token punctuation">.</span>concat<span class="token punctuation">(</span> <span class="token punctuation">[</span> ok <span class="token punctuation">]</span> <span class="token punctuation">)</span><br /> <span class="token operator">|</span> Belt<span class="token punctuation">.</span>Result<span class="token punctuation">.</span>Error<span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> Js<span class="token punctuation">.</span>log<span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> accumulator<br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">let</span> parseRawRollingCaseRates <span class="token operator">=</span> <span class="token punctuation">(</span>json<span class="token punctuation">:</span> Js<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">:</span><br /> Belt<span class="token punctuation">.</span>Result<span class="token punctuation">.</span>t<span class="token operator"><</span>array<span class="token operator"><</span>Models<span class="token punctuation">.</span>rawRollingCaseRate<span class="token operator">></span><span class="token punctuation">,</span> string<span class="token operator">></span><br /> <span class="token operator">=></span> switch Js<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>classify<span class="token punctuation">(</span>json<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token operator">|</span> Js<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>JSONArray<span class="token punctuation">(</span>jsons<span class="token punctuation">)</span> <span class="token operator">=></span> Belt<span class="token punctuation">.</span>Result<span class="token punctuation">.</span>Ok<span class="token punctuation">(</span>jsons<br /> <span class="token operator">|></span> Js<span class="token punctuation">.</span>Array<span class="token punctuation">.</span>map<span class="token punctuation">(</span>parseRollingCaseRate<span class="token punctuation">)</span><br /> <span class="token operator">|></span> Js<span class="token punctuation">.</span>Array<span class="token punctuation">.</span>reduce<span class="token punctuation">(</span>flatLog<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><br /> <span class="token punctuation">)</span><br /> <span class="token operator">|</span> <span class="token punctuation">_</span> <span class="token operator">=></span> Belt<span class="token punctuation">.</span>Result<span class="token punctuation">.</span>Error<span class="token punctuation">(</span><span class="token string">"Parse issue: root not array. "</span><span class="token punctuation">)</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>There's a lot going on here (perhaps too much). Just to break it down, the
<code>req</code> function takes a <code>Result</code> of a record
model, and, if that <code>Result</code> is itself
okay, it tries to read (1) the given string-identified property using (2) the
given function that goes from a JSON object to a properly parsed member from
(3) the given dictionary and then uses that property to update the record using
(4) the given function. It returns another <code>Result</code> which can be piped into
another <code>Decode.req</code>, and the game begins again. The result is one call to
<code>Decode.req</code> for each property of the <code>Models.rawRollingCaseRate</code>
I'm decoding in <code>parseRollingCaseRate</code>.</p>
<h3>In Conclusion</h3>
<p>I hope this has been a useful introduction to my thinking on parsers to
contextualize the code I will introduce in the coming posts. The
<a href="https://webbureaucrat.gitlab.io/posts/parsing-json-in-rescript-part-ii-building-blocks/">next post</a>
will
introduce some underlying utilities that will form the building blocks of
our parsing library.</p>
Parsing JSON in ReScript Part II: Building Blocks2020-11-15T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/parsing-json-in-rescript-part-ii-building-blocks/<p>This is the second in a series of articles on how to build one's own,
general-purpose parsing library. After having established a few expectations
in the
<a href="https://webbureaucrat.gitlab.io/posts/parsing-json-in-rescript-part-i-prerequisites-and-requirements/">previous post</a>,
we are ready to begin building our utilities for our
library. Let's start with some highly generalized utilities for functional
programming.</p>
<h3>Basic Functional Utilities</h3>
<p>The first two functions here are utilities to go back and forth between
<code>Result<'a, string></code> and <code>option</code> types. This is important because
<a href="https://rescript-lang.org/docs/manual/latest/api/js/json"><code>Js.Json</code></a> uses
<code>option</code> types, but I want <code>Result</code>s with error messages.</p>
<p>The other is a utility that takes two <code>Result</code>s and a function that takes two
parameters and applies the function to the contents of both <code>Result</code>s if and
only if both <code>Result</code>s are <code>Ok</code>. Bluntly, it's <code>map</code> but with two items, so
it's <code>mapTogether</code>.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">open</span> Belt<span class="token punctuation">;</span><br /><br /><span class="token operator">/*</span> general utilities <span class="token operator">*/</span><br /><br /><span class="token keyword">let</span> toOption <span class="token operator">=</span> <span class="token punctuation">(</span>result<span class="token punctuation">:</span> Result<span class="token punctuation">.</span>t<span class="token operator"><</span><span class="token type-variable function">'t</span><span class="token punctuation">,</span> <span class="token type-variable function">'e</span><span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">:</span> option<span class="token operator"><</span><span class="token type-variable function">'t</span><span class="token operator">></span><br /> <span class="token operator">=></span> switch result <span class="token punctuation">{</span><br /> <span class="token operator">|</span> Error<span class="token punctuation">(</span><span class="token punctuation">_</span><span class="token punctuation">)</span> <span class="token operator">=></span> None<span class="token punctuation">;</span><br /> <span class="token operator">|</span> Ok<span class="token punctuation">(</span>t<span class="token punctuation">)</span> <span class="token operator">=></span> Some<span class="token punctuation">(</span>t<span class="token punctuation">)</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">let</span> toResult <span class="token operator">=</span> <span class="token punctuation">(</span>op<span class="token punctuation">:</span> option<span class="token operator"><</span><span class="token type-variable function">'a</span><span class="token operator">></span><span class="token punctuation">,</span> err<span class="token punctuation">:</span> <span class="token type-variable function">'b</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Result<span class="token punctuation">.</span>t<span class="token operator"><</span><span class="token type-variable function">'a</span><span class="token punctuation">,</span> <span class="token type-variable function">'b</span><span class="token operator">></span><br /> <span class="token operator">=></span> switch op <span class="token punctuation">{</span><br /> <span class="token operator">|</span> None <span class="token operator">=></span> Result<span class="token punctuation">.</span>Error<span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token operator">|</span> Some<span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token operator">=></span> Result<span class="token punctuation">.</span>Ok<span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">let</span> mapTogether <span class="token operator">=</span> <span class="token punctuation">(</span>first<span class="token punctuation">:</span> Result<span class="token punctuation">.</span>t<span class="token operator"><</span><span class="token type-variable function">'a</span><span class="token punctuation">,</span> <span class="token type-variable function">'error</span><span class="token operator">></span><span class="token punctuation">,</span><br /> second<span class="token punctuation">:</span> Result<span class="token punctuation">.</span>t<span class="token operator"><</span><span class="token type-variable function">'b</span><span class="token punctuation">,</span> <span class="token type-variable function">'error</span><span class="token operator">></span><span class="token punctuation">,</span><br /> func<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token type-variable function">'a</span><span class="token punctuation">,</span> <span class="token type-variable function">'b</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token type-variable function">'c</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Result<span class="token punctuation">.</span>t<span class="token operator"><</span><span class="token type-variable function">'c</span><span class="token punctuation">,</span> <span class="token type-variable function">'error</span><span class="token operator">></span><br /> <span class="token operator">=></span> Result<span class="token punctuation">.</span>flatMap<span class="token punctuation">(</span>first<span class="token punctuation">,</span> f <span class="token operator">=></span> Result<span class="token punctuation">.</span>map<span class="token punctuation">(</span>second<span class="token punctuation">,</span> s <span class="token operator">=></span> func<span class="token punctuation">(</span>f<span class="token punctuation">,</span> s<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Neither of these have a lot to do with parsers specifically, but I want them
on hand. Let's start to take things out of abstraction.</p>
<h3>Parsing Utilities</h3>
<p>Finally, we can get started on some parsing-specific code. Specifically, I
want my parsing library to have error messages, so I need a couple of functions
to help generate consistent and descriptive failure strings.</p>
<p>The first is <code>getProp</code>, which is ust a wrapper around <code>Js.Dict.get</code> that uses
our above <code>toResult</code>. It takes a dictionary and a property name, and if the
given dictionary doesn't have an entry for the property name, it will fail
with an error message that tells us which property failed. If it succeeds, it
will give us a ReScript JSON type, which we can then narrow down to our
expected type.</p>
<pre class="language-ocaml"><code class="language-ocaml"><br /><span class="token keyword">let</span> getProp <span class="token operator">=</span> <span class="token punctuation">(</span>dict<span class="token punctuation">:</span> Js<span class="token punctuation">.</span>Dict<span class="token punctuation">.</span>t<span class="token operator"><</span>Js<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>t<span class="token operator">></span><span class="token punctuation">,</span> prop<span class="token punctuation">:</span> string<span class="token punctuation">)</span><span class="token punctuation">:</span><br /> Result<span class="token punctuation">.</span>t<span class="token operator"><</span>Js<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>t<span class="token punctuation">,</span> string<span class="token operator">></span><br /> <span class="token operator">=></span> Js<span class="token punctuation">.</span>Dict<span class="token punctuation">.</span>get<span class="token punctuation">(</span>dict<span class="token punctuation">,</span> prop<span class="token punctuation">)</span><br /> <span class="token operator">-></span> toResult<span class="token punctuation">(</span>Js<span class="token punctuation">.</span>String<span class="token punctuation">.</span>concat<span class="token punctuation">(</span><span class="token string">"Parse Error: property not found: "</span><span class="token punctuation">,</span> prop<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /></code></pre>
<p>The second is a function that helps us generate descriptive errors. This
function will be called if <code>getProp</code> succeeds with a JSON, but that JSON can't
be resolved as the type we expect. All it does is generate an error like "Parse
Error: name not string." or some other combination.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">let</span> typeError <span class="token operator">=</span> <span class="token punctuation">(</span>type_<span class="token punctuation">:</span> string<span class="token punctuation">,</span> prop<span class="token punctuation">:</span> string<span class="token punctuation">)</span><span class="token punctuation">:</span> string<br /> <span class="token operator">=></span> <span class="token string">"Parse Error: "</span><br /><span class="token operator">|></span> Js<span class="token punctuation">.</span>String<span class="token punctuation">.</span>concat<span class="token punctuation">(</span>prop<span class="token punctuation">)</span><br /><span class="token operator">|></span> Js<span class="token punctuation">.</span>String<span class="token punctuation">.</span>concat<span class="token punctuation">(</span><span class="token string">" not "</span><span class="token punctuation">)</span><br /><span class="token operator">|></span> Js<span class="token punctuation">.</span>String<span class="token punctuation">.</span>concat<span class="token punctuation">(</span>type_<span class="token punctuation">)</span><br /><span class="token punctuation">;</span><br /></code></pre>
<p>Lastly, I want one more utility for dealing with more problematic numbers.
Technically, <code>NaN</code> behaves as a
number a lot of the time, but it's also, quite explicitly, well, <em>not a
number</em>. I want the option to handle these as failures instead of successes,
so I'm going to write a quick filter to turn these fake successes into
descriptive failures.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">let</span> failNaN <span class="token operator">=</span> <span class="token punctuation">(</span>number<span class="token punctuation">:</span> float<span class="token punctuation">)</span><span class="token punctuation">:</span> Result<span class="token punctuation">.</span>t<span class="token operator"><</span>float<span class="token punctuation">,</span> string<span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">if</span> Js<span class="token punctuation">.</span>Float<span class="token punctuation">.</span>isNaN<span class="token punctuation">(</span>number<span class="token punctuation">)</span> <span class="token punctuation">{</span> Result<span class="token punctuation">.</span>Error<span class="token punctuation">(</span><span class="token string">"Parse Error: yielded NaN"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><br /> <span class="token keyword">else</span> <span class="token punctuation">{</span> Result<span class="token punctuation">.</span>Ok<span class="token punctuation">(</span>number<span class="token punctuation">)</span> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<h3>In conclusion</h3>
<p>This has been a walk through of a couple of helpful utilities for building
parsers. With these out of the way, we finally <a href="https://webbureaucrat.gitlab.io/posts/parsing-json-in-rescript-part-iii-getting-to-the-point/">start building our parsing
library</a>.</p>
Parsing JSON in ReScript Part III: Getting to the Point2020-11-15T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/parsing-json-in-rescript-part-iii-getting-to-the-point/<p>After having established
<a href="https://webbureaucrat.gitlab.io/posts/parsing-json-in-rescript-part-i-prerequisites-and-requirements/">some requirements</a>
and
<a href="https://webbureaucrat.gitlab.io/posts/parsing-json-in-rescript-part-ii-building-blocks/">some basic utilities</a>,
we're
ready for the fun part: putting the pieces together. At the end of this post,
we will have our working parser.</p>
<h3>Writing our pipeline functions</h3>
<p>When we use our parsing library, we will want to declare each property of
our JSON as either required or optional, and we'll want to write logic for
every representable state. We can write functions that allow us to express
this declaratively. Let's call these two functions <code>req</code> and <code>opt</code>.</p>
<h4>Handling required JSON properties</h4>
<p>Making use of the <code>mapTogether</code> function we wrote previously, let's write the
main function that drives the calling code through the pipeline, the <code>req</code>
function.
Calling code will use the <code>req</code> function once for each property we require.</p>
<p>Before we define it, let's break down the parameters. They are:</p>
<ol>
<li><code>t</code>: a <code>Result</code> of the model we're trying to parse the JSON into.</li>
<li><code>prop</code>: the string name of the property we are defining as required.</li>
<li><code>decode</code>: a function that defines <em>how</em> we go from a dictionary
of JSONs to the proper type of our property.</li>
<li><code>dict</code>: the dictionary that (hopefully) contains the property we're
looking for.</li>
<li><code>update</code>: a function that defines how to update our model if the property
exists in the dictionary <em>and</em> the property is the type we expect.</li>
</ol>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">let</span> req <span class="token operator">=</span> <span class="token punctuation">(</span>t<span class="token punctuation">:</span> Result<span class="token punctuation">.</span>t<span class="token operator"><</span><span class="token type-variable function">'t</span><span class="token punctuation">,</span> string<span class="token operator">></span><span class="token punctuation">,</span><br /> prop<span class="token punctuation">:</span> string<span class="token punctuation">,</span><br /> decode<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>Js<span class="token punctuation">.</span>Dict<span class="token punctuation">.</span>t<span class="token operator"><</span>Js<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>t<span class="token operator">></span><span class="token punctuation">,</span> string<span class="token punctuation">)</span> <span class="token operator">=></span><br /> Result<span class="token punctuation">.</span>t<span class="token operator"><</span><span class="token type-variable function">'prop</span><span class="token punctuation">,</span> string<span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> dict<span class="token punctuation">:</span> Js<span class="token punctuation">.</span>Dict<span class="token punctuation">.</span>t<span class="token operator"><</span>Js<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>t<span class="token operator">></span><span class="token punctuation">,</span><br /> update<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token type-variable function">'t</span><span class="token punctuation">,</span> <span class="token type-variable function">'prop</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token type-variable function">'t</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Result<span class="token punctuation">.</span>t<span class="token operator"><</span><span class="token type-variable function">'t</span><span class="token punctuation">,</span> string<span class="token operator">></span><br /> <span class="token operator">=></span> mapTogether<span class="token punctuation">(</span>t<span class="token punctuation">,</span> decode<span class="token punctuation">(</span>dict<span class="token punctuation">,</span> prop<span class="token punctuation">)</span><span class="token punctuation">,</span> update<span class="token punctuation">)</span><span class="token punctuation">;</span><br /></code></pre>
<p>Again, the <code>mapTogether</code> function takes two <code>Result</code>s and a function to apply
to the contents if both of them succeed. Here, it's taking a result of the
old state of the model and a <code>Result</code> of the newly parsed property and updating
the model if both of those are successful.</p>
<h4>Handling optional JSON properties</h4>
<p>We can adapt the <code>req</code> function for optional properties. The main difference is
the last parameter: the <code>update</code> function that determines what to do with our
property will have to handle both cases using an <code>option</code> monad.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">let</span> opt <span class="token operator">=</span> <span class="token punctuation">(</span>t<span class="token punctuation">:</span> Result<span class="token punctuation">.</span>t<span class="token operator"><</span><span class="token type-variable function">'t</span><span class="token punctuation">,</span> string<span class="token operator">></span><span class="token punctuation">,</span><br /> prop<span class="token punctuation">:</span> string<span class="token punctuation">,</span><br /> decode<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>Js<span class="token punctuation">.</span>Dict<span class="token punctuation">.</span>t<span class="token operator"><</span>Js<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>t<span class="token operator">></span><span class="token punctuation">,</span> string<span class="token punctuation">)</span> <span class="token operator">=></span><br /> Result<span class="token punctuation">.</span>t<span class="token operator"><</span><span class="token type-variable function">'prop</span><span class="token punctuation">,</span> string<span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> dict<span class="token punctuation">:</span> Js<span class="token punctuation">.</span>Dict<span class="token punctuation">.</span>t<span class="token operator"><</span>Js<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>t<span class="token operator">></span><span class="token punctuation">,</span><br /> update<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token type-variable function">'t</span><span class="token punctuation">,</span> option<span class="token operator"><</span><span class="token type-variable function">'prop</span><span class="token operator">></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token type-variable function">'t</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Result<span class="token punctuation">.</span>t<span class="token operator"><</span><span class="token type-variable function">'t</span><span class="token punctuation">,</span> string<span class="token operator">></span><br /> <span class="token operator">=></span> switch t <span class="token punctuation">{</span><br /> <span class="token operator">|</span> Error<span class="token punctuation">(</span>str<span class="token punctuation">)</span> <span class="token operator">=></span> t<span class="token punctuation">;</span><br /> <span class="token operator">|</span> Ok<span class="token punctuation">(</span>obj<span class="token punctuation">)</span> <span class="token operator">=></span> Result<span class="token punctuation">.</span>Ok<span class="token punctuation">(</span>decode<span class="token punctuation">(</span>dict<span class="token punctuation">,</span> prop<span class="token punctuation">)</span> <span class="token operator">-></span> toOption <span class="token operator">|></span> update<span class="token punctuation">(</span>obj<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>If the <code>Result</code> we started with was already in an error state, we just return
that error state, but if it succeeds, then we attempt to decode the property
and run the update function either way, counting on the update function to
handle both cases.</p>
<h3>Writing a collection of <code>decode</code> functions</h3>
<p>The real meat of the parsing library, then, will be writing <code>decode</code> functions.
Users of the library will need to pass a function to the decode parameter
whenever they call <code>req</code>. Of course, any function with an appropriate type
signature will do, so users can write their own, but it'll help to include
some common ones. For example, our library
needs a function to grab floating point numbers from a dictionary, like so</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">let</span> number <span class="token operator">=</span> <span class="token punctuation">(</span>dict<span class="token punctuation">:</span> Js<span class="token punctuation">.</span>Dict<span class="token punctuation">.</span>t<span class="token operator"><</span>Js<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>t<span class="token operator">></span><span class="token punctuation">,</span> prop<span class="token punctuation">:</span> string<span class="token punctuation">)</span><span class="token punctuation">:</span><br /> Result<span class="token punctuation">.</span>t<span class="token operator"><</span>float<span class="token punctuation">,</span> string<span class="token operator">></span><br /> <span class="token operator">=></span> getProp<span class="token punctuation">(</span>dict<span class="token punctuation">,</span> prop<span class="token punctuation">)</span><br /> <span class="token operator">-></span> Result<span class="token punctuation">.</span>map<span class="token punctuation">(</span>json <span class="token operator">=></span> Js<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>decodeNumber<span class="token punctuation">(</span>json<span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token operator">-></span> Result<span class="token punctuation">.</span>flatMap<span class="token punctuation">(</span>op <span class="token operator">=></span> toResult<span class="token punctuation">(</span>op<span class="token punctuation">,</span> typeError<span class="token punctuation">(</span><span class="token string">"number"</span><span class="token punctuation">,</span> prop<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">;</span></code></pre>
<p>Our <code>number</code> parser uses the <code>getProp</code> we defined previously, and if
that succeeds,
gives us either a parsed number or our type error. Let's define some more ways
to <code>decode</code>.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">let</span> str <span class="token operator">=</span> <span class="token punctuation">(</span>dict<span class="token punctuation">:</span> Js<span class="token punctuation">.</span>Dict<span class="token punctuation">.</span>t<span class="token operator"><</span>Js<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>t<span class="token operator">></span><span class="token punctuation">,</span> prop<span class="token punctuation">:</span> string<span class="token punctuation">)</span><span class="token punctuation">:</span> Result<span class="token punctuation">.</span>t<span class="token operator"><</span>string<span class="token punctuation">,</span> string<span class="token operator">></span><br /> <span class="token operator">=></span> getProp<span class="token punctuation">(</span>dict<span class="token punctuation">,</span> prop<span class="token punctuation">)</span><br /> <span class="token operator">-></span> Result<span class="token punctuation">.</span>map<span class="token punctuation">(</span>json <span class="token operator">=></span> Js<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>decodeString<span class="token punctuation">(</span>json<span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token operator">-></span> Result<span class="token punctuation">.</span>flatMap<span class="token punctuation">(</span>op <span class="token operator">=></span> toResult<span class="token punctuation">(</span>op<span class="token punctuation">,</span> typeError<span class="token punctuation">(</span><span class="token string">"string"</span><span class="token punctuation">,</span> prop<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">;</span><br /><br /><br /><span class="token keyword">let</span> numeric <span class="token operator">=</span> <span class="token punctuation">(</span>dict<span class="token punctuation">:</span> Js<span class="token punctuation">.</span>Dict<span class="token punctuation">.</span>t<span class="token operator"><</span>Js<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>t<span class="token operator">></span><span class="token punctuation">,</span> prop<span class="token punctuation">:</span> string<span class="token punctuation">)</span><span class="token punctuation">:</span><br /> Result<span class="token punctuation">.</span>t<span class="token operator"><</span>float<span class="token punctuation">,</span> string<span class="token operator">></span><br /> <span class="token operator">=></span> str<span class="token punctuation">(</span>dict<span class="token punctuation">,</span> prop<span class="token punctuation">)</span><br /> <span class="token operator">-></span> Result<span class="token punctuation">.</span>flatMap<span class="token punctuation">(</span>str <span class="token operator">=></span> switch Belt<span class="token punctuation">.</span>Float<span class="token punctuation">.</span>fromString<span class="token punctuation">(</span>str<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token operator">|</span> Some<span class="token punctuation">(</span>number<span class="token punctuation">)</span> <span class="token operator">=></span> Result<span class="token punctuation">.</span>Ok<span class="token punctuation">(</span>number<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token operator">|</span> None <span class="token operator">=></span> Result<span class="token punctuation">.</span>Error<span class="token punctuation">(</span><span class="token string">"could not parse number from: "</span><br /> <span class="token operator">|></span> Js<span class="token punctuation">.</span>String<span class="token punctuation">.</span>concat<span class="token punctuation">(</span>str<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>It's your library--continue adding more of these functions to taste.</p>
<h3>In conclusion</h3>
<p>So this is our library, or the important parts, anyway. If you're confused or
if I've left anything out, feel free to examine
<a href="https://gitlab.com/chicagotestout/chicagotestout.gitlab.io/-/blob/master/public/scripts/rescript/src/Decode.res">my source</a>
available on GitLab. That same project uses the library in
<a href="https://gitlab.com/chicagotestout/chicagotestout.gitlab.io/-/blob/master/public/scripts/rescript/src/Parsing.res"><em>Parsing.res</em></a> if you'd like to see
an example.</p>
Continuously Deploying an NPM Package with GitLab CI/CD2020-12-02T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/continuously-deploying-an-npm-package-with-gitlab-ci-cd/<p>Setting up continuous deployment is important to me, even when publishing is
as simple as it is on npm. The
<a href="https://docs.gitlab.com/ee/user/packages/npm_registry/">official GitLab documentation</a>,
though, is a little more than I need and geared toward their own npm
repository, so I'd like to gather the information I need here in a brief
article.</p>
<h3>Generate and store an authentication token</h3>
<p>The npm team has made this straightforward.</p>
<h4>Generating the token in npm</h4>
<ol>
<li>Go to <a href="https://www.npmjs.com/">npmjs.com</a> and log in if you haven't
already.</li>
<li>Click on your profile picture at the top right.</li>
<li>Select the fifth item, "Access Tokens."</li>
<li>Click "Generate New Token" on the top right of the page.</li>
<li>Select the middle option, "automation" for the right security
settings.</li>
<li>Click "Generate Token."</li>
<li>Copy the token to your clipboard.</li>
</ol>
<h4>Storing the token in GitLab</h4>
<ol>
<li>Log into GitLab and open the project you intend to automate.</li>
<li>Select "Settings" at the bottom of the menu on the left. This will
open a
submenu.</li>
<li>Select "CI/CD."</li>
<li>Find the "Variables" section of the CI/CD menu and click "expand" on the
right.</li>
<li>Click the green "Add variable" button at the bottom.</li>
<li>Fill in the "Key" text box with "NPM_TOKEN".</li>
<li>Fill in the "Value" box with the token you copied from earlier.</li>
<li>Make sure the "Type" is set to "variable" instead of "file."</li>
<li>Make sure both checkboxes are checked to protect and
mask
the variable.</li>
</ol>
<h3>A word on security</h3>
<p>Clearly, an authentication token, especially one that controls deployment to
production, is very sensitive information, so it's worthwhile to familiarize
oneself with the protections GitLab offers.</p>
<p><strong>Masking</strong> an environment variable protects the variable from being seen in
the console output. It is easy to imagine a scenario where an error message
(or just a simple scripting mistake) could lead to this kind of information
being printed to the console, and once the toothpaste is out of the tube and
on the internet, there's no putting it back in--you have to revoke that token
and generate a new one. Masking prevents this easy-to-make security mistake.</p>
<p><strong>Protecting</strong> an environment variable is a kind of access control. A protected
environment variable can only be used in protected branches or on protected
tags, and it can't be seen by all contributors.</p>
<p>A critically sensitive authentication token like an NPM publish token should
be <strong>both</strong> protected and masked.</p>
<h3>Set up the pipeline with your <em>.gitlab.yml</em></h3>
<p>This is the easy part. Copy the following text and save it to a file called
<em>.gitlab.yml</em></p>
<p><em>.gitlab.yml</em></p>
<pre class="language-yml"><code class="language-yml"><span class="token key atrule">image</span><span class="token punctuation">:</span> node<span class="token punctuation">:</span>latest<br /><span class="token key atrule">stages</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> deploy<br /><br /><span class="token key atrule">deploy</span><span class="token punctuation">:</span><br /> <span class="token key atrule">stage</span><span class="token punctuation">:</span> deploy<br /> <span class="token key atrule">script</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> echo "//registry.npmjs.org/<span class="token punctuation">:</span>_authToken=$<span class="token punctuation">{</span>NPM_TOKEN<span class="token punctuation">}</span>" <span class="token punctuation">></span><span class="token punctuation">></span> .npmrc<br /> <span class="token punctuation">-</span> npm publish</code></pre>
<p>Just to break it down: This file grabs an image that has node installed.
It deploys by creating a file called .npmrc that defines where our registry
is and what our authentication token is based on the environment variable
<em>NPM_TOKEN</em> we created earlier. With that file in place, <code>npm publish</code> will
run.</p>
<h3>Celebrate</h3>
<p>Update your <em>package.json</em> with a fresh version number to make sure the
push succeeds, then commit and push the new <em>.gitlab.yml</em> and the edited
<em>package.json</em>. The pipeline will succeed every time you increment the
version number.</p>
Designers and front-end folks! How do you name colors?2020-12-21T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/how-do-you-name-colors/<p>In my back-end life, naming values that get reused is important to
maintaining clean code, but somehow I keep turning my CSS variables into
spaghetti. I've tried three basic strategies, none of which seem to work
quite right for me.</p>
<h3>Naming by Usage</h3>
<pre class="language-css"><code class="language-css"><span class="token selector">:root</span><br /><span class="token punctuation">{</span><br /> <span class="token property">--color-h1-background</span><span class="token punctuation">:</span> #cecec0<span class="token punctuation">;</span><br /> <span class="token property">--color-h1-text</span><span class="token punctuation">:</span> #405199<span class="token punctuation">;</span><br /> <span class="token property">--color-main-text</span><span class="token punctuation">:</span> #666258<span class="token punctuation">;</span><br /> ...<br /><span class="token punctuation">}</span></code></pre>
<p>There are a couple of problems with this one. One is: it doesn't really lend
itself to reusage of colors. If I want to use <code>--color-main-text</code> for, say,
the navigation menu background, I would have to</p>
<ol>
<li>
<p><code>.nav { background-color: var(--color-main-text); }</code><br />
This is bad because it means <code>--color-main-text</code> is misleadingly named. My
future self would try to change the color of text and end up also changing
the nav menu.</p>
</li>
<li>
<p>Define another variable with the same value. I think there's some value in
this because if all my variables are defined together at the top, I can
clearly see each place where a certain color is used. The downside is it
means if I need to make a thematic change that changed every instance of that
color, I'd have to manually change the value of each variable.</p>
</li>
</ol>
<h3>Name by color</h3>
<p>I'm tempted to name my color variables by naming the actual color. In other
words, I could name a <code>--color-brown</code> and a <code>--color-teal</code> for the particular
brown and blue that go with my theme.</p>
<p>I like this because it's probably the most readable solution--I can look at
<code>--color-brown</code> anywhere in my stylesheet and clearly call to mind the exact
shade of brown I'm using.</p>
<p>The problem is that if I ever needed to make a significant change in my theme,
let's say from a brown-based theme to a navy one, I'd have to find and
replace the variable name everyplace it was used.</p>
<h3>Abstract Naming</h3>
<p>My favorite way of naming is the most abstract--that is, naming colors based
on their role within the theme by designating one color as a primary color
and then defining <em>accents</em> and <em>highlights</em> and so forth in relation to that
theme.</p>
<p>The problem is that I too quickly run out of these kinds of names (possibly
relating to the simple fact that I don't know jack about this subject and am
just making it up as I go). So far I've come up with:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">:root</span><br /><span class="token punctuation">{</span><br /> <span class="token property">--color-primary</span><span class="token punctuation">:</span> ...<br /> <span class="token property">--color-highlight</span><span class="token punctuation">:</span> ...<br /> <span class="token property">--color-lowlight</span><span class="token punctuation">:</span> ...<br /> <span class="token property">--color-negative-space</span><span class="token punctuation">:</span> ...<br /> <span class="token property">--color-accent</span><span class="token punctuation">:</span> ...<br /><span class="token punctuation">}</span></code></pre>
<p>I like this because it seems the most open to change. I could entirely switch
themes just by changing these values, and all the variable names would still
make sense.</p>
<p>The problem is it quickly devolves as I continue to add colors, including
shades of each of these, and I end up being pushed into one of the other
strategies.</p>
<h3>Feel free to give me a hand!</h3>
<p>Do you have a naming scheme you really like? Are there more abstract color
names I should know? Am I just thinking too hard about this?</p>
<p>How do you plan for changes within your color scheme?</p>
<p><strong><a href="https://twitter.com/webburueacrat/">@ me with your help if you can!</a></strong></p>
Tracking COVID-19 Vaccinations in Chicago: Release Notes2021-01-01T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/tracking-COVID-19-vaccinations-in-chicago-release-notes/<p>Happy New Year, and thank you for reading! I am delighted to announce that
the City of Chicago has started reporting daily statistics on COVID-19
vaccinations, and so am I. This article will detail recent changes as well
as changes to come in the near future.</p>
<h3>New tab added to display vaccination data</h3>
<p>Right now, the new tab for displaying vaccination data is already in
production at <a href="https://chicagotestout.gitlab.io/">https://chicagotestout.gitlab.io/</a> with some basic information.
Right now I'm charting the total vaccinations given out per day but displaying
more detailed information on the table below.</p>
<p>At the time of this post, it looks like Chicago is administering about 3,000
vaccinations per day, all of them first vaccinations (meaning no one in the
city so far has received a complete two-dose vaccination series).</p>
<p>I've written previously on
<a href="https://webbureaucrat.gitlab.io/posts/chicago-test-out-moving-to-by-test-data">the importance of citing official sources</a>. It is my intention that all of the data on the vaccinations tab
will come from this
<a href="https://data.cityofchicago.org/Health-Human-Services/COVID-19-Daily-Vaccinations-Chicago-Residents/2vhs-cf6b">city vaccination data set</a>.</p>
<h3>Future plans include more detailed charting and useful data columns</h3>
<p>Eventually, someone somewhere in the city will get their second vaccination,
and I'll be able to meaningfully differentiate between first vaccinations and
total vaccinations in my vaccination chart. I intend to have a three line chart
containing the volume of first vaccines, final vaccines, and the total per
day.</p>
<p>I don't plan on reporting the raw volume of Chicagoans <em>cumulatively</em>
vaccinated because I don't find that number particularly interesting, but I
will add a new chart for <em>percent of Chicagoans cumulatively vaccinated</em>
because I think that's a more useful metric.</p>
<p>Perhaps more importantly, the city is graciously providing columns indicating
the percent of each demographic group that has been vaccinated, including
how many Chicagoans have been vaccinated by age group. I am not displaying
this data right now but intend to add those columns in the coming days and
weeks.</p>
<h3>Happy Reading!</h3>
<p>I started writing this site early last year to channel my fears and feel
informed and empowered in these dark times. For the first time in a long time,
my site is reporting some unmitigated good news, so I hope I can share. Stay
safe and God bless!</p>
Writing Elm Ports in ReScript2021-01-08T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/writing-elm-ports-in-rescript/<p><em>This is a
post-<a href="https://rescript-lang.org/blog/bucklescript-is-rebranding">rebrand</a>
update to my previous post,
"<a href="https://webbureaucrat.gitlab.io/posts/how-to-write-elm-ports-in-reasonml/">How to Write Elm Ports in ReasonML</a>."
I rewrote the package in the new ReScript syntax
so that people who aren't familiar with the
old syntax can still read it.</em></p>
<p>Recently I've published an npm package called
<a href="https://npmjs.org/package/res-elm/">res-elm</a> and put it into
production on a couple of projects. It's documented briefly by its README, but
I think it deserves a full post. This post will walk through how to set up
ports both into and out of an elm 0.19 project using ReScript.</p>
<h3>The Goal: shared control between ReScript and Elm through ports</h3>
<p>The final product is intended to be minimally reproducible and easy to
understand, not necessarily useful. In this case, I think the best page to
show the features of this very small library is a very small web app--an
app with two text boxes that show the ReScript app and the Elm app
communicating in real time. You can find such an app
<a href="https://elmandrescript.gitlab.io/">in this live demo</a>.</p>
<p>Take a moment to play around with the two text boxes. The first one
lives in ReScriptland, but on its input event, ReScript sends its content
into the Elm app. The second lives in Elmland, but on its input event,
sends its input to the ReScript scripts through another port. The result
are two text boxes that always match.</p>
<p>Ordinarily, I would never have a textbox that lives outside the elm
app--I'd give control of the whole view to Elm, but it's easy to imagine that
the app instead has ports to something like an IndexedDB repository, in the
case of my <a href="https://chicagotestout.gitlab.io/">Chicago area COVID-19 tracker</a>,
an HTTP call to some JSON data.</p>
<h3>Basic elm setup</h3>
<p>Detailed instructions for how to write a basic elm project is out of scope for
this kind of post, but I want some elm code here for
completeness--so that I could fully
reproduce this kind of project without having to flip back to the demo
project's source code.</p>
<p>I'll start with two basic messages <code>SendString</code> and <code>UpdateString</code> that
represent the two directions of information flow into and out of the app.</p>
<p><em>Msg.elm</em></p>
<pre class="language-elm"><code class="language-elm"><span class="token keyword">module</span> <span class="token constant">Msg</span> <span class="token keyword">exposing</span> <span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span><br /><br /><span class="token keyword">type</span> <span class="token constant">Msg</span> <span class="token operator">=</span> <span class="token constant">SendString</span> <span class="token constant">String</span><br /> <span class="token operator">|</span> <span class="token constant">UpdateString</span> <span class="token constant">String</span><br /></code></pre>
<p>If you're familiar with Elm ports already, you should be familiar with
JSON encoding/decoding in Elm ports. This is out of the scope of what
I'm trying to demonstrate, so strings here will be fine, but safely parsing
JSON is
a best practice, and you'll need it for complex data types.</p>
<p>I also want two ports on this elm app, again representing the
bidirectional flow of data into and out of this elm app.</p>
<p><em>Ports.elm</em></p>
<pre class="language-elm"><code class="language-elm"><span class="token hvariable">port</span> <span class="token keyword">module</span> <span class="token constant">Ports</span> <span class="token keyword">exposing</span> <span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span><br /><br /><span class="token hvariable">port</span> <span class="token hvariable">toReScript</span> <span class="token operator">:</span> <span class="token constant">String</span> <span class="token operator">-></span> <span class="token constant">Cmd</span> <span class="token hvariable">msg</span><br /><br /><span class="token hvariable">port</span> <span class="token hvariable">toElm</span> <span class="token operator">:</span> <span class="token punctuation">(</span><span class="token constant">String</span> <span class="token operator">-></span> <span class="token hvariable">msg</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token constant">Sub</span> <span class="token hvariable">msg</span></code></pre>
<p>And now draw the rest of the owl.</p>
<p><em>Main.elm</em></p>
<pre class="language-elm"><code class="language-elm"><span class="token keyword">module</span> <span class="token constant">Main</span> <span class="token keyword">exposing</span> <span class="token punctuation">(</span><span class="token hvariable">main</span><span class="token punctuation">)</span><br /><br /><span class="token import-statement"><span class="token keyword">import</span> Browser</span><br /><span class="token import-statement"><span class="token keyword">import</span> Html <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span><br /><span class="token import-statement"><span class="token keyword">import</span> Html.Attributes <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span><br /><span class="token import-statement"><span class="token keyword">import</span> Html.Events <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span><br /><span class="token import-statement"><span class="token keyword">import</span> Http</span><br /><span class="token import-statement"><span class="token keyword">import</span> Json.Decode</span><br /><span class="token import-statement"><span class="token keyword">import</span> Models <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token constant">Model</span><span class="token punctuation">)</span><br /><span class="token import-statement"><span class="token keyword">import</span> Msg <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span><br /><span class="token import-statement"><span class="token keyword">import</span> Ports</span><br /><br /><span class="token hvariable">main</span> <span class="token operator">:</span> <span class="token constant">Program</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token constant">Model</span> <span class="token constant">Msg</span><br /><span class="token hvariable">main</span> <span class="token operator">=</span> <span class="token hvariable">Browser.element</span><br /> <span class="token punctuation">{</span> <span class="token hvariable">init</span> <span class="token operator">=</span> <span class="token hvariable">init</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">subscriptions</span> <span class="token operator">=</span> <span class="token hvariable">subscriptions</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">update</span> <span class="token operator">=</span> <span class="token hvariable">update</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">view</span> <span class="token operator">=</span> <span class="token hvariable">view</span><br /> <span class="token punctuation">}</span><br /><br /><span class="token comment">------------------------</span><br /><span class="token hvariable">init</span> <span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token punctuation">(</span><span class="token constant">Model</span><span class="token punctuation">,</span> <span class="token constant">Cmd</span> <span class="token constant">Msg</span><span class="token punctuation">)</span><br /><span class="token hvariable">init</span> _ <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token hvariable">Models.init</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">Cmd.none</span><br /> <span class="token punctuation">)</span><br /><br /> <br /><span class="token hvariable">subscriptions</span> <span class="token operator">:</span> <span class="token constant">Model</span> <span class="token operator">-></span> <span class="token constant">Sub</span> <span class="token constant">Msg</span><br /><span class="token hvariable">subscriptions</span> <span class="token hvariable">model</span> <span class="token operator">=</span><br /> <span class="token hvariable">Sub.batch</span> <span class="token punctuation">[</span> <span class="token hvariable">Ports.toElm</span> <span class="token constant">UpdateString</span><br /> <span class="token punctuation">]</span><br /><br /><span class="token hvariable">update</span> <span class="token operator">:</span> <span class="token constant">Msg</span> <span class="token operator">-></span> <span class="token constant">Model</span> <span class="token operator">-></span> <span class="token punctuation">(</span><span class="token constant">Model</span><span class="token punctuation">,</span> <span class="token constant">Cmd</span> <span class="token constant">Msg</span><span class="token punctuation">)</span><br /><span class="token hvariable">update</span> <span class="token hvariable">msg</span> <span class="token hvariable">model</span> <span class="token operator">=</span><br /> <span class="token keyword">case</span> <span class="token hvariable">msg</span> <span class="token keyword">of</span><br /> <span class="token constant">SendString</span> <span class="token hvariable">str</span> <span class="token operator">-></span> <span class="token punctuation">{</span> <span class="token hvariable">model</span> <span class="token operator">|</span> <span class="token hvariable">str</span> <span class="token operator">=</span> <span class="token hvariable">str</span> <span class="token punctuation">}</span><br /> <span class="token operator">|></span> \<span class="token hvariable">m</span> <span class="token operator">-></span> <span class="token punctuation">(</span> <span class="token hvariable">m</span><span class="token punctuation">,</span> <span class="token hvariable">Ports.toReScript</span> <span class="token hvariable">m</span><span class="token punctuation">.</span><span class="token hvariable">str</span> <span class="token punctuation">)</span><br /> <span class="token constant">UpdateString</span> <span class="token hvariable">val</span> <span class="token operator">-></span> <span class="token punctuation">{</span> <span class="token hvariable">model</span> <span class="token operator">|</span> <span class="token hvariable">str</span> <span class="token operator">=</span> <span class="token hvariable">val</span> <span class="token punctuation">}</span><br /> <span class="token operator">|></span> \<span class="token hvariable">m</span> <span class="token operator">-></span> <span class="token punctuation">(</span><span class="token hvariable">m</span><span class="token punctuation">,</span> <span class="token hvariable">Cmd.none</span><span class="token punctuation">)</span><br /> <br /><span class="token hvariable">view</span> <span class="token operator">:</span> <span class="token constant">Model</span> <span class="token operator">-></span> <span class="token constant">Html</span> <span class="token constant">Msg</span><br /><span class="token hvariable">view</span> <span class="token hvariable">model</span> <span class="token operator">=</span><br /> <span class="token hvariable">div</span> <span class="token punctuation">[</span> <span class="token hvariable">class</span> <span class="token string">"elm-parent"</span> <span class="token punctuation">]</span><br /> <span class="token punctuation">[</span> <span class="token hvariable">h2</span> <span class="token punctuation">[</span> <span class="token hvariable">class</span> <span class="token string">"h2"</span> <span class="token punctuation">]</span> <span class="token punctuation">[</span> <span class="token hvariable">text</span> <span class="token string">"Controlled by Elm"</span> <span class="token punctuation">]</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">input</span> <span class="token punctuation">[</span> <span class="token hvariable">placeholder</span> <span class="token string">"enter some text"</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">type_</span> <span class="token string">"text"</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">onInput</span> <span class="token constant">SendString</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">value</span> <span class="token hvariable">model</span><span class="token punctuation">.</span><span class="token hvariable">str</span><br /> <span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><br /> <span class="token punctuation">]</span><br /></code></pre>
<p>Again, I'm not going to go through every inch of this--I just want it here for
reference. As you can see, the Messages are wired up in the <code>update</code> function
and the <code>onInput</code> event, and the incoming port is wired up in the
<code>subscriptions</code>.</p>
<h3>ReScript project setup</h3>
<p>Next up, initialize a new ReScript project,
and go ahead and install
<a href="https://npmjs.org/package/res-elm/">res-elm</a> and add it to the
<code>bs-dependencies</code>.</p>
<p>Finally, open an <em>Index.res</em> file and expose the module.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">open</span> Elm<span class="token punctuation">;</span></code></pre>
<h4>For completeness</h4>
<p>Next, I'm going to define the logic surrounding the ports. I'll compose my
ports from these functions.</p>
<p>Explaining this code in detail is out of scope for this post. Basically, all
I'm doing is defining bindings for the basic DOM functionality I need like
getting and setting the value of an input and getting the <code>target</code> from a
JavaScript <code>event</code>.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token operator">/*</span> setup<span class="token punctuation">:</span> simple JS dom interop <span class="token operator">*/</span><br /><span class="token operator">@</span>bs<span class="token punctuation">.</span><span class="token keyword">val</span> <span class="token operator">@</span>bs<span class="token punctuation">.</span>scope<span class="token punctuation">(</span><span class="token string">"document"</span><span class="token punctuation">)</span><br /><span class="token keyword">external</span> getElementById<span class="token punctuation">:</span> string <span class="token operator">=></span> Dom<span class="token punctuation">.</span>element <span class="token operator">=</span> <span class="token string">"getElementById"</span><br /><br /><span class="token operator">@</span>bs<span class="token punctuation">.</span>get <span class="token keyword">external</span> getValue<span class="token punctuation">:</span> Dom<span class="token punctuation">.</span>element <span class="token operator">=></span> string <span class="token operator">=</span> <span class="token string">"value"</span><br /><span class="token operator">@</span>bs<span class="token punctuation">.</span>set <span class="token keyword">external</span> setValue<span class="token punctuation">:</span> <span class="token punctuation">(</span>Dom<span class="token punctuation">.</span>element<span class="token punctuation">,</span> string<span class="token punctuation">)</span> <span class="token operator">=></span> unit <span class="token operator">=</span> <span class="token string">"value"</span><br /><br /><span class="token operator">@</span>bs<span class="token punctuation">.</span>set<br /><span class="token keyword">external</span> setOnInput<span class="token punctuation">:</span> <span class="token punctuation">(</span>Dom<span class="token punctuation">.</span>element<span class="token punctuation">,</span> Dom<span class="token punctuation">.</span>event <span class="token operator">=></span> unit<span class="token punctuation">)</span> <span class="token operator">=></span> unit <span class="token operator">=</span> <span class="token string">"oninput"</span><br /><br /><span class="token operator">@</span>bs<span class="token punctuation">.</span>get<br /><span class="token keyword">external</span> getTarget<span class="token punctuation">:</span> Dom<span class="token punctuation">.</span>event <span class="token operator">=></span> Dom<span class="token punctuation">.</span>element <span class="token operator">=</span> <span class="token string">"target"</span><br /><br /><span class="token operator">/*</span> get input element <span class="token operator">*/</span><br /><span class="token keyword">let</span> inputReScript<span class="token punctuation">:</span> Dom<span class="token punctuation">.</span>element <span class="token operator">=</span> getElementById<span class="token punctuation">(</span><span class="token string">"input-rescript"</span><span class="token punctuation">)</span><br /></code></pre>
<h3>Declare the ports as fields in a record</h3>
<p>Initializing the elm app requires a type parameter in the form of a record
in which each field represents a port in our elm app. The <code>res-elm</code> package
includes two types <code>Elm.sendable<'t></code> and <code>Elm.subscribable<'t></code> so that we
can send information to our elm app and subscribe to information from it.</p>
<p>This app is a simple case with just two ports, but I'm going to take the
liberty of defining a module for this type so I can move it to a new file later
if need be.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">module</span> Ports <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token keyword">type</span> t <span class="token operator">=</span> <span class="token punctuation">{</span><br /> toElm<span class="token punctuation">:</span> Elm<span class="token punctuation">.</span>sendable<span class="token operator"><</span>string<span class="token operator">></span><span class="token punctuation">,</span><br /> toReScript<span class="token punctuation">:</span> Elm<span class="token punctuation">.</span>subscribable<span class="token operator"><</span>string<span class="token operator">></span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /></code></pre>
<h3>Get a reference to the elm app</h3>
<p>Now that we have our type, we can get our app. This is should look familiar
to anyone who's written elm (v 0.19) ports in JavaScript. The <code>init</code> function
takes a record which has a single field <code>node</code> of type <code>Dom.element</code>.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token operator">/*</span> get app <span class="token operator">*/</span><br /><br /><span class="token keyword">let</span> app<span class="token punctuation">:</span> Elm<span class="token punctuation">.</span>app<span class="token operator"><</span>Ports<span class="token punctuation">.</span>t<span class="token operator">></span> <span class="token operator">=</span><br /> Elm<span class="token punctuation">.</span>Main<span class="token punctuation">.</span>init<span class="token punctuation">(</span><span class="token punctuation">{</span> node<span class="token punctuation">:</span> getElementById<span class="token punctuation">(</span><span class="token string">"elm-target"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>The result is an <code>Elm.app</code> that gives us access to our ports, so let's use
them.</p>
<h3>Wiring up the events</h3>
<p>This looks like a lot, but all we're doing is taking the <code>Dom.element</code> named
<code>inputReScript</code> and setting its <code>oninput</code> event to a function of a <code>Dom.event</code>.</p>
<p>The <code>app</code> we got earlier has a member called <code>ports</code> (just like in
elm-to-JavaScript ports), and the <code>Elm</code> package has a <code>send</code> binding, so
we <code>send</code> <code>event.target.value</code>, just like we would in JavaScript.</p>
<pre class="language-ocaml"><code class="language-ocaml"><br />inputReScript<br /> <span class="token operator">-></span> setOnInput<span class="token punctuation">(</span>event <span class="token operator">=></span> app<span class="token punctuation">.</span>ports<span class="token punctuation">.</span>toElm<br /> <span class="token operator">-></span> Elm<span class="token punctuation">.</span>send<span class="token punctuation">(</span>event<br /> <span class="token operator">-></span> getTarget<br /> <span class="token operator">-></span> getValue<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>This next one is a little easier to follow. Here, I'm using the
<code>subscribe</code> binding to set the value of <code>inputReScript</code> whenever our elm app
sends a value through the port.</p>
<pre class="language-ocaml"><code class="language-ocaml"><br />app<span class="token punctuation">.</span>ports<span class="token punctuation">.</span>toReScript<br /> <span class="token operator">-></span> Elm<span class="token punctuation">.</span>subscribe<span class="token punctuation">(</span>str <span class="token operator">=></span> setValue<span class="token punctuation">(</span>inputReScript<span class="token punctuation">,</span> str<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /></code></pre>
<p>Now compile to get <em>Index.bs.js</em>.</p>
<h3>Put it all together in the HTML markup</h3>
<p>Now all that's left to do is to put it all together in our HTML markup.</p>
<pre class="language-html"><code class="language-html"> ...<br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>div-rescript-demo<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h2</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>h2<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Controlled by ReScript<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h2</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>input<span class="token punctuation">"</span></span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>input-rescript<span class="token punctuation">"</span></span><br /> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>enter some text<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>elm-target<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token comment"><!--end container div--></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>scripts/elm/index.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>scripts/rescript/src/Index.bs.js<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>module<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span></code></pre>
<p>This gives us everything our app is expecting: 1) an "input-rescript" text
box, 2) an "elm-target" div, and 3) references to our scripts.</p>
<p>That finishes our project! Again, a completed example can be found
<a href="https://elmandrescript.gitlab.io/">on my demo site</a>, and
<a href="https://gitlab.com/elmandrescript/elmandrescript.gitlab.io">full source here</a>.
Let me know if you have any questions!</p>
Displaying Notifications in ReScript2021-02-26T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/displaying-notifications-in-rescript/<p>This article will serve to document and demonstrate the
<a href="https://npmjs.org/package/rescript-notifications"><code>rescript-notifications</code></a>
npm package, a complete set of bindings for the JavaScript-compiling ReScript
language
(<a href="https://rescript-lang.org/blog/bucklescript-is-rebranding">formerly BuckleScript/ReasonML</a>). At the close of this article, the reader should be able to
enable and display notifications in an entirely type safe and functional way.</p>
<p>Be advised that there are some limitations to web notifications, and I
encourage the reader to review the
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API">JavaScript Notifications API documentation</a>. TL;DR: Notifications require an HTTPS
connection, and they are widely but
<a href="https://caniuse.com/?search=notifications">not universally supported</a>.</p>
<h3>How to use this article</h3>
<p>This article is <em>both</em> a tutorial <em>and</em> a demo. While I've included the code
snippets I think are important, I also want to give a tour of the source so
that the reader can browse it in context.</p>
<ul>
<li><em>NotificationsDemo.res</em> is the ReScript source for the scripts on this page.
You can read the whole thing in context by reading the
<a href="https://gitlab.com/webbureaucrat/webbureaucrat.gitlab.io/-/blob/master/scripts/demos/src/NotificationsDemo.res">source on GitLab</a></li>
<li>It compiles to
<a href="https://webbureaucrat.gitlab.io/scripts/demos/lib/es6_global/src/NotificationsDemo.bs.js"><em>NotificationsDemo.bs.js</em></a>,
which is referenced in a script tag at the bottom of this article.</li>
<li>For completeness, the <a href="https://gitlab.com/webbureaucrat/webbureaucrat.gitlab.io/-/blob/master/src/posts/2021/displaying-notifications-in-rescript.md">source of this article</a> is also available on GitLab, although there's no magic here
and you may not need it.</li>
</ul>
<h4>A word from across the web</h4>
<p>I plan on adapting this article to be cross-posted on sites like
<a href="https://dev.to/">Dev.to</a> and
<a href="https://functional.works-hub.com/">Functional Works</a> by stripping out the
<em>demo-ness</em> of it all. However, if you're getting this from an RSS feed
reader or some such, I'm really not sure how some of this is going to end
up being displayed for you.</p>
<p>So feel free to read how you want! But if you'd like to see the notifications,
visit <a href="https://webbureaucrat.gitlab.io/posts/displaying-notifications-in-rescript">the live demo site on my blog</a>.</p>
<h3>Setting up the project</h3>
<p>You'll need to install the
<a href="https://npmjs.org/package/rescript-notifications"><code>rescript-notifications</code></a>
npm package and add it to your <em>bsconfig.json</em> in the usual way.</p>
<p>I'm also using a local clone of
<a href="https://www.npmjs.com/package/bs-webapi"><code>bs-webapi</code></a>,
although of course you can always just write your own bindings for DOM
manipulation.</p>
<p>Lastly, I just want to flag that I'm using two <code>open</code> statements like so:</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">open</span> Notifications<span class="token punctuation">;</span><br /><span class="token keyword">open</span> Webapi<span class="token punctuation">.</span>Dom<span class="token punctuation">;</span></code></pre>
<p>(I usually try to limit myself to two or three open statements per file.)</p>
<h3>Reading and requesting Notifications permissions</h3>
<p>The first thing to establish is notification permissions. We do this
through the
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Notification/permission"><code>Notification.permission</code></a>
static property and the
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Notification/requestPermission"><code>Notification.requestPermission()</code></a>
static method.</p>
<p>For example:</p>
<p><em>NotificationsDemo.res</em></p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">open</span> Notifications<span class="token punctuation">;</span><br /><span class="token keyword">open</span> Webapi<span class="token punctuation">.</span>Dom<span class="token punctuation">;</span><br /><br />window <span class="token operator">|></span> Window<span class="token punctuation">.</span>addEventListener<span class="token punctuation">(</span><span class="token string">"load"</span><span class="token punctuation">,</span> <span class="token punctuation">_</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> spanPermissions<span class="token punctuation">:</span> option<span class="token operator"><</span>Dom<span class="token punctuation">.</span>element<span class="token operator">></span> <span class="token operator">=</span> <br /> Document<span class="token punctuation">.</span>querySelector<span class="token punctuation">(</span><span class="token string">"#span-permission"</span><span class="token punctuation">,</span> document<span class="token punctuation">)</span><span class="token punctuation">;</span> <br /><br /> <span class="token keyword">let</span> setSpanText <span class="token operator">=</span> <span class="token punctuation">(</span>span<span class="token punctuation">:</span> option<span class="token operator"><</span>Dom<span class="token punctuation">.</span>element<span class="token operator">></span><span class="token punctuation">,</span> text<span class="token punctuation">:</span> string<span class="token punctuation">)</span><span class="token punctuation">:</span> unit <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> <span class="token punctuation">_</span> <span class="token operator">=</span> span <span class="token operator">-></span> Belt<span class="token punctuation">.</span>Option<span class="token punctuation">.</span>map<span class="token punctuation">(</span>span <span class="token operator">=></span> Element<span class="token punctuation">.</span>setInnerText<span class="token punctuation">(</span>span<span class="token punctuation">,</span> text<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> Js<span class="token punctuation">.</span>log<span class="token punctuation">(</span><span class="token string">"Permission text: "</span> <span class="token operator">|></span> Js<span class="token punctuation">.</span>String<span class="token punctuation">.</span>concat<span class="token punctuation">(</span>text<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">let</span> <span class="token punctuation">_</span> <span class="token operator">=</span> spanPermissions <span class="token operator">-></span> setSpanText<span class="token punctuation">(</span>Notification<span class="token punctuation">.</span>permission<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">let</span> onBtnRequestClick <span class="token operator">=</span> <span class="token punctuation">(</span>event<span class="token punctuation">:</span> Dom<span class="token punctuation">.</span>event<span class="token punctuation">)</span><span class="token punctuation">:</span> unit <span class="token operator">=></span> <span class="token punctuation">{</span><br /> Js<span class="token punctuation">.</span>log<span class="token punctuation">(</span><span class="token string">"button-request clicked."</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">let</span> <span class="token punctuation">_</span> <span class="token operator">=</span> Notification<span class="token punctuation">.</span>requestPermission<span class="token punctuation">(</span><span class="token punctuation">)</span><br /> <span class="token operator">|></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>then_<span class="token punctuation">(</span>str <span class="token operator">=></span> <span class="token punctuation">{</span><br /> spanPermissions <span class="token operator">-></span> setSpanText<span class="token punctuation">(</span>str<span class="token punctuation">)</span> <span class="token operator">|></span> Js<span class="token punctuation">.</span>Promise<span class="token punctuation">.</span>resolve<br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">let</span> <span class="token punctuation">_</span> <span class="token operator">=</span> Document<span class="token punctuation">.</span>querySelector<span class="token punctuation">(</span><span class="token string">"#button-request"</span><span class="token punctuation">,</span> document<span class="token punctuation">)</span><br /> <span class="token operator">-></span> Belt<span class="token punctuation">.</span>Option<span class="token punctuation">.</span>map<span class="token punctuation">(</span>btn <span class="token operator">=></span> <span class="token punctuation">{</span> <br /> btn <span class="token operator">|></span> Element<span class="token punctuation">.</span>addEventListener<span class="token punctuation">(</span><span class="token string">"click"</span><span class="token punctuation">,</span> onBtnRequestClick<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> Js<span class="token punctuation">.</span>log<span class="token punctuation">(</span><span class="token string">"button-request event added."</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>As you can see, this demo sets the <code>span</code> text to the current notification
permission state and then wires up an even to the button to ask for
permission and update the <code>span</code> accordingly. You can see the result below:</p>
<div class="demo">
<h3>
Current Notification Permission Status:
<span id="span-permission"></span>
</h3>
<button id="button-request">Request Permission</button>
</div>
<p>Go ahead and grant permission if you'd like--I am only using notifications
for the purposes of this demo. Ordinarily, I would hide the button after permission has been granted because
it only works once, but as a demo this is fine.</p>
<h3>Constructing notifications to display them</h3>
<p>As long as permissions are granted, notifications are displayed as soon as
they're constructed.</p>
<p>The <code>rescript-notifications</code> binding includes
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Notification/Notification">two Notification constructors</a>,
<code>makeWithoutOptions</code> and <code>makeWithOptions</code>.</p>
<p>Notifications are typed with a type parameter because they can include a
data object of any datatype. If you don't need it, you can always just use
a throwaway object of some sort.</p>
<p>The options object includes a lot of properties that the calling code won't
necessarily need (and isn't necessarily widely supported), so the library
includes an <code>init</code> function for convenience.</p>
<p><em>NotificationsDemo.res</em> continues:</p>
<pre class="language-ocaml"><code class="language-ocaml"> <span class="token keyword">let</span> onBtnNotifyClick <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">_</span><span class="token punctuation">:</span> Dom<span class="token punctuation">.</span>event<span class="token punctuation">)</span><span class="token punctuation">:</span> unit <span class="token operator">=></span> <span class="token punctuation">{</span><br /> Js<span class="token punctuation">.</span>log<span class="token punctuation">(</span><span class="token string">"button-notify clicked."</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">let</span> <span class="token punctuation">_</span> <span class="token operator">=</span> Notification<span class="token punctuation">.</span>makeWithoutOptions<span class="token punctuation">(</span><span class="token string">"You have been notified."</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">let</span> onBtnWithOptionsClick <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">_</span><span class="token punctuation">:</span> Dom<span class="token punctuation">.</span>event<span class="token punctuation">)</span><span class="token punctuation">:</span> unit <span class="token operator">=></span> <span class="token punctuation">{</span><br /> Js<span class="token punctuation">.</span>log<span class="token punctuation">(</span><span class="token string">"button-with-options clicked."</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">let</span> options<span class="token punctuation">:</span> NotificationOptions<span class="token punctuation">.</span>t<span class="token operator"><</span>string<span class="token operator">></span> <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token operator">..</span><span class="token punctuation">.</span>NotificationOptions<span class="token punctuation">.</span>init<span class="token punctuation">(</span>Js<span class="token punctuation">.</span>Nullable<span class="token punctuation">.</span>return<span class="token punctuation">(</span><span class="token string">"unused data."</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">,</span> icon<span class="token punctuation">:</span> <span class="token string">"https://webbureaucrat.gitlab.io/img/icons/192.png"</span><br /> <span class="token punctuation">,</span> body<span class="token punctuation">:</span> <span class="token string">"with an icon and additional text."</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <span class="token keyword">let</span> <span class="token punctuation">_</span> <span class="token operator">=</span> Notification<span class="token punctuation">.</span>makeWithOptions<span class="token punctuation">(</span><span class="token string">"You have been thoroughly notified"</span><span class="token punctuation">,</span><br /> options<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /> <br /> <br /> <span class="token keyword">let</span> <span class="token punctuation">_</span> <span class="token operator">=</span> Document<span class="token punctuation">.</span>querySelector<span class="token punctuation">(</span><span class="token string">"#button-notify"</span><span class="token punctuation">,</span> document<span class="token punctuation">)</span><br /> <span class="token operator">-></span> Belt<span class="token punctuation">.</span>Option<span class="token punctuation">.</span>map<span class="token punctuation">(</span>btn <span class="token operator">=></span> <span class="token punctuation">{</span><br /> btn <span class="token operator">|></span> Element<span class="token punctuation">.</span>addEventListener<span class="token punctuation">(</span><span class="token string">"click"</span><span class="token punctuation">,</span> onBtnNotifyClick<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">let</span> <span class="token punctuation">_</span> <span class="token operator">=</span> Document<span class="token punctuation">.</span>querySelector<span class="token punctuation">(</span><span class="token string">"#button-with-options"</span><span class="token punctuation">,</span> document<span class="token punctuation">)</span><br /> <span class="token operator">-></span> Belt<span class="token punctuation">.</span>Option<span class="token punctuation">.</span>map<span class="token punctuation">(</span>btn <span class="token operator">=></span> <span class="token punctuation">{</span><br /> btn <span class="token operator">|></span> Element<span class="token punctuation">.</span>addEventListener<span class="token punctuation">(</span><span class="token string">"click"</span><span class="token punctuation">,</span> onBtnWithOptionsClick<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>As you can see, the click events call the <code>make</code> methods, which, by ReScript
convention, bind to the Notification object constructor. The notifications
will appear as soon as they are constructed.</p>
<p>The feel free to play with the live demo below:</p>
<div class="demo">
<button id="button-notify">Notify without options</button>
<button id="button-with-options">Notify with options</button>
</div>
<h3>Happy notifying!</h3>
<p>This has been a quick demonstration of using JavaScript notifications in a
type-safe way using ReScript. I hope you have found this helpful and
informative, and, as always, feel free to
<a href="https://gitlab.com/webbureaucrat/webbureaucrat.gitlab.io/-/issues">reach out</a>
with questions or comments.</p>
<script src="https://webbureaucrat.gitlab.io/scripts/demos/lib/es6_global/src/NotificationsDemo.bs.js" type="module">
</script>
Setting Up Webpack for ReScript2021-03-20T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/setting-up-webpack-for-rescript/<p>As much as I <em>strongly</em> prefer ES6 modules, using them with
ReScript (<a href="https://rescript-lang.org/blog/bucklescript-is-rebranding">formerly BuckleScript / ReasonML</a>)
and ServiceWorkers just isn't practical right now. I'm writing this article so
that I can easily grab the configuration the next time I need it. This is a
beginner's guide because I am a webpack beginner, and, well, <em>everyone</em> is a
ReScript beginner right now.</p>
<h3>Some basic setup</h3>
<ol>
<li>Open your <em>bsconfig.json</em> and set the <code>module</code> property of the
<code>package-specs</code> object to <code>"commonjs"</code> if it is not already set.</li>
<li>Install webpack locally by running <code>npm i webpack webpack-cli</code>.</li>
</ol>
<h3>Configuring webpack</h3>
<p>It's important to note for your configuration <em>where</em> your javascript <em>.bs.js</em>
files are output, and this is controlled by the <code>in-source</code> property of the
same <code>package-specs</code> object in <em>bsconfig.json</em>. This guide assumes <code>in-source</code>
is <code>false</code> (because, quite frankly, that's my preference) but it means that
the <em>.bs.js</em> outputs get buried in a deeply nested folder structure.</p>
<p>This is a sample <em>webpack.config.js</em> file based on those assumptions.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> path <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'path'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br />module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">entry</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">index</span><span class="token operator">:</span> <span class="token string">"./lib/js/src/index.bs.js"</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">about</span><span class="token operator">:</span> <span class="token string">"./lib/js/src/about.bs.js"</span><br /> <span class="token punctuation">}</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">output</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token literal-property property">filename</span><span class="token operator">:</span> <span class="token string">"[name].js"</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">path</span><span class="token operator">:</span> path<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span>__dirname<span class="token punctuation">,</span> <span class="token string">"dist/"</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>This folder assumes that we should process two output files <em>index.bs.js</em> and
<em>about.bs.js</em> (and their dependencies) and then outputs each bundled file
by their name ("index" and "about") into the folder called <em>dist/</em>. The
resulting bundles are <em>dist/index.js</em> and <em>dist/about.js</em>.</p>
<h3>Including webpack in the build</h3>
<p>You're welcome to run <code>npx webpack</code> any time you want to regenerate your
bundled files, but it's a good automation practice to add it to your build
command like so:</p>
<pre class="language-json"><code class="language-json"><span class="token property">"scripts"</span><span class="token operator">:</span> <span class="token punctuation">{</span><br /> <span class="token property">"build"</span><span class="token operator">:</span> <span class="token string">"npx bsb -make-world && npx webpack"</span><span class="token punctuation">,</span><br /> <span class="token property">"start"</span><span class="token operator">:</span> <span class="token string">"npx bsb -make-world -w"</span><span class="token punctuation">,</span><br /> <span class="token property">"clean"</span><span class="token operator">:</span> <span class="token string">"npx bsb -clean-world"</span><br /><span class="token punctuation">}</span></code></pre>
<h3>In conclusion</h3>
<p>I'm still not a fan of script bundlers and avoid them wherever possible, but
when it's not possible, it's nice to have a configuration pasta on hand. In a
future article, I'll talk about my main use for webpack: ServiceWorkers.</p>
Writing Service Workers in ReScript2021-04-09T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/writing-service-workers-in-rescript/<p>Does your SPA work fully offline? Would you like to define a caching strategy
in an exhaustively type-safe way? If so, you might be interested in
<a href="https://www.npmjs.com/package/rescript-service-worker">this Service Worker binding</a>
for ReScript
(<a href="https://rescript-lang.org/blog/bucklescript-is-rebranding">formerly BuckleScript / ReasonML</a>). This article documents the binding by example, including
two different caching strategies and a service worker registration example.</p>
<h3>The need for Service Workers in ReScript</h3>
<p>JavaScript Service Workers are a way of intercepting HTTP requests in a web
app and caching the result. This allows web app developers to write
applications that work fully offline or supplement poor network speeds. They're
extremely valuable in applications that have a lot of large assets such as
SPAs (single page applications).</p>
<p>However, with great power comes great responsibility. The code that manages
all HTTP requests can't afford to have <em>any</em> bugs, but JavaScript is <em>so</em>
dynamic and <em>so</em> weakly typed that it's very easy to inadvertently ship
typos.</p>
<p>Enter ReScript, which is statically typed and more functional than TypeScript.
It's much more difficult to introduce bugs in such a robust type system, which
is why I use ReScript for all my Service Workers.</p>
<h3>Setting up your new service worker project</h3>
<p>For a couple of reasons, it's almost entirely necessary to use a script
bundler instead of ES6 modules. (It can be done with ES6 modules if you're
able to mess with the security headers and trim the export statement off the
bottom of the compiled script, but that's pretty cursed.) If you, (like me)
don't usually do this, I wrote a quick example of
<a href="https://webbureaucrat.gitlab.io/articles/setting-up-webpack-for-rescript/">setting up webpack in ReScript</a>
in a previous article.</p>
<p>In addition to a script bundler, install the <code>bs-fetch</code> and
<a href="https://www.npmjs.com/package/rescript-service-worker"><code>rescript-service-worker</code></a>
npm packages.</p>
<h3>Defining the service worker cache strategies</h3>
<p>For completeness, this tutorial will include both a cache-first and a
network-first component. This should allow the reader to easily adapt the
sample code to other strategies.</p>
<p>My strategy for this site will be as follows:</p>
<ul>
<li>If the asset is in the asset cache:
<ul>
<li>resolve with the cache version</li>
<li>update the cache version in background</li>
</ul>
</li>
<li>Else (the asset is not in the cache)
<ul>
<li>If the network can be reached
<ul>
<li>resolve with the network version</li>
<li>update the runtime cache in background with the network version</li>
</ul>
</li>
<li>Else (the network cannot be reached)
<ul>
<li>resolve with the runtime cache version</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>Basically, I have some assets like fonts and stylesheets which I don't think
will change very much very often, so I want to use a cache-first strategy for
them while keeping them up to date in background. However, I have content pages
that should be as recent as possible while being able to fail over to a cache.</p>
<h3>Bindings and configuration</h3>
<p>Personally, it's my practice to keep it to two or three open statements per
ReScript file. In this case, because Service Workers are so tied to
JavaScript Promises, it makes sense to <code>open Js;</code>.</p>
<p>I'm also going to <code>open ServiceWorker</code> because there are so many different
modules from that package I will need. I would also highly recommend
<code>open</code> ing <code>ServiceWorkerGlobalScope</code>, which, as the name suggests, exposes
bindings to native JavaScript functions and values that are exposed by
default in native JavaScript in the Service Worker context.</p>
<p>Outside of the
<a href="https://www.npmjs.com/package/rescript-service-worker"><code>rescript-service-worker</code></a>
bindings and the default <code>Js</code> bindings, there's only one more I'm going to
add, which is the binding to the
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Error">JavaScript <code>error</code> constructor</a></p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token operator">@</span>bs<span class="token punctuation">.</span><span class="token keyword">new</span> <span class="token keyword">external</span> makeExn<span class="token punctuation">:</span> string <span class="token operator">=></span> exn <span class="token operator">=</span> <span class="token string">"Error"</span><span class="token punctuation">;</span></code></pre>
<p>This will be used in a <code>Promise</code> constructor (basically to make the types
work out).</p>
<h4>Configuration</h4>
<p>The next few lines represent some basic configuration for the app. There are
two caches, which we will name "static" and "runtime" and we will version them
in order to more quickly pick up changes when we need to.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token operator">/*</span> configuration <span class="token operator">*/</span><br /><span class="token keyword">let</span> version <span class="token operator">=</span> <span class="token string">"0.0.10"</span><span class="token punctuation">;</span><br /> <br /><span class="token keyword">let</span> assetCacheName <span class="token operator">=</span> Js<span class="token punctuation">.</span>String<span class="token punctuation">.</span>concat<span class="token punctuation">(</span>version<span class="token punctuation">,</span> <span class="token string">"webbureaucrat-static-"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">let</span> runtimeCacheName <span class="token operator">=</span> Js<span class="token punctuation">.</span>String<span class="token punctuation">.</span>concat<span class="token punctuation">(</span>version<span class="token punctuation">,</span> <span class="token string">"webbureaucrat-runtime-"</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<h3>Adding requests to the static asset cache</h3>
<p>This is a tricky binding, and I want to call special attention to it. In
service workers, a cache is basically a key/value store, where a key can be
either a URL string or a JavaScript Request object. Therefore, to properly bind
to <a href="https://developer.mozilla.org/en-US/docs/Web/API/Cache/addAll"><code>Cache.addAll</code></a>
we would need an array that can hold string or a Request or any combination
thereof.</p>
<p>I went to <a href="https://stackoverflow.com/questions/65055018/how-do-i-use-an-unwrapped-polymorphic-variant-union-type-in-a-type-parameter">StackOverflow with this problem</a>, and you can see the solution
there.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">let</span> assets <span class="token operator">=</span> <span class="token punctuation">[</span><br /> Cache<span class="token punctuation">.</span>str<span class="token punctuation">(</span><span class="token string">"/css/default.css"</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> Cache<span class="token punctuation">.</span>str<span class="token punctuation">(</span><span class="token string">"/css/fonts/DejaVuSansMono-webfont.woff"</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> Cache<span class="token punctuation">.</span>str<span class="token punctuation">(</span><span class="token string">"/css/menu.css"</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> Cache<span class="token punctuation">.</span>str<span class="token punctuation">(</span><span class="token string">"/css/prism-base16-monokai.dark.css"</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> Cache<span class="token punctuation">.</span>str<span class="token punctuation">(</span><span class="token string">"/css/site.css"</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> Cache<span class="token punctuation">.</span>str<span class="token punctuation">(</span><span class="token string">"/css/util.css"</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> Cache<span class="token punctuation">.</span>str<span class="token punctuation">(</span><span class="token string">"/img/icons/192.png"</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> Cache<span class="token punctuation">.</span>str<span class="token punctuation">(</span><span class="token string">"/manifest.json"</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> Cache<span class="token punctuation">.</span>str<span class="token punctuation">(</span><span class="token string">"/portfolio/"</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> Cache<span class="token punctuation">.</span>str<span class="token punctuation">(</span><span class="token string">"/resume/"</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> Cache<span class="token punctuation">.</span>str<span class="token punctuation">(</span><span class="token string">"/about/"</span><span class="token punctuation">)</span><br /><span class="token punctuation">]</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">let</span> precache <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token punctuation">.</span>t<span class="token operator"><</span>unit<span class="token operator">></span> <span class="token operator">=></span><br /> caches<span class="token punctuation">(</span>self<span class="token punctuation">)</span><br /> <span class="token operator">-></span> CacheStorage<span class="token punctuation">.</span>open_<span class="token punctuation">(</span>assetCacheName<span class="token punctuation">)</span><br /> <span class="token operator">|></span> Promise<span class="token punctuation">.</span>then_<span class="token punctuation">(</span>cache <span class="token operator">=></span> cache <span class="token operator">-></span> Cache<span class="token punctuation">.</span>addAll<span class="token punctuation">(</span>assets<span class="token punctuation">)</span><span class="token punctuation">)</span><br /><span class="token punctuation">;</span><br /><br />self <span class="token operator">-></span> set_oninstall<span class="token punctuation">(</span>event <span class="token operator">=></span> <span class="token punctuation">{</span><br /> Js<span class="token punctuation">.</span>log<span class="token punctuation">(</span><span class="token string">"The service worker is being installed."</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> event <span class="token operator">-></span> Notifications<span class="token punctuation">.</span>ExtendableEvent<span class="token punctuation">.</span>waitUntil<span class="token punctuation">(</span>precache<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /></code></pre>
<p>What's going on here is a Cache.addAll takes an array of <code>Cache.req</code>, a type
that can be instantiated either via <code>Cache.request</code> or <code>Cache.str</code>. Here,
since I'm only using string-representations, it makes sense to use
<code>Cache.str</code> for all.</p>
<h3>Including helper methods for interacting with the service worker caches</h3>
<p>Service workers are a relatively low-level API, so it's helpful to break our
service worker cache strategy into building blocks that can be reused, so if
we change our cache strategy later on, we will have to change very little
actual code.</p>
<p>The <code>fromCache</code> function abstracts over fetching a response from a given
cache, failing if it can't find the resource in cache. <code>fromNetwork</code> just
passes straight through to the <code>bs-fetch</code> binding (for now), and
<code>addToCache</code>, well, adds a request to a given cache.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">let</span> fromCache <span class="token operator">=</span> <span class="token punctuation">(</span>req<span class="token punctuation">:</span> Fetch<span class="token punctuation">.</span>Request<span class="token punctuation">.</span>t<span class="token punctuation">,</span> cacheName<span class="token punctuation">:</span> string<span class="token punctuation">)</span><span class="token punctuation">:</span><br /> Promise<span class="token punctuation">.</span>t<span class="token operator"><</span>Fetch<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>t<span class="token operator">></span> <span class="token operator">=></span><br /> caches<span class="token punctuation">(</span>self<span class="token punctuation">)</span> <span class="token operator">-></span> CacheStorage<span class="token punctuation">.</span>open_<span class="token punctuation">(</span>cacheName<span class="token punctuation">)</span><br /><span class="token operator">|></span> Promise<span class="token punctuation">.</span>then_<span class="token punctuation">(</span>cache <span class="token operator">=></span> cache <span class="token operator">-></span> Cache<span class="token punctuation">.</span>Match<span class="token punctuation">.</span>withoutOptions<span class="token punctuation">(</span>req<span class="token punctuation">)</span><span class="token punctuation">)</span><br /><span class="token operator">|></span> Promise<span class="token punctuation">.</span>then_<span class="token punctuation">(</span>matching <span class="token operator">=></span> switch<span class="token punctuation">(</span>Js<span class="token punctuation">.</span>Nullable<span class="token punctuation">.</span>toOption<span class="token punctuation">(</span>matching<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token operator">|</span> Some<span class="token punctuation">(</span>m<span class="token punctuation">)</span> <span class="token operator">=></span> Promise<span class="token punctuation">.</span>resolve<span class="token punctuation">(</span>m<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token operator">|</span> None <span class="token operator">=></span> Promise<span class="token punctuation">.</span>reject<span class="token punctuation">(</span>makeExn<span class="token punctuation">(</span><span class="token string">"no-match"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /><span class="token punctuation">;</span><br /><br /><span class="token keyword">let</span> fromNetwork <span class="token operator">=</span> <span class="token punctuation">(</span>req<span class="token punctuation">:</span>Fetch<span class="token punctuation">.</span>Request<span class="token punctuation">.</span>t<span class="token punctuation">)</span><span class="token punctuation">:</span><br /> Promise<span class="token punctuation">.</span>t<span class="token operator"><</span>Fetch<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>t<span class="token operator">></span> <span class="token operator">=></span><br /> Fetch<span class="token punctuation">.</span>fetchWithRequest<span class="token punctuation">(</span>req<span class="token punctuation">)</span><br /> <br /><span class="token keyword">let</span> addToCache <span class="token operator">=</span> <span class="token punctuation">(</span>req<span class="token punctuation">:</span> Fetch<span class="token punctuation">.</span>Request<span class="token punctuation">.</span>t<span class="token punctuation">,</span> cacheName<span class="token punctuation">:</span> string<span class="token punctuation">)</span><span class="token punctuation">:</span><br /> Promise<span class="token punctuation">.</span>t<span class="token operator"><</span>unit<span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> caches<span class="token punctuation">(</span>self<span class="token punctuation">)</span><br /> <span class="token operator">-></span> CacheStorage<span class="token punctuation">.</span>open_<span class="token punctuation">(</span>cacheName<span class="token punctuation">)</span><br /> <span class="token operator">|></span> Promise<span class="token punctuation">.</span>then_<span class="token punctuation">(</span>cache <span class="token operator">=></span> cache <span class="token operator">-></span> Cache<span class="token punctuation">.</span>add<span class="token punctuation">(</span><span class="token directive property">#Request</span><span class="token punctuation">(</span>req<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>These are small functions, but they'll make our lives a little
easier in the next step.</p>
<h3>Defining both cache strategies</h3>
<p>Now we can more easily define our two strategies we described at the top. The
first, <code>assetStrategy</code>, takes grabs the asset from the cache and then
updates it if it's in the asset cache.</p>
<p>The second tries the network first and then fails over to the runtime
cache if there's a problem with the network.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">let</span> assetStrategy <span class="token operator">=</span> <span class="token punctuation">(</span>req<span class="token punctuation">:</span> Fetch<span class="token punctuation">.</span>Request<span class="token punctuation">.</span>t<span class="token punctuation">,</span> cacheName<span class="token punctuation">:</span> string<span class="token punctuation">)</span><span class="token punctuation">:</span><br /> Promise<span class="token punctuation">.</span>t<span class="token operator"><</span>Fetch<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>t<span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> result <span class="token operator">=</span> req <span class="token operator">-></span> Fetch<span class="token punctuation">.</span>Request<span class="token punctuation">.</span>makeWithRequest<br /> <span class="token operator">-></span> fromCache<span class="token punctuation">(</span>cacheName<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token operator">/*</span> update cache iff<span class="token punctuation">.</span> item already exists <span class="token keyword">in</span> cache <span class="token operator">*/</span><br /> <span class="token keyword">let</span> <span class="token punctuation">_</span> <span class="token operator">=</span> result <span class="token operator">|></span> Promise<span class="token punctuation">.</span>then_<span class="token punctuation">(</span><span class="token punctuation">_</span> <span class="token operator">=></span> addToCache<span class="token punctuation">(</span>req<span class="token punctuation">,</span> cacheName<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token operator">/*</span> don't wait on the update <span class="token keyword">to</span> return <span class="token operator">*/</span><br /> result<br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">let</span> runtimeStrategy <span class="token operator">=</span> <span class="token punctuation">(</span>req<span class="token punctuation">:</span> Fetch<span class="token punctuation">.</span>Request<span class="token punctuation">.</span>t<span class="token punctuation">,</span> cacheName<span class="token punctuation">:</span> string<span class="token punctuation">)</span><span class="token punctuation">:</span><br /> Promise<span class="token punctuation">.</span>t<span class="token operator"><</span>Fetch<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>t<span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> result <span class="token operator">=</span> req <span class="token operator">-></span> Fetch<span class="token punctuation">.</span>Request<span class="token punctuation">.</span>makeWithRequest <span class="token operator">-></span> fromNetwork<br /> <span class="token operator">|></span> Promise<span class="token punctuation">.</span>catch<span class="token punctuation">(</span><span class="token punctuation">_</span> <span class="token operator">=></span> req <span class="token operator">-></span> Fetch<span class="token punctuation">.</span>Request<span class="token punctuation">.</span>makeWithRequest<br /> <span class="token operator">-></span> fromCache<span class="token punctuation">(</span>cacheName<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">let</span> <span class="token punctuation">_</span> <span class="token operator">=</span> addToCache<span class="token punctuation">(</span>req<span class="token punctuation">,</span> cacheName<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> result<br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<h4>A word on <code>Fetch.makeWithRequest</code></h4>
<p>This is the binding to the constructor that makes a copy of the given
<code>Request</code>, and you'll notice I use it quite a bit. This is because
<code>Request</code>s are stateful, which can very easily introduce some very
weird and counterintuitive bugs. Therefore, I always make it a
habit of copying a <code>Request</code> every time I reference it. Even if
it's not always necessary, it means I can change my code without
worrying whether I've introduced a state-management bug.</p>
<h3><code>onfetch</code></h3>
<p>Because our code is well-organized, we can write our <code>onfetch</code> event handler
in just three lines. Our response, per our requirements, tries the asset cache
strategy and then fails over to the runtime cache strategy, using the versioned
cache names we defined at the top of the file.</p>
<pre class="language-ocaml"><code class="language-ocaml">self <span class="token operator">-></span> set_onfetch<span class="token punctuation">(</span>event <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> req <span class="token operator">=</span> event <span class="token operator">-></span> FetchEvent<span class="token punctuation">.</span>request<span class="token punctuation">;</span><br /> <span class="token keyword">let</span> resp<span class="token punctuation">:</span> Promise<span class="token punctuation">.</span>t<span class="token operator"><</span>Fetch<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>t<span class="token operator">></span> <span class="token operator">=</span><br /> req <span class="token operator">-></span> Fetch<span class="token punctuation">.</span>Request<span class="token punctuation">.</span>makeWithRequest<br /> <span class="token operator">-></span> assetStrategy<span class="token punctuation">(</span>assetCacheName<span class="token punctuation">)</span><br /> <span class="token operator">|></span> Promise<span class="token punctuation">.</span>catch<span class="token punctuation">(</span><span class="token punctuation">_</span> <span class="token operator">=></span> req<br /> <span class="token operator">-></span> Fetch<span class="token punctuation">.</span>Request<span class="token punctuation">.</span>makeWithRequest<br /> <span class="token operator">-></span> runtimeStrategy<span class="token punctuation">(</span>runtimeCacheName<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> event <span class="token operator">-></span> FetchEvent<span class="token punctuation">.</span>respondWith<span class="token punctuation">(</span><span class="token directive property">#Promise</span><span class="token punctuation">(</span>resp<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Note: Service Workers themselves are scoped to their directory, so you'll
probably want to locate your output at the root of your project. Unfortunately,
there's <a href="https://github.com/rescript-lang/rescript-compiler/issues/4857">no easy way to do this with ES6 modules in ReScript</a>. Configuring a script bundler
is out of scope for this article,
but I have written a
<a href="https://webbureaucrat.gitlab.io/articles/setting-up-webpack-for-rescript/">tutorial for configuring webpack</a>
for ReScript, and, as always, feel free to reach out or open an issue if you
have trouble.</p>
<h3>Write the Service Worker loader</h3>
<p>Lastly, if you're writing a Service Worker, you will need to run a script to
register it, which is trivial.</p>
<p>Here's my solution, in a file called <em>SWLoader.res</em>:</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">open</span> ServiceWorker<span class="token punctuation">;</span><br /><span class="token keyword">open</span> ServiceWorkerGlobalScope<span class="token punctuation">;</span><br /><br /><span class="token keyword">let</span> path <span class="token operator">=</span> <span class="token string">"/SW.bs.js"</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">let</span> <span class="token punctuation">_</span> <span class="token operator">=</span> self <span class="token operator">-></span> navigator<br /> <span class="token operator">-></span> ServiceWorkerNavigator<span class="token punctuation">.</span>serviceWorker<br /> <span class="token operator">-></span> ServiceWorkerContainer<span class="token punctuation">.</span>Register<span class="token punctuation">.</span>withoutOptions<span class="token punctuation">(</span>path<span class="token punctuation">)</span><br /> <span class="token punctuation">;</span></code></pre>
<p>This assumes that my build outputs a file called <em>/SW.bs.js</em> in the root
of the directory. Configure your own path appropriately.</p>
<h3>Source / For Further Reading</h3>
<p>This has been a tutorial on service workers in ReScript, based on this
website's own service worker. If anything is unclear, feel free to view the
<a href="https://gitlab.com/webbureaucrat/webbureaucrat.gitlab.io/-/tree/master/scripts/wb-service-worker/src">source in context</a>
on GitLab. You'll notice that my main assets like scripts and stylesheets are
automatically available offline, and you'll find every article you visit on my
site is available in the runtime cache. If something is still unclear,
feel free to reach out or open an
issue, and I'll be happy to take another run at it.</p>
Styling for Internet Explorer Using Progressive Enhancement2021-05-01T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/styling-for-internet-explorer-using-progressive-enhancement/<p>Internet Explorer 11 accounts for less than one percent of total Internet
usage globally and less than half a percent in the United States. Even
Microsoft's own web apps have stopped supporting it. But on the off chance
someone <em>does</em> use Internet Explorer, what do you want them
to see? This article explains how to employ the concept of <em>progressive
enhancement</em> to provide a moderately <em>acceptable</em> experience to users of a
decaying platform without taking on all of the legacy burden of formally
targeting it.</p>
<h3>The need for Internet Explorer 11 support</h3>
<p>As unfortunate as it is, Internet
Explorer still fulfills its own cursed little niche in the browser market in
enterprise software because it still provides the best support for Active
Directory authentication. That means there are still many office environments
which are still writing new, greenfield web apps which support Internet
Explorer <em>exclusively</em>. If you're applying for jobs in enterprise, your
future hiring manager may well use Internet
Explorer as their default browser because it's the only way for them to access
their timesheet.</p>
<p>With that in mind, let's open my own website in Internet Explorer and see what
it looks like from that perspective.</p>
<div class="center">
<img alt="an almost entirely unstyled webpage" class="img" src="https://webbureaucrat.gitlab.io/img/internet-explorer-original.png" title="AHHHHH it burns" />
</div>
<p>Welp. That's problematic. It looks like a website written by someone who hasn't
yet learned <em>what CSS even is</em>. Of the handful of styles Internet Explorer is
able to apply, many of them weren't even applied correctly, leaving cutoff
text.</p>
<h3>Establishing minimal requirements for Internet Explorer 11</h3>
<p>Clearly, if I were going to <em>target</em> Internet Explorer, I'd have to almost
completely rewrite all my stylesheets, but I don't necessarily want that.
Ideally, here's how I'd like things to work out:</p>
<ul>
<li>I want to focus my support on the more modern browsers used by 99% of users.</li>
<li>Wherever possible, I want to use modern, maintainable CSS, even if it's not
supported by Internet Explorer.</li>
<li>I want Internet Explorer users themselves to see a small warning encouraging
them to use one of my preferred platforms where I know the site will always
look a little better, but...</li>
<li>I want the website to look a bit better in Internet Explorer (not perfect by
any means, but also not offensively bad like it is now).</li>
</ul>
<h3>Progressive Enhancement: Theory and Practice</h3>
<p>Progressive enhancement is a Third Way for legacy support in which a developer
neither targets a platform nor ignores it. Rather, a developer <em>supports</em> a
platform with some minimal features while providing a <em>progressively</em> better
experience on targeted, modern platforms.</p>
<p>I recommend bringing a project to a stable version before backfilling code
to work on Internet Explorer for two reason:</p>
<ol>
<li>Internet Explorer support is secondary, so it makes sense to focus on
primary platforms.</li>
<li>Internet Explorer-compatible styling is inherently less maintainable, so
it is best to write it last after most of the large refactors have passed.</li>
</ol>
<p>Right now my website largely looks how I want it to look on modern browsers.
I expect there to be
some small changes here and there, but I believe the largest changes to the
style of the site are behind me <em>for the forseeable future</em>, so I believe
my website is a good candidate to begin backfilling Internet Explorer support.</p>
<h3>Internet Explorer's weaknesses and how to use them to our advantage</h3>
<p>Primarily, the problems of my website on Internet Explorer come down to two
issues:</p>
<ol>
<li>Internet Explorer does not support CSS variables at all.</li>
<li>Internet Explorer does not support flexboxes <em>correctly</em>.</li>
</ol>
<p>The second thing is just annoying, but the first is something we can definitely
use to our advantage. It means we can use plain CSS to style things for IE but
override them with CSS variables for modern platforms that <em>do</em> support them.</p>
<h3>Backfilling CSS variables with hard values</h3>
<p>I'm going to tackle the CSS variables first because it's by far the most
noticeable issue on my site in Internet Explorer. Since Internet Explorer
does not support CSS variables, I'm going to have to manually go through my
style sheet and fill in the hard values in the line before the line that uses
the variable, so that the hard value will be overwritten by the variable
in browsers that support variables.</p>
<p>For example,
<em>site.css</em></p>
<pre class="language-css"><code class="language-css"><span class="token selector">h2, h3, h4</span><br /><span class="token punctuation">{</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> #0f0<span class="token punctuation">;</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-theme-highlight<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<h4>But why tho?</h4>
<p>You might be wondering why I'm bothering to keep my CSS variables at all if I
have to chase each of them with hardcoded values. After all, if I change the
value stored in the variable, I'm now going to have to change each hardcoded
value.</p>
<p>But recall that targeting IE is not a main goal of my website--I'm just
providing some minimal secondary support. Yes, ideally, if I ever decide to
change the value of <code>--color-theme-highlight</code>, I should also go in and change
the hardcoded values, but it's not a big deal if one or two of them get
missed for some period of time.</p>
<p>As counterintuitive as it is, then, I am keeping both the hardcoded values
and the variables because it gives me the maintainability and readability of
variables while providing some minimal support to IE users.</p>
<h3>Hiding the things that just don't work in Internet Explorer</h3>
<p>Most of my other issues come down to my love of flexboxes--and I'm not just
going to give them up. Instead, for my use case, I'm just going to strip out
the parts of my site that Internet Explorer doesn't display properly.</p>
<p>We can use CSS variables as a crude feature detection, like so:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">:root</span><br /><span class="token punctuation">{</span><br /> <span class="token comment">/* Internet Explorer compatibility hacks */</span><br /> <span class="token property">--display-block</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">.footer</span><br /><span class="token punctuation">{</span><br /> <span class="token property">background-color</span><span class="token punctuation">:</span> #070<span class="token punctuation">;</span><br /> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-theme<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">box-shadow</span><span class="token punctuation">:</span> 0 0 15px #000<span class="token punctuation">;</span> <br /> <span class="token property">box-shadow</span><span class="token punctuation">:</span> 0 0 15px <span class="token function">var</span><span class="token punctuation">(</span>--color-shade-well<span class="token punctuation">)</span><span class="token punctuation">;</span> <br /> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span><br /> <span class="token property">display</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--display-block<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">z-index</span><span class="token punctuation">:</span> 2<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /></code></pre>
<p>This hides the website footer with <code>display: none;</code> but then overrides it
with a CSS variable for browsers that support CSS variables.
This is a little hacky, but that's okay because if it breaks, we only
inconvenience the very few users who use Internet Explorer.</p>
<p>But of course, at the end of the day, our favorite solution would be for
our Internet Explorer users to switch. Let's work on encouraging them to do
so.</p>
<h3>Displaying a message encouraging users to upgrade their browser</h3>
<p>There are no surprises here. I'm just reusing the CSS variable hack again so
that my ugly orange warning message displays only for browsers that don't
support CSS variables.</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.div-browser-warning</span><br /><span class="token punctuation">{</span><br /> <span class="token property">background-color</span><span class="token punctuation">:</span> #FF8C00<span class="token punctuation">;</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> #000<span class="token punctuation">;</span><br /> <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span><br /> <span class="token property">display</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--display-none<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token property">padding</span><span class="token punctuation">:</span> 5pt<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">...<br /><br />.p-browser-warning</span><br /><span class="token punctuation">{</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> #000<span class="token punctuation">;</span><br /><span class="token punctuation">}</span><br /><br /><span class="token selector">.p-browser-warning a</span><br /><span class="token punctuation">{</span><br /> <span class="token property">color</span><span class="token punctuation">:</span> #000<span class="token punctuation">;</span><br /> <span class="token property">text-decoration</span><span class="token punctuation">:</span> underline<span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>Now I can just add a warning at the top of the page container like:</p>
<pre class="language-html"><code class="language-html"> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>body</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>container<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>div-browser-warning<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>p-browser-warning<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Welcome! We notice you're<br /> using an outdated browser, which<br /> will result in a degraded experience on this site. Please<br /> consider<br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://www.mozilla.org/en-US/firefox/new/<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>a<br /> modern, fully supported browser.<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>header</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>flex-row header padded<span class="token punctuation">"</span></span><span class="token punctuation">></span></span></code></pre>
<h3>In conclusion</h3>
<p>At this point, with minimal effort and no structural changes, my site is
acceptable to look at in Internet Explorer, and my Internet Explorer users now
know how to reach a better user experience. I'll probably continue to make a
few incremental improvements, but for now I am quite happy with this
compromise, and I hope that this article has given you a few ideas for how
to apply the theory of progressive enhancement to make peace with the legacy
users on your platform of choice.</p>
Binding to a JavaScript Function that Returns a Variant in ReScript2021-06-09T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/binding-to-a-javascript-function-that-returns-a-variant-in-rescript/<p>ReScript provides easy ways to bind to most JavaScript functions in a way that
feels both native and safe. Conveniently, it even provides an
<a href="https://rescript-lang.org/docs/manual/latest/bind-to-js-function#trick-2-polymorphic-variant--unwrap"><code>@unwrap</code></a>
decorator for parametric polymorphism. However, there are a few places where
we still have to fill in the gaps. This article documents how to bind to a
JavaScript function that can return any one of several different types
using ReScript variants.</p>
<h3>The need for a custom solution</h3>
<p>JavaScript is both dynamic and weakly typed, and even the standard libraries
take full advantage of those features in ways that can cause headaches for
anyone trying to use a static type system.</p>
<p>TypeScript deals with this in a very literal way through union types. That is,
the type is literally defined as <code>OneType | TheOtherType</code> so that the
developer can account for both cases. ReScript does not have union types, but
does have <a href="https://rescript-lang.org/docs/manual/latest/variant">variants</a>,
which can be abstractions <em>around</em> different types.</p>
<p>Under the hood, these are JavaScript objects with properties that represent
the underlying values.</p>
<p><em>sample output from the official documentation</em></p>
<pre class="language-js"><code class="language-js"><span class="token keyword">var</span> f1 <span class="token operator">=</span> <span class="token comment">/* Child */</span><span class="token number">0</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> f2 <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token constant">TAG</span><span class="token operator">:</span> <span class="token comment">/* Mom */</span><span class="token number">0</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">_0</span><span class="token operator">:</span> <span class="token number">30</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">_1</span><span class="token operator">:</span> <span class="token string">"Jane"</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">var</span> f3 <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token constant">TAG</span><span class="token operator">:</span> <span class="token comment">/* Dad */</span><span class="token number">1</span><span class="token punctuation">,</span><br /> <span class="token literal-property property">_0</span><span class="token operator">:</span> <span class="token number">32</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>It's sleek on the ReScript side, but nonnative to JS. This means there's no way
under the current variant structure to directly bind to a method like
<a href="https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/keyPath"><code>IDBObjectStore.keypath</code></a>,
which could return <code>null</code> a string, or an array of strings. We can certainly
represent a similar type like</p>
<p><em>IDBObjectStoreKeyPath.res</em></p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">type</span> t <span class="token operator">=</span> Null <span class="token operator">|</span> String<span class="token punctuation">(</span>string<span class="token punctuation">)</span> <span class="token operator">|</span> Array<span class="token punctuation">(</span>Js<span class="token punctuation">.</span>Array<span class="token punctuation">.</span>t<span class="token operator"><</span>string<span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>...but ReScript will expect that instances of this type will have <code>TAG</code> and
numbered properties like the sample JavaScript output above. What we need is
a way to <em>classify</em> what gets returned by our binding and call the
appropriate variant constructor accordingly.</p>
<h3>Writing a binding to a dummy type</h3>
<p>We're going to end up doing a bit of unsafe black magic that we don't want our
library users to use, so let's wrap it in a module to offset it from the code
we'll expose in our <em>.resi</em>:</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">module</span> Private <span class="token operator">=</span> <span class="token punctuation">{</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>As we've established, there's no way to directly represent the returned value
of <code>keyPath</code> in the ReScript type system, so let's not bother.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">module</span> Private <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token keyword">type</span> any<span class="token punctuation">;</span><br /> <span class="token operator">@</span>get <span class="token keyword">external</span> keyPath<span class="token punctuation">:</span> t <span class="token operator">=></span> any <span class="token operator">=</span> <span class="token string">"keyPath"</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Now, let's dig into the ugly stuff.</p>
<h3>Thinking about types in JavaScript</h3>
<p>Let's break out of ReScript for a moment and think about the JavaScript
runtime side of things. If we were managing this in JavaScript, we would
probably use the <code>typeof</code> operator to return a string, and then we could
branch our logic accordingly.</p>
<p>But we can't only use <code>typeof</code> because <code>typeof null</code> and <code>typeof []</code> both
return <code>"object"</code>, so we'll need a null check as well.</p>
<p>So if we were doing this in JavaScript, we'd end up with a piece of code
something like</p>
<pre class="language-js"><code class="language-js"><span class="token parameter">x</span> <span class="token operator">=></span> x <span class="token operator">===</span> <span class="token keyword">null</span> <span class="token operator">?</span> <span class="token string">"null"</span> <span class="token operator">:</span> <span class="token keyword">typeof</span> x</code></pre>
<p>Let's hold on to that thought.</p>
<h3>Modeling the type <em>of the type</em> in ReScript</h3>
<p>Our JavaScript expression above will (for all <code>IDBObjectStoreKeyPath</code>s) return
"null", "object", or "string". This translates very nicely to a ReScript
polymorphic variant, like so:</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">type</span> typeName <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token directive property">#null</span> <span class="token operator">|</span> <span class="token punctuation">#</span><span class="token string">"object"</span> <span class="token operator">|</span> <span class="token punctuation">#</span><span class="token string">"string"</span> <span class="token punctuation">]</span><span class="token punctuation">;</span></code></pre>
<p>So now, with this type, we can type our JavaScript expression in a <code>%raw</code>
JavaScript snippet:</p>
<pre class="language-ocaml"><code class="language-ocaml"> <span class="token keyword">type</span> typeName <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token directive property">#null</span> <span class="token operator">|</span> <span class="token punctuation">#</span><span class="token string">"object"</span> <span class="token operator">|</span> <span class="token punctuation">#</span><span class="token string">"string"</span> <span class="token punctuation">]</span><span class="token punctuation">;</span><br /> <span class="token keyword">let</span> getType<span class="token punctuation">:</span> any <span class="token operator">=></span> typeName <span class="token operator">=</span> <span class="token operator">%</span>raw<span class="token punctuation">(</span><span class="token variant symbol">`x</span> <span class="token operator">=></span> x <span class="token operator">===</span> null <span class="token operator">?</span> <span class="token string">"null"</span> <span class="token punctuation">:</span> typeof x`<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>So now we can get the <code>keyPath</code> through the binding, and we can then get the
type name of that keyPath. We're so close.</p>
<h3><code>magic</code>ally calling the proper constructor</h3>
<p>We have one last step: we need to switch on our <code>typeName</code> to call switch on
our <code>typeName</code>, use <code>Obj.magic</code> to convert our type to the proper ReScript
type, and then call our constructor, which will wrap our type in our variant.</p>
<pre class="language-ocaml"><code class="language-ocaml"> <span class="token keyword">let</span> classify <span class="token operator">=</span> <span class="token punctuation">(</span>v<span class="token punctuation">:</span> any<span class="token punctuation">)</span><span class="token punctuation">:</span> IDBObjectStoreKeyPath<span class="token punctuation">.</span>t <span class="token operator">=></span> <br /> switch<span class="token punctuation">(</span>v <span class="token operator">-></span> getType<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token operator">|</span> <span class="token directive property">#null</span> <span class="token operator">=></span> IDBObjectStoreKeyPath<span class="token punctuation">.</span>Null<span class="token punctuation">;</span><br /> <span class="token operator">|</span> <span class="token punctuation">#</span><span class="token string">"object"</span> <span class="token operator">=></span> IDBObjectStoreKeyPath<span class="token punctuation">.</span>Array<span class="token punctuation">(</span>v <span class="token operator">-></span> Obj<span class="token punctuation">.</span>magic<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token operator">|</span> <span class="token punctuation">#</span><span class="token string">"string"</span> <span class="token operator">=></span> IDBObjectStoreKeyPath<span class="token punctuation">.</span>String<span class="token punctuation">(</span>v <span class="token operator">-></span> Obj<span class="token punctuation">.</span>magic<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p><code>Obj.magic</code> will cast the value to return whatever it infers, but our <code>switch</code>
should ensure the cast is safe (in practice, though not in theory).</p>
<h3><code>classify</code>ing <code>any</code> <code>keyPath</code></h3>
<p>Tying it all together, we can now use our <code>classify</code> function to sanitize
the <code>any</code> dummy type returned from our <code>keyPath</code> binding.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">let</span> keyPath <span class="token operator">=</span> <span class="token punctuation">(</span>t<span class="token punctuation">:</span> t<span class="token punctuation">)</span><span class="token punctuation">:</span> IDBObjectStoreKeyPath<span class="token punctuation">.</span>t <span class="token operator">=></span><br /> t <span class="token operator">-></span> Private<span class="token punctuation">.</span>keyPath <span class="token operator">-></span> Private<span class="token punctuation">.</span>classify<span class="token punctuation">;</span></code></pre>
<p>(This is the kind of thing that gets me excited about functional programming--
when we break things into small enough pieces, anything seems easy and simple.)</p>
<h3>Wrapping up</h3>
<p>I hope this has been a useful resource for writing difficult bindings. Just to
review, we were able to successfully return this variant...</p>
<p><em>IDBObjectStoreKeyPath.res</em></p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">type</span> t <span class="token operator">=</span> Null <span class="token operator">|</span> String<span class="token punctuation">(</span>string<span class="token punctuation">)</span> <span class="token operator">|</span> Array<span class="token punctuation">(</span>Js<span class="token punctuation">.</span>Array<span class="token punctuation">.</span>t<span class="token operator"><</span>string<span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>...from a function called <code>keyPath</code> by wrapping the binding like so:</p>
<p><em>IDBObjectStore.res</em></p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">type</span> t<span class="token punctuation">;</span><br /><br /><span class="token keyword">module</span> Private <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token keyword">type</span> any<span class="token punctuation">;</span><br /> <span class="token operator">@</span>get <span class="token keyword">external</span> keyPath<span class="token punctuation">:</span> t <span class="token operator">=></span> any <span class="token operator">=</span> <span class="token string">"keyPath"</span><span class="token punctuation">;</span><br /> <span class="token keyword">type</span> typeName <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token directive property">#null</span> <span class="token operator">|</span> <span class="token punctuation">#</span><span class="token string">"object"</span> <span class="token operator">|</span> <span class="token punctuation">#</span><span class="token string">"string"</span> <span class="token punctuation">]</span><span class="token punctuation">;</span><br /> <span class="token keyword">let</span> getType<span class="token punctuation">:</span> any <span class="token operator">=></span> typeName <span class="token operator">=</span> <span class="token operator">%</span>raw<span class="token punctuation">(</span><span class="token variant symbol">`x</span> <span class="token operator">=></span> x <span class="token operator">===</span> null <span class="token operator">?</span> <span class="token string">"null"</span> <span class="token punctuation">:</span> typeof x`<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">let</span> classify <span class="token operator">=</span> <span class="token punctuation">(</span>v<span class="token punctuation">:</span> any<span class="token punctuation">)</span><span class="token punctuation">:</span> IDBObjectStoreKeyPath<span class="token punctuation">.</span>t <span class="token operator">=></span> <br /> switch<span class="token punctuation">(</span>v <span class="token operator">-></span> getType<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token operator">|</span> <span class="token directive property">#null</span> <span class="token operator">=></span> IDBObjectStoreKeyPath<span class="token punctuation">.</span>Null<span class="token punctuation">;</span><br /> <span class="token operator">|</span> <span class="token punctuation">#</span><span class="token string">"object"</span> <span class="token operator">=></span> IDBObjectStoreKeyPath<span class="token punctuation">.</span>Array<span class="token punctuation">(</span>v <span class="token operator">-></span> Obj<span class="token punctuation">.</span>magic<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token operator">|</span> <span class="token punctuation">#</span><span class="token string">"string"</span> <span class="token operator">=></span> IDBObjectStoreKeyPath<span class="token punctuation">.</span>String<span class="token punctuation">(</span>v <span class="token operator">-></span> Obj<span class="token punctuation">.</span>magic<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token operator">/*</span> properties <span class="token operator">*/</span><br /><br /><span class="token keyword">let</span> keyPath <span class="token operator">=</span> <span class="token punctuation">(</span>t<span class="token punctuation">:</span> t<span class="token punctuation">)</span><span class="token punctuation">:</span> IDBObjectStoreKeyPath<span class="token punctuation">.</span>t <span class="token operator">=></span><br /> t <span class="token operator">-></span> Private<span class="token punctuation">.</span>keyPath <span class="token operator">-></span> Private<span class="token punctuation">.</span>classify<span class="token punctuation">;</span><br /></code></pre>
<p>I hope that this has been helpful for modeling union types using ReScript
variants. For my part, I'm sure to refer back to this article as I continue
writing and iterating on bindings.</p>
Containerizing your CLI tools for a clean development experience2022-01-01T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/containerizing-your-cli-tools-for-a-clean-development-experience/<p>Some platforms are more delicate than others. Once, in
college, I ended up with a Scala installation that used a different version of
Java than my version of Eclipse. It was a lot like trying to maintain a
particularly aggressive aquarium. How do I keep these feisty little
guys from trying to
murder each other?
<a href="https://xkcd.com/349/">Headaches like this</a> are one reason I try to
keep my dev
environment as clean as possible with a stock installation of Ubuntu, a handful
of programs, and precious few customizations. It makes it a little harder to
foul up and a lot easier to nuke and pave with a fresh installation if needed.
Recently, I've decided to embrace the logical conclusion to this method by
avoiding installing <em>any</em> extra command line tools at all. No node, no javac,
no competing for my <code>$PATH</code>; just me, emacs, and docker.</p>
<h2>Why not just install things on the host machine?</h2>
<p>Developers often wax poetic about the state of flow that comes from a smooth
development experience. It's as addictive as it is productive. If this sounds
familiar to you, you probably also know the opposite is true: anything that
takes you out of that state feels worse than disruptive. This is why tooling
problems feel like such a nightmare--you have to drop what you're doing and
decide whether to spend hours searching for reinstallation instructions and
configuration tips or just nuke the machine and pave it with a fresh operating
system installation.</p>
<p>The thing is, I don't want to have to be super careful with my environment, and
sometimes I can't afford to be. Have you ever needed to downgrade a tool and
ended up with two half-installed versions? And even if you're <em>really good</em>
at managing your own system (I'm not, tbh), you still have to consider the
risks of a solution that works on your machine if you don't know it will
deploy smoothly on a container.</p>
<p>Sometimes these can be fun puzzles, but if you're a developer, this probably
isn't what you want to spend your time doing.</p>
<h2>Example case: because npm is being a jerk</h2>
<p>I don't know why, but my node snap package installation is not at all playing
nice with ReScript, so I've uninstalled it and will not install it again.
However, I still want to be able to use node commands
(<code>node</code>, <code>npm</code>, and <code>npx</code>). I can do this using <strong>Docker</strong>.</p>
<p>It's a good idea to go ahead and pull the image appropriate to the tool you
want to replace because it may take a while to download.</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> pull <span class="token function">node</span> <span class="token comment"># latest because whatever</span></code></pre>
<h2>Building out an appropriate <code>run</code> command</h2>
<p>You probably know that you can run commands inside a container by postfixing
those commands and arguments to the run command, like so:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> run <span class="token parameter variable">--rm</span> node:latest <span class="token function">sh</span> <span class="token string">"echo /"</span>hello, world/<span class="token string">""</span></code></pre>
<p>But we need a bit more than that. Let's start by making sure our we can
properly interact with our run command by running it as an interactive TTY
(<code>-it</code>).</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> run <span class="token punctuation">\</span><br /> <span class="token parameter variable">-it</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">--rm</span> <span class="token punctuation">\</span><br /> node:latest</code></pre>
<p>This will allow us to interact or interrupt our node commands if needed, using
the keyboard.</p>
<p>Next, let's give our run command access
to the network, so that npm can download things, for example:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> run <br /> <span class="token parameter variable">-it</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">--network</span> host<span class="token punctuation">\</span><br /> <span class="token parameter variable">--rm</span> <span class="token punctuation">\</span><br /> node:latest</code></pre>
<p>To avoid permissions issues, let's run as our current user by giving it our
user id, group id, and read access to the relevant information from our system.</p>
<pre class="language-bash"><code class="language-bash"> <span class="token function">docker</span> run <span class="token punctuation">\</span><br /> <span class="token parameter variable">-it</span> <span class="token punctuation">\</span> <br /> <span class="token parameter variable">--network</span> <span class="token function">host</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">--rm</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">--user</span> <span class="token variable"><span class="token variable">$(</span><span class="token function">id</span> <span class="token parameter variable">-u</span><span class="token variable">)</span></span><span class="token builtin class-name">:</span><span class="token variable"><span class="token variable">$(</span><span class="token function">id</span> <span class="token parameter variable">-g</span><span class="token variable">)</span></span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-v</span> <span class="token string">"/etc/passwd:/etc/passwd:ro"</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-v</span> <span class="token string">"/etc/group:/etc/group:ro"</span> <span class="token punctuation">\</span><br /> node:latest <span class="token string">"<span class="token variable">$@</span>"</span><br /></code></pre>
<p>Our run command will also need access to our files and a working directory to
start in. At a minimum, the command will need our current working directory
to operate on current files. For my purposes, I use some locally installed
packages which may be anywhere in my home directory, so I am going to give
the container access to my whole home directory, but also tell it to start
working in the current directory:</p>
<pre class="language-bash"><code class="language-bash"> <span class="token function">docker</span> run <span class="token punctuation">\</span><br /> <span class="token parameter variable">-it</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">--network</span> <span class="token function">host</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">--rm</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">--user</span> <span class="token variable"><span class="token variable">$(</span><span class="token function">id</span> <span class="token parameter variable">-u</span><span class="token variable">)</span></span><span class="token builtin class-name">:</span><span class="token variable"><span class="token variable">$(</span><span class="token function">id</span> <span class="token parameter variable">-g</span><span class="token variable">)</span></span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-v</span> <span class="token string">"/home:/home"</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-v</span> <span class="token string">"/etc/passwd:/etc/passwd:ro"</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-v</span> <span class="token string">"/etc/group:/etc/group:ro"</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-w</span> <span class="token environment constant">$PWD</span> node:latest <span class="token string">"<span class="token variable">$@</span>"</span><br /></code></pre>
<p>Okay, that was a lot of typing. Let's make sure we never have to do that again.</p>
<h2>Replacing commands using exported shell functions</h2>
<p>If you don't already have a <em>.bash_functions</em> file, you're going to want to
create one and reference it in your <em>.bashrc</em>. (You could also just put
everything in the <em>.bashrc</em>, but it's better to have things separated out.)</p>
<p>Open your <em>.bashrc</em> file and insert the following lines:</p>
<pre class="language-bash"><code class="language-bash"><span class="token keyword">if</span> <span class="token punctuation">[</span> <span class="token parameter variable">-f</span> ~/.bash_functions <span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">then</span><br /> <span class="token builtin class-name">.</span> ~/.bash_functions<br /><span class="token keyword">fi</span></code></pre>
<p>A standard Ubuntu 20 installation includes a similar <code>if</code> statement for
<em>.bash_aliases</em>, so I put mine right below that, but it doesn't really matter
much where it goes.</p>
<p>Now open (or create) your <em>~/.bash_functions</em> file, and create a function
we can reuse for each of our node-based commands. I am going to call mine,
"container-node."</p>
<p><em>~/.bash_functions</em></p>
<pre class="language-bash"><code class="language-bash"><span class="token keyword">function</span> <span class="token function-name function">container-node</span><br /><span class="token punctuation">{</span><br /> <span class="token function">docker</span> run <span class="token punctuation">\</span><br /> <span class="token parameter variable">-it</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">--network</span> <span class="token function">host</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">--rm</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">--user</span> <span class="token variable"><span class="token variable">$(</span><span class="token function">id</span> <span class="token parameter variable">-u</span><span class="token variable">)</span></span><span class="token builtin class-name">:</span><span class="token variable"><span class="token variable">$(</span><span class="token function">id</span> <span class="token parameter variable">-g</span><span class="token variable">)</span></span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-v</span> <span class="token string">"/home:/home"</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-v</span> <span class="token string">"/etc/passwd:/etc/passwd:ro"</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-v</span> <span class="token string">"/etc/group:/etc/group:ro"</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-w</span> <span class="token environment constant">$PWD</span> node:latest <br /><span class="token punctuation">}</span></code></pre>
<p>Now we need <code>container-node</code> to collect the command and arguments we give it.
<code>"$@"</code> represents that arbitrarily long list, so let's append it to the end.</p>
<pre class="language-bash"><code class="language-bash"><span class="token keyword">function</span> <span class="token function-name function">container-node</span><br /><span class="token punctuation">{</span><br /> <span class="token function">docker</span> run <span class="token punctuation">\</span><br /> <span class="token parameter variable">-it</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">--network</span> <span class="token function">host</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">--rm</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">--user</span> <span class="token variable"><span class="token variable">$(</span><span class="token function">id</span> <span class="token parameter variable">-u</span><span class="token variable">)</span></span><span class="token builtin class-name">:</span><span class="token variable"><span class="token variable">$(</span><span class="token function">id</span> <span class="token parameter variable">-g</span><span class="token variable">)</span></span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-v</span> <span class="token string">"/home:/home"</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-v</span> <span class="token string">"/etc/passwd:/etc/passwd:ro"</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-v</span> <span class="token string">"/etc/group:/etc/group:ro"</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-w</span> <span class="token environment constant">$PWD</span> node:latest <span class="token string">"<span class="token variable">$@</span>"</span><br /><span class="token punctuation">}</span></code></pre>
<p>So, now, for example, if we call this function like</p>
<pre class="language-bash"><code class="language-bash">container-node <span class="token function">npm</span> run build</code></pre>
<p>the <code>npm run</code> command will run on the docker container we've defined.</p>
<p>Now it should be easy to see how we can build on this to simulate a real
node installation, by defining three more functions and again using the <code>"$@"</code>
to pass arguments:</p>
<pre class="language-bash"><code class="language-bash"><span class="token keyword">function</span> <span class="token function-name function">container-node</span><br /><span class="token punctuation">{</span><br /> <span class="token function">docker</span> run <span class="token punctuation">\</span><br /> <span class="token parameter variable">-it</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">--network</span> <span class="token function">host</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">--rm</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">--user</span> <span class="token variable"><span class="token variable">$(</span><span class="token function">id</span> <span class="token parameter variable">-u</span><span class="token variable">)</span></span><span class="token builtin class-name">:</span><span class="token variable"><span class="token variable">$(</span><span class="token function">id</span> <span class="token parameter variable">-g</span><span class="token variable">)</span></span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-v</span> <span class="token string">"/home:/home"</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-v</span> <span class="token string">"/etc/passwd:/etc/passwd:ro"</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-v</span> <span class="token string">"/etc/group:/etc/group:ro"</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-w</span> <span class="token environment constant">$PWD</span> node:latest <span class="token string">"<span class="token variable">$@</span>"</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">function</span> <span class="token function-name function">node</span><br /><span class="token punctuation">{</span><br /> container-node <span class="token function">node</span> <span class="token string">"<span class="token variable">$@</span>"</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">function</span> <span class="token function-name function">npm</span><br /><span class="token punctuation">{</span><br /> container-node <span class="token function">npm</span> <span class="token string">"<span class="token variable">$@</span>"</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">function</span> <span class="token function-name function">npx</span><br /><span class="token punctuation">{</span><br /> container-node npx <span class="token string">"<span class="token variable">$@</span>"</span><br /><span class="token punctuation">}</span><br /></code></pre>
<p>Now if we <code>source</code> this file, we'll have our commands available in the current
bash window.</p>
<h2><code>export</code>ing to scripts</h2>
<p>We have one
small remaining problem: other shells spun off of our main shell won't be able
to read our functions, like in scripts, for example. If we want to truly be
able to run our node commands like we would with a node installation, we should
<code>export</code> our functions.</p>
<p><em>~/.bash_functions</em></p>
<pre class="language-bash"><code class="language-bash"><span class="token keyword">function</span> <span class="token function-name function">container-node</span><br /><span class="token punctuation">{</span><br /> <span class="token function">docker</span> run <span class="token punctuation">\</span><br /> <span class="token parameter variable">-it</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">--network</span> <span class="token function">host</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">--rm</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">--user</span> <span class="token variable"><span class="token variable">$(</span><span class="token function">id</span> <span class="token parameter variable">-u</span><span class="token variable">)</span></span><span class="token builtin class-name">:</span><span class="token variable"><span class="token variable">$(</span><span class="token function">id</span> <span class="token parameter variable">-g</span><span class="token variable">)</span></span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-v</span> <span class="token string">"/home:/home"</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-v</span> <span class="token string">"/etc/passwd:/etc/passwd:ro"</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-v</span> <span class="token string">"/etc/group:/etc/group:ro"</span> <span class="token punctuation">\</span><br /> <span class="token parameter variable">-w</span> <span class="token environment constant">$PWD</span> node:latest <span class="token string">"<span class="token variable">$@</span>"</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">function</span> <span class="token function-name function">node</span><br /><span class="token punctuation">{</span><br /> container-node <span class="token function">node</span> <span class="token string">"<span class="token variable">$@</span>"</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">function</span> <span class="token function-name function">npm</span><br /><span class="token punctuation">{</span><br /> container-node <span class="token function">npm</span> <span class="token string">"<span class="token variable">$@</span>"</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">function</span> <span class="token function-name function">npx</span><br /><span class="token punctuation">{</span><br /> container-node npx <span class="token string">"<span class="token variable">$@</span>"</span><br /><span class="token punctuation">}</span><br /><br /><span class="token builtin class-name">export</span> <span class="token parameter variable">-f</span> container-node<br /><span class="token builtin class-name">export</span> <span class="token parameter variable">-f</span> <span class="token function">node</span><br /><span class="token builtin class-name">export</span> <span class="token parameter variable">-f</span> <span class="token function">npm</span><br /><span class="token builtin class-name">export</span> <span class="token parameter variable">-f</span> npx</code></pre>
<p>Our changes will take effect as soon as we <code>source</code> our <em>~/.bashrc</em> referencing
our <em>~/.bash_functions</em> file.</p>
<pre class="language-bash"><code class="language-bash"><span class="token builtin class-name">source</span> ~/.bashrc</code></pre>
<h2>Celebrating the end of environment struggles</h2>
<p>I realize this sort of setup is a bit extreme, and spinning up containers
all the time does carry a small amount of overhead, but I do think I'm going
to stick with this method for a while and see how far I can take it.</p>
<p>Certainly, I will follow up sometime with an explainer on doing Drupal
development on Windows using a container-based setup (because trying to get
XAMPP to work is literally the hardest thing I've ever done in my career and
I don't ever want to think about it again).</p>
<p>I also intend to add this script to version control so that I can grab-and-go
whenever I do need to set up a new environment, although, for the moment, this
article itself will suffice.</p>
Better Continuous Deployment with GitLab CI/CD2022-02-01T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/better-continuous-deployment-with-gitlab-ci-cd/<p>This article follows up with a
<a href="https://webbureaucrat.gitlab.io/articles/continuously-deploying-an-npm-package-with-gitlab-ci-cd/">previous article</a>, which details the bare minimum
for a CI/CD pipeline from GitLab to the <a href="http://npmjs.com/">npmjs.com</a> package repository. It's not
a bad start for learning how to deploy to <a href="http://npmjs.com/">npmjs.com</a> from a pipeline, but as a
pipeline itself, it's... well, it was my first attempt. This article will
detail a better pipeline in terms of maintainability, build-safety, and
testing. (NPM will still be used as an example, but the broader concepts will
be applicable to other stacks.)</p>
<h3>Requirements for a good GitLab CI/CD pipeline</h3>
<p>A good pipeline should be able to do more than just authenticate and push to
production.</p>
<ul>
<li>I want to protect the production product from ever being in a non-compiling
state.</li>
<li>I want to protect the production product from ever being in a state where
some tests fail.</li>
<li>I want to deploy to production whenever my code compiles and my
tests succeed without manual intervention.</li>
<li>I want my "main" branch to always be representative of the production code.</li>
</ul>
<p>Therefore, I'd like my process to look something like this:</p>
<ol>
<li>Push code to a "dev" branch whenever I fix a bug or complete a feature.</li>
<li>Run the compilation.</li>
<li>Run the test suite.</li>
<li>If both of those are successful, merge to "main."</li>
<li>Deploy from "main."</li>
</ol>
<h3>Authenticating our CI/CD pipeline with environment variables.</h3>
<p>Obviously, you can't put passwords or authentication tokens in a script in a
publicly-visible open source project. Fortunately, GitLab allows secure
storage and use of environment variables in CI/CD pipelines with these two
protections:</p>
<p><strong>Masking</strong> an environment variable protects the variable from being seen in
the console output. It is easy to imagine a scenario where an error message
(or just a simple scripting mistake) could lead to this kind of information
being printed to the console, and once the toothpaste is out of the tube and
on the internet, there's no putting it back in--you have to revoke that token
and generate a new one. Masking prevents this easy-to-make security mistake.</p>
<p><strong>Protecting</strong> an environment variable is a kind of access control. A protected
environment variable can only be used in protected branches or on protected
tags, and it can't be seen by all contributors.</p>
<p>A critically sensitive authentication token like an NPM publish token
or a GitLab personal access token should
be <strong>both</strong> protected and masked.</p>
<h3>Generating a token for GitLab CI/CD</h3>
<p>GitLab CI/CD pipelines do come with a CI_JOB_TOKEN environment variable, but
it's a bit of a blunt instrument in terms of permissions--it doesn't have many
of them, and you can't edit them, so the most secure and least annoying
practice is to go ahead and create
a fresh GitLab personal access token and give it exactly the permissions it
needs and no more.</p>
<p>To create a GitLab personal access token:</p>
<ol>
<li>Log into GitLab on the web.</li>
<li>Click on your profile photo on the top right of the screen to open the
menu.</li>
<li>Click on preferences in the open menu.</li>
<li>Under "User Settings" on the left, select "Access Tokens" near the middle
of the vertical navigation menu.</li>
</ol>
<p>Give your token a meaningful name. Mine is named "merge-token" because it will
only be used to merge dev branches into main branches in automated pipelines.
For this purpose, it's probably impractical to set an expiration date, and
that's okay.</p>
<p>I would recommend only giving the token read and write access to
repositories, so that if the token is leaked the attacker at least won't have
access to the whole GitLab API.</p>
<p>Once the token is created, save it in a password manager.</p>
<h3>Generating an automation token in npm</h3>
<p>The second token we'll need is from npm. The npm team has made this
straightforward.</p>
<ol>
<li>Go to <a href="https://www.npmjs.com/">npmjs.com</a> and log in if you haven't
already.</li>
<li>Click on your profile picture at the top right.</li>
<li>Select the fifth item, "Access Tokens."</li>
<li>Click "Generate New Token" on the top right of the page.</li>
<li>Select the middle option, "automation" for the right security settings.</li>
<li>Click "Generate Token."</li>
<li>Save the token in a password manager.</li>
</ol>
<h3>Storing the tokens in GitLab</h3>
<p>Both tokens need to be available as environment variables in the pipeline.
To add them to the pipeline's context:</p>
<ol>
<li>Log into GitLab and open the project you intend to automate.</li>
<li>Select "Settings" at the bottom of the menu on the left. This will open a
submenu.</li>
<li>Select "CI/CD."</li>
<li>Find the "Variables" section of the CI/CD menu and click "expand" on the
right.</li>
</ol>
<p>Then, for both variales:</p>
<ol start="5">
<li>Click the green "Add variable" button at the bottom.</li>
<li>Fill in the "Key" text box with "NPM_TOKEN" and "MERGE_TOKEN"
respectively.</li>
<li>Fill in the "Value" box with the token from your password manager.</li>
<li>Make sure the "Type" is set to "variable" instead of "file."</li>
<li>Make sure both checkboxes are checked to protect and mask the variable.</li>
</ol>
<p>(Again: Protecting the variable, while important for security-sensitive
information like authentication tokens, makes the variable unavailable on
unprotected branches or unprotected tags. Consult the GitLab documentation on
protected variables if you are having trouble accessing your variables from
the pipeline.)</p>
<h3>Build and test automation in the dev branch</h3>
<p>By default, GitLab CI/CD comes with three "stages"--build, test, and
deploy--which will run in order whenever a commit is pushed. Let's go ahead
and implement the first couple of stages.</p>
<pre class="language-yml"><code class="language-yml"><span class="token key atrule">image</span><span class="token punctuation">:</span> node<span class="token punctuation">:</span>latest<br /><br /><span class="token key atrule">compile</span><span class="token punctuation">:</span> <span class="token comment"># arbitrary name to identify the script</span><br /> <span class="token key atrule">stage</span><span class="token punctuation">:</span> build <span class="token comment"># indicates its chronological order in the pipeline</span><br /> <span class="token key atrule">script</span><span class="token punctuation">:</span> <br /> <span class="token punctuation">-</span> npm ci <span class="token comment"># the recommended best practice for CI/CD (as opposed to npm i)</span><br /> <span class="token punctuation">-</span> npm run build <br /> <span class="token key atrule">only</span><span class="token punctuation">:</span> <br /> <span class="token punctuation">-</span> dev <span class="token comment"># only run this script for the dev branch</span><br /> <br /><span class="token key atrule">test</span><span class="token punctuation">:</span><br /> <span class="token key atrule">stage</span><span class="token punctuation">:</span> test<br /> <span class="token key atrule">script</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> npm ci<br /> <span class="token punctuation">-</span> npm run build<br /> <span class="token punctuation">-</span> npm run test<br /> <span class="token key atrule">only</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> dev<br /></code></pre>
<h3>Understanding the default state of the repository in GitLab CI/CD</h3>
<p>The way that GitLab sets up the repository inside the CI/CD runner by default
is optimized to be <em>fast</em>, but not necessarily intuitive.</p>
<p>When it fetches a copy of the code, it doesn't clone the whole repository
because the whole git history and the various branches often aren't needed
in a CI/CD pipeline. It also rests in a "detached" state from any particular
branch. Finally, its default origin is the CI_JOB_TOKEN, which does not have
permission to push code.</p>
<p>These are three problems which solvable in three steps.</p>
<ol>
<li>Swap out the job token for the GitLab personal access token by
<strong>running the <code>git remote set-url origin...</code> command.</strong></li>
<li>Get the main branch by <strong>running <code>git pull origin main</code>.</strong></li>
<li>Check out the main branch <strong>using the <code>git checkout</code> command.</strong></li>
</ol>
<p>(...or you could just clone a fresh copy of the repository with a sensible
origin and not bother figuring out how to make the existing pipeline work, but
where is the fun in that?)</p>
<h3>Automating a merge in a GitLab pipeline</h3>
<p>With that in mind, we end up with a CI/CD stage that looks like this:</p>
<pre class="language-yml"><code class="language-yml"><span class="token key atrule">merge</span><span class="token punctuation">:</span><br /> <span class="token key atrule">only</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> dev<br /> <span class="token key atrule">script</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> git remote set<span class="token punctuation">-</span>url origin https<span class="token punctuation">:</span>//merge<span class="token punctuation">-</span>token<span class="token punctuation">:</span>$<span class="token punctuation">{</span>MERGE_TOKEN<span class="token punctuation">}</span>@gitlab.com/$<span class="token punctuation">{</span>CI_PROJECT_NAMESPACE<span class="token punctuation">}</span>/$<span class="token punctuation">{</span>CI_PROJECT_NAME<span class="token punctuation">}</span>.git<br /> <span class="token punctuation">-</span> git pull origin main<br /> <span class="token punctuation">-</span> git checkout main<br /> <span class="token punctuation">-</span> git merge origin/dev<br /> <span class="token punctuation">-</span> git push origin main<br /> <span class="token key atrule">stage</span><span class="token punctuation">:</span> deploy<br /></code></pre>
<p>By the way, <code>CI_PROJECT_NAMESPACE</code> and <code>CI_PROJECT_NAME</code> aren't just
placeholders--they're real environment variables provided to you automatically
by GitLab, which is a nice feature because it means you can reuse this
pipeline in similar projects. <code>MERGE_TOKEN</code>, of course, is the
personal access token we created earlier.</p>
<h3>Automating the deployment to npm</h3>
<p>This is straightforward. To deploy to <a href="http://npmjs.com/">npmjs.com</a>, authenticate by including
your token in the .npmrc, recalling our <code>$NPM_TOKEN</code> environment variable we
create earlier.</p>
<pre class="language-yml"><code class="language-yml"><span class="token key atrule">deploy</span><span class="token punctuation">:</span><br /> <span class="token key atrule">only</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> main <span class="token comment"># importantly, deploy only from the main branch</span><br /> <span class="token key atrule">stage</span><span class="token punctuation">:</span> deploy<br /> <span class="token key atrule">script</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> echo "//registry.npmjs.org/<span class="token punctuation">:</span>_authToken=$<span class="token punctuation">{</span>NPM_TOKEN<span class="token punctuation">}</span>" <span class="token punctuation">></span><span class="token punctuation">></span> .npmrc<br /> <span class="token punctuation">-</span> npm publish</code></pre>
<h3>Putting it all together</h3>
<p>This is my full-length CI/CD script, which I am applying to an increasing
number of projects such as
<a href="https://gitlab.com/eleanorofs/rescript-notifications/-/pipelines">rescript-notifications</a>.</p>
<pre class="language-yml"><code class="language-yml"><span class="token key atrule">image</span><span class="token punctuation">:</span> node<span class="token punctuation">:</span>latest<br /><br /><span class="token key atrule">compile</span><span class="token punctuation">:</span> <span class="token comment"># arbitrary name to identify the script</span><br /> <span class="token key atrule">stage</span><span class="token punctuation">:</span> build <span class="token comment"># indicates its chronological order in the pipeline</span><br /> <span class="token key atrule">script</span><span class="token punctuation">:</span> <br /> <span class="token punctuation">-</span> npm ci <span class="token comment"># the recommended best practice for CI/CD (as opposed to npm i)</span><br /> <span class="token punctuation">-</span> npm run build <br /> <span class="token key atrule">only</span><span class="token punctuation">:</span> <br /> <span class="token punctuation">-</span> dev <span class="token comment"># only run this script for the dev branch</span><br /> <br /><span class="token key atrule">test</span><span class="token punctuation">:</span><br /> <span class="token key atrule">stage</span><span class="token punctuation">:</span> test<br /> <span class="token key atrule">script</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> npm ci<br /> <span class="token punctuation">-</span> npm run build<br /> <span class="token punctuation">-</span> npm run test<br /> <span class="token key atrule">only</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> dev<br /><br /><span class="token key atrule">merge</span><span class="token punctuation">:</span><br /> <span class="token key atrule">only</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> dev<br /> <span class="token key atrule">script</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> git remote set<span class="token punctuation">-</span>url origin https<span class="token punctuation">:</span>//merge<span class="token punctuation">-</span>token<span class="token punctuation">:</span>$<span class="token punctuation">{</span>MERGE_TOKEN<span class="token punctuation">}</span>@gitlab.com/$<span class="token punctuation">{</span>CI_PROJECT_NAMESPACE<span class="token punctuation">}</span>/$<span class="token punctuation">{</span>CI_PROJECT_NAME<span class="token punctuation">}</span>.git<br /> <span class="token punctuation">-</span> git pull origin main<br /> <span class="token punctuation">-</span> git checkout main<br /> <span class="token punctuation">-</span> git merge origin/dev<br /> <span class="token punctuation">-</span> git push origin main<br /> <span class="token key atrule">stage</span><span class="token punctuation">:</span> deploy<br /> <br /><span class="token key atrule">deploy</span><span class="token punctuation">:</span><br /> <span class="token key atrule">only</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> main<br /> <span class="token key atrule">stage</span><span class="token punctuation">:</span> deploy<br /> <span class="token key atrule">script</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> echo "//registry.npmjs.org/<span class="token punctuation">:</span>_authToken=$<span class="token punctuation">{</span>NPM_TOKEN<span class="token punctuation">}</span>" <span class="token punctuation">></span><span class="token punctuation">></span> .npmrc<br /> <span class="token punctuation">-</span> npm publish</code></pre>
<h3>Handling NPM version numbers in a CI/CD pipeline</h3>
<p>There's one small, annoying, potential issue you might bump up against:
version numbers. NPM doesn't allow new code to be deployed under an existing
version number, so every time you push, you will need to remember to update
the version number in your <em>package.json</em>.</p>
<p>There's a somewhat cumbersome way to manage this automatically. You could
create a version number in a GitLab environment variable and then use the
GitLab API to update that version number within the pipeline.</p>
<p>However, I personally don't do this and don't recommend it because requiring
you to think about version numbers is good. I don't want to
autoincrement a patch number that should be a minor version or a minor version
that should be a major version. A big part of the point of CI/CD is more
quickly delivering value to users, so you don't want to burn off that goodwill
by delivering breaking changes in a patch.</p>
<h3>Looking forward to more fun with GitLab CI/CD</h3>
<p>It feels good to have this process documented for myself, and I hope someone
else will be able to get some value out of it as well. My next article will
address dual-deployment to <a href="http://npmjs.com/">npmjs.com</a> and GitLab's own npm registry.</p>
Why I'm Not Writing about Myself for SheCoded2022-03-01T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/why-im-not-writing-about-myself-for-international-womens-day/<p>First: I do get the irony of this article; there's no way around the idea that
I am, actually, writing a post for the <a href="http://dev.to/">Dev.to</a> #shecoded tag. But there are so
many things that bother me so much about this annual event as a feminist, so
instead of writing about my own experiences, I
want to offer some ways to make the annual #shecoded event better.</p>
<h3>Pick a different tag</h3>
<p>There are so many things to love about Elizabeth Warren, not the least of which
is that she appropriately cited Coretta Scott King in the debate on the
famous attorney general confirmation hearings.</p>
<p>However, I dislike that the soundbyte from McConnell, "Nevertheless, she
persisted" <em>about Elizabeth Warren</em> has become a feminist rallying cry.</p>
<p>If you need a refresher or if you've never closely followed American politics,
Warren was excluded from the attorney general confirmation debate after reading
from a letter from Coretta Scott King on the racism of the attorney general
nominee, Jeff Sessions. Mitch McConnell objected to the letter, and, in
solidarity, another (male) senator stepped up to read the letter.</p>
<p>People seized on the idea that Warren was penalized for reading the letter
while her colleague wasn't, but this is incredibly shallow analysis. Warren
wasn't being punished because she was a woman; she was being punished because
McConnell was in a tight spot politically and made a stupid tactical move in
trying to censor King's letter. Moreover, Warren's colleague wasn't let off the
hook because he was a man but because it was obvious at that point what would
happen if McConnell kept objecting--every Democratic senator would have lined
up to read the letter and get kicked out, which would further raise the profile
of the letter, which was the opposite of what McConnell was trying to
accomplish.</p>
<p>It's worth asking why I find the #neverthelessshepersisted meme so
objectionable--what's the harm in a shallow meme if it creates a platform
for women to tell their stories.</p>
<ol>
<li>
<p>Gender-based discrimination is a serious issue, and it should be treated
with seriousness. Latching onto a catchy, out-of-context soundbyte diminishes
stories of actual discrimination.</p>
</li>
<li>
<p>Much more importantly, the misappropriation of the episode is a sad example
of white feminism. The mythology around the silencing of Elizabeth Warren and
the subsequent outpouring of other women telling their stories effectively
buried the story of the <em>actual woman who was being discriminated against and
silenced</em>: Coretta Scott King. It was <em>her</em> words which McConnell objected to
and <em>her</em> story of discrimination being censored. In a way, McConnell
succeeded in burying the letter <em>because of</em> the hashtag, and we cooperate with
King's silencing when we play off of #neverthelessshepersisted, which refers
to Warren.</p>
</li>
</ol>
<h3>Get rid of #shecodedally</h3>
<p>Allyship is great, and incentivising allyship is great, but incentivizing men
to speak out on feminism in a space that's vastly majority-male anyway... isn't
great. If you're a man, I think the most important thing you can do for IWD is
to practice reading-to-understand rather than reading-to-respond. You don't
have to ultimately agree with every woman's take--I certainly don't--but read
everything with an open mind and just sit with it for a few days before making
up your mind.</p>
<p>I'm not saying men in general should not say anything, but I think the
ally hashtag shouldn't be promoted or officially recognized. Men who have
particular experiences or data they feel they should share certainly should
do so! But donating for every #shecodedally post necessarily puts men in the
difficult position of chosing between pontificating on a subject they aren't
fluent in or foregoing a free $20 donation. It's not fair to men, and it is
necessarily going to lead to a "She
Coded" event with a lot more "ally" voices than actual #shecoded posts.</p>
<h3>Give non-binary people their own day</h3>
<p>As a woman, I don't speak for non-binary people, and
I know not all non-binary people are going to agree with me here, but every
time I see non-binary issues ham-fistedly tacked on to a women's thing, I
think of <a href="https://reductress.com/post/4-inclusive-statements-that-arent-women-and-non-binary-people-i-consider-women/">this brutal Reductress article</a>.
Non-binary people are not a kind of woman. Their issues are not usually women's
issues and vice versa.</p>
<p>Just like #shecodedally posts are going to overwhelm #shecoded posts, #shecoded
posts are going dwarf the number of #theycoded posts. It's not fair to
non-binary people.</p>
<p>Alternately, if you absolutely <em>have</em> to lump all non-men together, then
please in the name of all that is holy, at least find a way to actually treat
non-binary people as <em>equal</em> in value to women instead of an afterthought.
If you want to include non-binary people</p>
<ul>
<li>Don't call the event "SheCoded" with a mention of #theycoded buried in the
text.</li>
<li>Don't hold it on a day called, "International Women's Day."</li>
<li>Don't make the beneficiary an organization called "GirlDevelopIt."</li>
</ul>
<h3>In conclusion</h3>
<p>In spite of all this, I am glad that <a href="http://dev.to/">Dev.to</a> is actively trying to close the
gender gap in tech. I think the problems with the SheCoded program are
significant but very solvable, and I hope that, like a well-maintained
software project, it continues to improve over time.</p>
Writing Elm Ports in ReScript - 0.32022-04-07T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/writing-elm-ports-in-rescript-0.3/<p><em>This is an update to a previous article following a breaking change in the
<code>res-elm</code> binding. In short, the <code>init</code> function has been broken up into
<code>init</code> and <code>initWithOptions</code> to allow for Elm initialization flags and Elm
web applications.</em></p>
<p>Recently I've published an npm package called
<a href="https://npmjs.org/package/res-elm/">res-elm</a> and put it into
production on a couple of projects. It's documented briefly by its README, but
I think it deserves a full post. This post will walk through how to set up
ports both into and out of an Elm 0.19 project using ReScript.</p>
<h3>The Goal: shared control between ReScript and Elm through ports</h3>
<p>The final product is intended to be minimally reproducible and easy to
understand, not necessarily useful. In this case, I think the best page to
show the features of this very small library is a very small web app--an
app with two text boxes that show the ReScript app and the Elm app
communicating in real time. You can find such an app
<a href="https://elmandrescript.gitlab.io/">in this live demo</a>.</p>
<p>Take a moment to play around with the two text boxes. The first one
lives in ReScriptland, but on its input event, ReScript sends its content
into the Elm app. The second lives in Elmland, but on its input event,
sends its input to the ReScript scripts through another port. The result
are two text boxes that always match.</p>
<p>Ordinarily, I would never have a textbox that lives outside the elm
app--I'd give control of the whole view to Elm, but it's easy to imagine that
the app instead has ports to something like an IndexedDB repository, in the
case of my <a href="https://chicagotestout.gitlab.io/">Chicago area COVID-19 tracker</a>,
an HTTP call to some JSON data.</p>
<h3>Basic elm setup</h3>
<p>Detailed instructions for how to write a basic elm project is out of scope for
this kind of post, but I want some elm code here for
completeness--so that I could fully
reproduce this kind of project without having to flip back to the demo
project's source code.</p>
<p>I'll start with two basic messages <code>SendString</code> and <code>UpdateString</code> that
represent the two directions of information flow into and out of the app.</p>
<p><em>Msg.elm</em></p>
<pre class="language-elm"><code class="language-elm"><span class="token keyword">module</span> <span class="token constant">Msg</span> <span class="token keyword">exposing</span> <span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span><br /><br /><span class="token keyword">type</span> <span class="token constant">Msg</span> <span class="token operator">=</span> <span class="token constant">SendString</span> <span class="token constant">String</span><br /> <span class="token operator">|</span> <span class="token constant">UpdateString</span> <span class="token constant">String</span><br /></code></pre>
<p>If you're familiar with Elm ports already, you should be familiar with
JSON encoding/decoding in Elm ports. This is out of the scope of what
I'm trying to demonstrate, so strings here will be fine, but safely parsing
JSON is a best practice, and you'll need it for complex data types.</p>
<p>I also want two ports on this elm app, again representing the
bidirectional flow of data into and out of this elm app.</p>
<p><em>Ports.elm</em></p>
<pre class="language-elm"><code class="language-elm"><span class="token hvariable">port</span> <span class="token keyword">module</span> <span class="token constant">Ports</span> <span class="token keyword">exposing</span> <span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span><br /><br /><span class="token hvariable">port</span> <span class="token hvariable">toReScript</span> <span class="token operator">:</span> <span class="token constant">String</span> <span class="token operator">-></span> <span class="token constant">Cmd</span> <span class="token hvariable">msg</span><br /><br /><span class="token hvariable">port</span> <span class="token hvariable">toElm</span> <span class="token operator">:</span> <span class="token punctuation">(</span><span class="token constant">String</span> <span class="token operator">-></span> <span class="token hvariable">msg</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token constant">Sub</span> <span class="token hvariable">msg</span></code></pre>
<p>And now draw the rest of the owl.</p>
<p><em>Main.elm</em></p>
<pre class="language-elm"><code class="language-elm"><span class="token keyword">module</span> <span class="token constant">Main</span> <span class="token keyword">exposing</span> <span class="token punctuation">(</span><span class="token hvariable">main</span><span class="token punctuation">)</span><br /><br /><span class="token import-statement"><span class="token keyword">import</span> Browser</span><br /><span class="token import-statement"><span class="token keyword">import</span> Html <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span><br /><span class="token import-statement"><span class="token keyword">import</span> Html.Attributes <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span><br /><span class="token import-statement"><span class="token keyword">import</span> Html.Events <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span><br /><span class="token import-statement"><span class="token keyword">import</span> Http</span><br /><span class="token import-statement"><span class="token keyword">import</span> Json.Decode</span><br /><span class="token import-statement"><span class="token keyword">import</span> Models <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token constant">Model</span><span class="token punctuation">)</span><br /><span class="token import-statement"><span class="token keyword">import</span> Msg <span class="token keyword">exposing</span> </span><span class="token punctuation">(</span><span class="token operator">..</span><span class="token punctuation">)</span><br /><span class="token import-statement"><span class="token keyword">import</span> Ports</span><br /><br /><span class="token hvariable">main</span> <span class="token operator">:</span> <span class="token constant">Program</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token constant">Model</span> <span class="token constant">Msg</span><br /><span class="token hvariable">main</span> <span class="token operator">=</span> <span class="token hvariable">Browser.element</span><br /> <span class="token punctuation">{</span> <span class="token hvariable">init</span> <span class="token operator">=</span> <span class="token hvariable">init</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">subscriptions</span> <span class="token operator">=</span> <span class="token hvariable">subscriptions</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">update</span> <span class="token operator">=</span> <span class="token hvariable">update</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">view</span> <span class="token operator">=</span> <span class="token hvariable">view</span><br /> <span class="token punctuation">}</span><br /><br /><span class="token comment">------------------------</span><br /><span class="token hvariable">init</span> <span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token punctuation">(</span><span class="token constant">Model</span><span class="token punctuation">,</span> <span class="token constant">Cmd</span> <span class="token constant">Msg</span><span class="token punctuation">)</span><br /><span class="token hvariable">init</span> _ <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token hvariable">Models.init</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">Cmd.none</span><br /> <span class="token punctuation">)</span><br /><br /> <br /><span class="token hvariable">subscriptions</span> <span class="token operator">:</span> <span class="token constant">Model</span> <span class="token operator">-></span> <span class="token constant">Sub</span> <span class="token constant">Msg</span><br /><span class="token hvariable">subscriptions</span> <span class="token hvariable">model</span> <span class="token operator">=</span><br /> <span class="token hvariable">Sub.batch</span> <span class="token punctuation">[</span> <span class="token hvariable">Ports.toElm</span> <span class="token constant">UpdateString</span> <span class="token comment">--subscribe to incoming string</span><br /> <span class="token punctuation">]</span><br /><br /><span class="token hvariable">update</span> <span class="token operator">:</span> <span class="token constant">Msg</span> <span class="token operator">-></span> <span class="token constant">Model</span> <span class="token operator">-></span> <span class="token punctuation">(</span><span class="token constant">Model</span><span class="token punctuation">,</span> <span class="token constant">Cmd</span> <span class="token constant">Msg</span><span class="token punctuation">)</span><br /><span class="token hvariable">update</span> <span class="token hvariable">msg</span> <span class="token hvariable">model</span> <span class="token operator">=</span><br /> <span class="token keyword">case</span> <span class="token hvariable">msg</span> <span class="token keyword">of</span><br /> <span class="token constant">SendString</span> <span class="token hvariable">str</span> <span class="token operator">-></span> <span class="token punctuation">{</span> <span class="token hvariable">model</span> <span class="token operator">|</span> <span class="token hvariable">str</span> <span class="token operator">=</span> <span class="token hvariable">str</span> <span class="token punctuation">}</span><br /> <span class="token operator">|></span> \<span class="token hvariable">m</span> <span class="token operator">-></span> <span class="token punctuation">(</span> <span class="token hvariable">m</span><span class="token punctuation">,</span> <span class="token hvariable">Ports.toReScript</span> <span class="token hvariable">m</span><span class="token punctuation">.</span><span class="token hvariable">str</span> <span class="token punctuation">)</span> <span class="token comment">--call outgoing port</span><br /> <span class="token constant">UpdateString</span> <span class="token hvariable">val</span> <span class="token operator">-></span> <span class="token punctuation">{</span> <span class="token hvariable">model</span> <span class="token operator">|</span> <span class="token hvariable">str</span> <span class="token operator">=</span> <span class="token hvariable">val</span> <span class="token punctuation">}</span><br /> <span class="token operator">|></span> \<span class="token hvariable">m</span> <span class="token operator">-></span> <span class="token punctuation">(</span><span class="token hvariable">m</span><span class="token punctuation">,</span> <span class="token hvariable">Cmd.none</span><span class="token punctuation">)</span><br /> <br /><span class="token hvariable">view</span> <span class="token operator">:</span> <span class="token constant">Model</span> <span class="token operator">-></span> <span class="token constant">Html</span> <span class="token constant">Msg</span><br /><span class="token hvariable">view</span> <span class="token hvariable">model</span> <span class="token operator">=</span><br /> <span class="token hvariable">div</span> <span class="token punctuation">[</span> <span class="token hvariable">class</span> <span class="token string">"elm-parent"</span> <span class="token punctuation">]</span><br /> <span class="token punctuation">[</span> <span class="token hvariable">h2</span> <span class="token punctuation">[</span> <span class="token hvariable">class</span> <span class="token string">"h2"</span> <span class="token punctuation">]</span> <span class="token punctuation">[</span> <span class="token hvariable">text</span> <span class="token string">"Controlled by Elm"</span> <span class="token punctuation">]</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">input</span> <span class="token punctuation">[</span> <span class="token hvariable">placeholder</span> <span class="token string">"enter some text"</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">type_</span> <span class="token string">"text"</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">onInput</span> <span class="token constant">SendString</span><br /> <span class="token punctuation">,</span> <span class="token hvariable">value</span> <span class="token hvariable">model</span><span class="token punctuation">.</span><span class="token hvariable">str</span><br /> <span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><br /> <span class="token punctuation">]</span><br /></code></pre>
<p>Again, I'm not going to go through every inch of this--I just want it here for
reference. As you can see, the Messages are wired up in the <code>update</code> function
and the <code>onInput</code> event, and the incoming port is wired up in the
<code>subscriptions</code>.</p>
<h3>ReScript project setup</h3>
<p>Next up, initialize a new ReScript project,
and go ahead and install
<a href="https://npmjs.org/package/res-elm/">res-elm</a> and add it to the
<code>bs-dependencies</code>.</p>
<p>Finally, open an <em>Index.res</em> file and expose the module.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">open</span> Elm<span class="token punctuation">;</span></code></pre>
<h4>For completeness</h4>
<p>Next, I'm going to define the logic surrounding the ports. I'll compose my
ports from these functions.</p>
<p>Explaining this code in detail is out of scope for this post. Basically, all
I'm doing is defining bindings for the basic DOM functionality I need like
getting and setting the value of an input and getting the <code>target</code> from a
JavaScript <code>event</code>.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token operator">/*</span> setup<span class="token punctuation">:</span> simple JS dom interop <span class="token operator">*/</span><br /><span class="token operator">@</span><span class="token keyword">val</span> <span class="token operator">@</span>scope<span class="token punctuation">(</span><span class="token string">"document"</span><span class="token punctuation">)</span><br /><span class="token keyword">external</span> getElementById<span class="token punctuation">:</span> string <span class="token operator">=></span> Dom<span class="token punctuation">.</span>element <span class="token operator">=</span> <span class="token string">"getElementById"</span><br /><br /><span class="token operator">@</span>get <span class="token keyword">external</span> getValue<span class="token punctuation">:</span> Dom<span class="token punctuation">.</span>element <span class="token operator">=></span> string <span class="token operator">=</span> <span class="token string">"value"</span><br /><span class="token operator">@</span>set <span class="token keyword">external</span> setValue<span class="token punctuation">:</span> <span class="token punctuation">(</span>Dom<span class="token punctuation">.</span>element<span class="token punctuation">,</span> string<span class="token punctuation">)</span> <span class="token operator">=></span> unit <span class="token operator">=</span> <span class="token string">"value"</span><br /><br /><span class="token operator">@</span>set <span class="token keyword">external</span> setOnInput<span class="token punctuation">:</span> <span class="token punctuation">(</span>Dom<span class="token punctuation">.</span>element<span class="token punctuation">,</span> Dom<span class="token punctuation">.</span>event <span class="token operator">=></span> unit<span class="token punctuation">)</span> <span class="token operator">=></span> unit <span class="token operator">=</span> <span class="token string">"oninput"</span><br /><br /><span class="token operator">@</span>get <span class="token keyword">external</span> getTarget<span class="token punctuation">:</span> Dom<span class="token punctuation">.</span>event <span class="token operator">=></span> Dom<span class="token punctuation">.</span>element <span class="token operator">=</span> <span class="token string">"target"</span><br /><br /><span class="token operator">/*</span> get input element <span class="token operator">*/</span><br /><span class="token keyword">let</span> inputReScript<span class="token punctuation">:</span> Dom<span class="token punctuation">.</span>element <span class="token operator">=</span> getElementById<span class="token punctuation">(</span><span class="token string">"input-rescript"</span><span class="token punctuation">)</span><br /></code></pre>
<h3>Declare the ports as fields in a record</h3>
<p>Initializing the elm app requires a type parameter in the form of a record
in which each field represents a port in our elm app. The <code>res-elm</code> package
includes two types <code>Elm.sendable<'t></code> and <code>Elm.subscribable<'t></code> so that we
can send information to our elm app and subscribe to information from it.</p>
<p>This app is a simple case with just two ports, but I'm going to take the
liberty of defining a module for this type so I can move it to a new file later
if need be.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token keyword">module</span> Ports <span class="token operator">=</span> <span class="token punctuation">{</span><br /> <span class="token keyword">type</span> t <span class="token operator">=</span> <span class="token punctuation">{</span><br /> toElm<span class="token punctuation">:</span> Elm<span class="token punctuation">.</span>sendable<span class="token operator"><</span>string<span class="token operator">></span><span class="token punctuation">,</span><br /> toReScript<span class="token punctuation">:</span> Elm<span class="token punctuation">.</span>subscribable<span class="token operator"><</span>string<span class="token operator">></span><br /> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span><br /></code></pre>
<h3>Get a reference to the elm app</h3>
<p>Now that we have our type, we can get our app. This is should look familiar
to anyone who's written elm (v 0.19) ports in JavaScript. The <code>init</code> function
takes a record which has a single field <code>node</code> of type <code>Dom.element</code>.</p>
<pre class="language-ocaml"><code class="language-ocaml"><span class="token operator">/*</span> get app <span class="token operator">*/</span><br /><br /><span class="token keyword">let</span> app<span class="token punctuation">:</span> Elm<span class="token punctuation">.</span>app<span class="token operator"><</span>Ports<span class="token punctuation">.</span>t<span class="token operator">></span> <span class="token operator">=</span><br /> Elm<span class="token punctuation">.</span>Main<span class="token punctuation">.</span>initWithOptions<span class="token punctuation">(</span><span class="token punctuation">{</span> node<span class="token punctuation">:</span> Some<span class="token punctuation">(</span>getElementById<span class="token punctuation">(</span><span class="token string">"elm-target"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> flags<span class="token punctuation">:</span> None<br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>The result is an <code>Elm.app</code> that gives us access to our ports, so let's use
them.</p>
<h3>Wiring up the events</h3>
<p>This looks like a lot, but all we're doing is taking the <code>Dom.element</code> named
<code>inputReScript</code> and setting its <code>oninput</code> event to a function of a <code>Dom.event</code>.</p>
<p>The <code>app</code> we got earlier has a member called <code>ports</code> (just like in
elm-to-JavaScript ports), and the <code>Elm</code> package has a <code>send</code> binding, so
we <code>send</code> <code>event.target.value</code>, just like we would in JavaScript.</p>
<pre class="language-ocaml"><code class="language-ocaml"><br />inputReScript <br /> <span class="token operator">-></span> setOnInput<span class="token punctuation">(</span>event <span class="token operator">=></span> app<span class="token punctuation">.</span>ports<span class="token punctuation">.</span>toElm<br /> <span class="token operator">-></span> Elm<span class="token punctuation">.</span>send<span class="token punctuation">(</span>event <span class="token operator">-></span> getTarget <span class="token operator">-></span> getValue<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>This next one is a little easier to follow. Here, I'm using the
<code>subscribe</code> binding to set the value of <code>inputReScript</code> whenever our elm app
sends a value through the port.</p>
<pre class="language-ocaml"><code class="language-ocaml"><br />app<span class="token punctuation">.</span>ports<span class="token punctuation">.</span>toReScript <span class="token operator">-></span> Elm<span class="token punctuation">.</span>subscribe<span class="token punctuation">(</span>str <span class="token operator">=></span> setValue<span class="token punctuation">(</span>inputReScript<span class="token punctuation">,</span> str<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /></code></pre>
<p>Now compile to get <em>Index.bs.js</em>.</p>
<h3>Put it all together in the HTML markup</h3>
<p>Now all that's left to do is to put it all together in our HTML markup.</p>
<pre class="language-html"><code class="language-html"> ...<br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>div-rescript-demo<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h2</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>h2<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Controlled by ReScript<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h2</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>input<span class="token punctuation">"</span></span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>input-rescript<span class="token punctuation">"</span></span><br /> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>enter some text<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>elm-target<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span><span class="token comment"><!--end container div--></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>scripts/elm/index.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>scripts/rescript/src/Index.bs.js<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>module<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span></code></pre>
<p>This gives us everything our app is expecting: 1) an "input-rescript" text
box, 2) an "elm-target" div, and 3) references to our scripts.</p>
<p>That finishes our project! Again, a completed example can be found
<a href="https://elmandrescript.gitlab.io/">on my demo site</a>, and
<a href="https://gitlab.com/elmandrescript/elmandrescript.gitlab.io">full source here</a>.
Let me know if you have any questions!</p>
The Second Best Way to Pick Your Next Programming Language2022-05-02T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/the-second-best-way-to-pick-your-next-programming-language/<p>Experienced developers often say that the best way to pick your next language
to learn is to pick a project you want to ship and pick the best language for
the job. This isn't wrong, but beginning developers often respond that the
only "project" they have
in mind for now is to <em>learn more about programming generally</em>
and become a better developer overall. If this is your goal (and it's a good
one!), then the "best way" to pick your next programming language just
isn't applicable. But I think you'll appreciate the second best way.</p>
<h3>Using this guide</h3>
<p>If you're just starting out with programming, it's helpful to understand
<em>why</em> there are so many languages in use. Every language has an underlying
philosophy, a set of opinions about the best way to solve problems or
the best way to solve a certain subset of
problems. A good programmer usually has their
own opinions on how to solve most problems, but is willing to put away
their preferences when there is a particular tool for a particular job.</p>
<p>If you're just starting out in software development or if you only know
one or two languages, your goal should be to develop your own judgement
through a variety of experiences, and picking a variety of programming
languages is a good way to do that. The nice thing is that you don't
have to learn <em>every</em> programming language--languages tend to fall into
certain categories, and once you've learned one language from a category,
you naturally develop a general understanding of the thinking behind
that category as well as a general understanding of what that category
of language is best used for.</p>
<p>My goal for this guide is to introduce a small, nonexhaustive variety of
languages based on a few different common features of programming languages:
type system, orientation, and abstraction level, in no particular order. Within
these categories, I've listed what I think are a few prime examples that have
these features. If your goal is to become a better programmer, you should try
to choose programming languages from different categories to get as broad an
experience as possible until you figure out for yourself what features you
prefer in a programming language.</p>
<p>While I can't separate this post from my own opinions, I will make an effort
to represent all sides, as I understand them, fairly. As a fair warning, since
I am targeting beginners, I will be greatly (over?)simplifying a lot of
advanced concepts, so take any technical nuances with just a grain of salt.</p>
<h3>Abstraction Level</h3>
<p><em>Abstraction</em> is a programming concept that refers to the need to hide details
about the mechanics of a computer in order to focus on the logic specific to a
program. At a very low level of abstraction, a programmer will have to have to
understand things like how numbers are stored in binary or how information is
organized in a computer's memory or the differences in processor architecture,
even down to different kinds of circuits and processor architectures. This
guide won't deal with languages at that level because beginners don't generally
deal with concepts at a very low level. Abstraction level is a matter of
degree, though, and working with lower-level languages can gently introduce
low-level concepts in an easier way.</p>
<h4>Lower-Level languages</h4>
<p>I'm specifically using the word "lower" because none of these are truly <strong>low
level</strong> languages in the way that something like Assembly is.
These are more like
mid-level languages or flexible languages that can optionally use some
low level features. They are often used in situations where developers need
to micromanage hardware or performance, such as:</p>
<ul>
<li>writing device drivers</li>
<li>robotics, especially relating to particularly small and specailized computers</li>
<li>writing compilers or runtimes for other programming languages</li>
<li>malware or information security research</li>
</ul>
<p><strong>C/C++</strong> are relatively old languages and relatively permissive. It's easy
to write what you want, and it's easy to write bugs. It's possible to make
granular optimizations in low-level languages, and it's a good opportunity to
learn about computer hardware, if that's what you're into.</p>
<p><strong>Rust</strong> is a low-level language with a more restrictive compiler (or a more
<em>helpful</em> compiler, depending on how you look at it). It's especially helpful
in dealing with memory safety, which helps with security.</p>
<h3>Higher-level languages</h3>
<p>Higher level languages tend to trade performance for simplicity (from the
developer point of view. These languages usually come with a lightweight
virtual machine to standardize the way it runs on physical machines. (As they
say, "write once, run anywhere.")</p>
<p>You don't have the flexibility that you have to control individual instructions
or memory locations in the way that you do in a lower level language, but
they're often better for managing larger code bases without worrying about the
kinds of bugs that inevitably creep in because of developer (human) error.</p>
<p>Higher-level languages are often used for:</p>
<ul>
<li>web apps</li>
<li>mobile apps</li>
<li>application servers</li>
<li>desktop apps</li>
<li>cross-platform development</li>
</ul>
<p>Higher level languages are wildly diverse in the <em>way</em> that they model a
program, from <strong>Java</strong>, the grandfather of cross-platform development to
<strong>Python</strong> the beloved scripting language. (Basically every language on this
page is a higher-level language, so I'm not going to elaborate here).</p>
<h4>A word on performance concerns</h4>
<p>It's easy for beginners to get way over-focused on performance. There are
three problems with this kind of thinking.</p>
<p>One is, as they say, "premature
optimization is the root of all evil." You might be tempted to try to adopt
a lower level language with the intention of taking advantage of every
performance optimization you possibly can, for the fastest possible version of
the app you wanted to write. The next thing you know, you have a small bug
that's causing performance problems, but you can't fix it because your
super-optimized code is very difficult to read.</p>
<p>The second problem is that your super-optimized code probably won't actually
have too many optimizations that you wouldn't be taking advantage of in a
higher-level context. I'm telling you that the people who write compilers and
runtimes are pretty smart and have a lot more help than you do. They know
about these optimizations, and, most of the time, they're already taking
advantage of them.</p>
<p>The last is YAGNI. You Ain't Gonna Need It. So let's say you're trying to
write the most memory-optimized desktop app ever. Okay. But can you remember
the last time you ran out of memory on your desktop? If it's been a while,
you may be sacrificing for something like low-memory usage when you won't get
any benefit.</p>
<p>So unless you're doing something pretty specialized, you probably won't benefit
from focussing on performance too much too early. On the other hand: do what
you want! Learn what you want! If you're interested in learning about
performance, then pursue it to your heart's content, and using lower-level
languages is a good way to do that. Just don't feel like your programs will
necessarily suffer if you pick a higher-level language.</p>
<h3>Type Systems</h3>
<p>Of all language features, type systems seem to generate the hottest takes.
I certainly have my preferences like everyone else, but know that there are
exceptional developers on all sides of the debate. There are so many dimensions
to the type system discussion from type dynamism to type strength to
exhaustiveness to inferencing, and as an experienced developer I'm still
learning new ways of thinking about types every day, but for the purposes of
this article, I'm going to focus on a beginner-friendly and salient attribute
of type system: dynamism vs staticism.</p>
<h4>Static typing</h4>
<p>A language is statically typed if the type of a value is determined at
compile time instead of runtime. This requires more from the developer up-front
before a program will run at all, but it can prevent certain types of runtime
exception.</p>
<p>For example, if I have a <code>string x</code> in a statically-typed language, and I later
try to assign a number to <code>x</code>, the program will not run at all because it will
not compile. This is considered a feature because it prevents developers from
accidentally using certain kinds of data--the compiler error prevents certain
kinds of bugs from going into production. It can also have performance benefits
because the compiler can make optimizations if it can make specific assumptions
about the underlying data.</p>
<p>By some metrics, Java is the most popular statically-typed language. C#, Java's
close cousin, is mostly statically typed but can allow dynamic types through
the rarely-used <code>dynamic</code> keyword. TypeScript, ReScript, and Elm are all
examples of languages which apply a statically typed compiler to produce
otherwise dynamically typed JavaScript.</p>
<h4>Dynamic typing</h4>
<p>In dynamically typed languages, no such constraint exists which prevents a
piece of code from running <em>at all</em>. Rather, types of values are determined
at runtime.</p>
<p>Proponents of dynamic typing argue that this makes code easier to change and
refactor because there are no type declarations that have to be changed.
Proponents of static typing argue that this makes it harder to change <em>with
confidence</em> because without type declarations, you can be less sure the
change you made won't result in a runtime error.</p>
<p>In general, dynamically typed languages tend to be used for smaller pieces of
code, which is why there's a strong overlap between dynamic languages and languages called <em>scripting languages</em>--JavaScript, Bash, PowerShell, etc. However,
devotees of dynamic typing may also decide to write larger programs like web
services in dynamically typed languages. Python and Ruby are good examples of
this.</p>
<h3>Orientation</h3>
<p>In the beginning, there was procedural code. And the code was bad.</p>
<p>Object orientation and functional orientation are two language features that
facilitate better code organization to keep even extremely large codebases
manageable, maintainable, easy to reason about, and easy to change
without introducing subtle bugs. After so many years, the holy wars between
the two philosophies are starting to wind down and the lines between
functional languages and object oriented languages are starting to blur as
languages continue to add features without regard for ideological purity.</p>
<p>What's great about learning both philosophies is that they can each be
judiciously applied even in languages that aren't geared toward them--i. e.,
you can use functional principles in object oriented languages and vice versa.</p>
<p>In order to learn both philosophies, though, I recommend learning languages
strongly geared to one side or another as well as languages that facilitate
both very easily.</p>
<h4>Object orientation</h4>
<p>Object orientation is something you may well already be familiar with. It is
well supported in many of the most popular languages such as <strong>Java,
JavaScript, C#, C++, and Python</strong>.</p>
<p>If you have done a Computer Science
undergraduate program, you're probably <em>very</em> familiar with object orientation.
You've probably studied and implemented the "Gang of Four" object
oriented design patterns. Perhaps you can also rattle off the principles of
S.O.L.I.D. code or the four object-oriented principles.</p>
<p>Obviously, if you're not familiar with object orientation at all, there's a lot
to learn, and while I won't cover all of that here, I hope I've given you
enough to start your search because object orientation, as you will see on
any job search site, is incredibly employable and you'll need it if you want
to have a career, but don't sleep on the next section as the world slowly
opens up to the functional orientation paradigm.</p>
<h4>Functional orientation</h4>
<p>If you're a beginner, the functional paradigm may be something you're not
familiar with, but it's an excellent arrow for your quiver. You might be
familiar with the conventional wisdom that "learning Haskell will make you a
better programmer" because Haskell is a sort of an extreme example of languages
that are highly restrictive and won't compile without conforming to a set of
best practices.</p>
<p>While object orientation tries to prevent state management bugs by encouraging
encapsulation, functional programming languages encourage (or outright require)
immutability; there can be no state management bugs if a given value can only
have one state ever. (This has an added benefit of automatic thread-safety.)</p>
<p>Further, while object oriented programs tend to be composed of procedure-like
<em>statements</em>, functional languages encourage programmers to write in
<em>expressions</em> instead, which often results in shorter, cleaner functions.</p>
<p>Lastly, functional programming languages tend to have more exhaustive
type systems geared toward making invalid states unrepresentable. They do this
by encouraging the use of enum-like structures to define all possible
states, which the type-checker can then use to ensure that all possible
states are considered in branched logic.</p>
<p>Today, many object oriented languages have added various functional
programming features (often in the form of syntactic sugar on
top of existing features), but I would recommend learning at least
two functional-first languages that don't support object orientation
at all. Some good examples include Elm, Haskell, LISP, and ReScript.</p>
<h4>Peanut butter cup languages</h4>
<p>Most languages are either primarily functional or primarily object
oriented, but there are also a few unopinionated languages designed
with both paradigms in mind. You can use these languages in
<em>either</em> way, or you can use both feature sets and combine them
in whatever ways make the most sense for your program.</p>
<p>You may find that you really like this Swiss-army-knife approach, or you may
decide a language shouldn't try to be a Jack-of-all-trades. (I titled this
section based on
<a href="http://james-iry.blogspot.com/2009/05/brief-incomplete-and-mostly-wrong.html">an old joke about the Scala programming language</a>
that I feel really captures both sentiments.)</p>
<p>Scala and F# are both firmly in this category, with honorable mention to
Kotlin and strict-mode TypeScript. I would not, however, include languages
like Java and C# in this category because they are both so primarily
object oriented; while they support functional programming, they do little
to encourage exploration in that area.</p>
<h3>Syntax</h3>
<p>This is a minor point, but it's worth working with different syntactic styles.
To generalize broadly, programs are made up of blocks which are made up of
lines, and blocks can be surrounded by braces or offset with indentation, and
line ends can be marked by semicolons or inferred from newline characters.
There are some exceptions (LISP, for example, is sometimes affectionately
called Lots of Infuriating and Silly Parentheses because, well, it delimits
everything with parentheses), but you'll find most languages lend themselves
to one of these patterns.</p>
<p>The distinction can be a little blurry. Scala and JavaScript, for example, have
<em>optional</em> semicolons, and many a holy war has been fought over whether it's
best to have them there. I would recommend learning at least one language that
doesn't have any significant white space and one language that uses
significant whitespace primarily.</p>
<p>What you should take away from these different experiences is that
<em>code readability is subjective</em>. We can poll and study and let the market
decide, but at the end of the day, you might find a language very clear and
easy to read that I think looks like gibberish, or vice versa. You may join
the syntax holy wars, or you may decide you fundamentally don't care. Clearly,
there's room in the market for a variety of preferences.</p>
<h4>C-like</h4>
<p>C is an old language and it's syntax choice have influenced other languages
which prefer visible delimiting characters over whitespace. Therefore, in
many languages, blocks are surrounded with braces and lines end in semicolons.</p>
<p>Proponents of C-like languages argue that having a visible character like a
brace at the beginning and end of a block makes it easier to tell when a
block begins and ends. It also makes formatting more flexible because it
means that you don't <em>have</em> to break your lines of code into visual lines--
you can put multiple lines of code on one line or put a whole block of lines
on one line, stylistically, as long as your have the proper characters
in place.</p>
<p><em>pseudocode</em></p>
<pre class="language-c"><code class="language-c"><span class="token comment">// you can do this if you want</span><br /><span class="token keyword">if</span> <span class="token punctuation">(</span>condition<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">statement</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">anotherStatement</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">aThirdStatement</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span><br /><br /><span class="token comment">// even though most code is formatted like this</span><br /><span class="token keyword">if</span> <span class="token punctuation">(</span>condition<span class="token punctuation">)</span> <br /><span class="token punctuation">{</span> <br /> <span class="token function">statement</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <br /> <span class="token function">anotherStatement</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <br /> <span class="token function">aThirdStatement</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <br /><span class="token punctuation">}</span> <br /><span class="token keyword">else</span> <br /><span class="token punctuation">{</span> <br /><span class="token punctuation">}</span></code></pre>
<p>Examples of C-like languages include Java, C#, and, of course, C.</p>
<h4>Whitespace delimited</h4>
<p>Opponents of C-like syntax argue that the visible delimiting characters are
redundant because most of the time the newlines and indentation are necessary
for readability anyway. They argue that omitting visible delimiting characters
makes the actual content of the code--the function calls--more visible, and
therefore the code more readable overall.</p>
<p>Python is many people's first introduction to whitespace-delimited languages,
but there are others. Many functional languages such as Elm and OCaml use
whitespace to different degrees.</p>
<h3>In conclusion</h3>
<p>I hope that this article has been a good starting point. While I tried to
include some example languages (that I know of) in each section, but I
encourage you to use this guide as a starting point as you look for languages.
Importantly, don't get overfocused on the "best" language to learn.
Ultimately, if there's a language that is intuitive and enjoyable to you,
that will be your best and most profitable language. Happy coding!</p>
Continuously Deploying to GitLab's NPM Package Registry2022-06-04T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/continuously-deploying-to-gitlabs-npm-package-registry/<p>In a
<a href="https://webbureaucrat.gitlab.io/articles/better-continuous-deployment-with-gitlab-ci-cd">previous article</a>,
we explored how to continuously deploy to the <a href="http://npmjs.com/">npmjs.com</a> package
registry. This is all well and good, but an advantage of CI/CD is that we can
easily deploy wherever we want. This article will explore how to deploy an
npm package to both <a href="http://npmjs.com/">npmjs.com</a> and GitLab's own package registry, including how
to change package names when necessary.</p>
<h3>Starting point: ordinary CI/CD for npm</h3>
<p>The earlier article built out a complete <em>.gitlab.yml</em> file for an npm
package, and it leverages environment variables in a way that makes it very
transferrable between projects. I will be adding a stage to this pipeline, so
I recommend familiarizing yourself with this pipeline as a starting point
before proceeding. The comments in the first stage provide a kind of legend
for the file in case you're not familiar with <em>.gitlab.yml</em> attributes.</p>
<pre class="language-yml"><code class="language-yml"><span class="token key atrule">image</span><span class="token punctuation">:</span> node<span class="token punctuation">:</span>latest<br /><br /><span class="token key atrule">compile</span><span class="token punctuation">:</span> <span class="token comment"># arbitrary name to identify the script</span><br /> <span class="token key atrule">stage</span><span class="token punctuation">:</span> build <span class="token comment"># indicates its chronological order in the pipeline</span><br /> <span class="token key atrule">script</span><span class="token punctuation">:</span> <br /> <span class="token punctuation">-</span> npm ci <span class="token comment"># the recommended best practice for CI/CD (as opposed to npm i)</span><br /> <span class="token punctuation">-</span> npm run build <br /> <span class="token key atrule">only</span><span class="token punctuation">:</span> <br /> <span class="token punctuation">-</span> dev <span class="token comment"># only run this script for the dev branch</span><br /> <br /><span class="token key atrule">test</span><span class="token punctuation">:</span><br /> <span class="token key atrule">stage</span><span class="token punctuation">:</span> test<br /> <span class="token key atrule">script</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> npm ci<br /> <span class="token punctuation">-</span> npm run build<br /> <span class="token punctuation">-</span> npm run test<br /> <span class="token key atrule">only</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> dev<br /><br /><span class="token key atrule">merge</span><span class="token punctuation">:</span><br /> <span class="token key atrule">only</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> dev<br /> <span class="token key atrule">script</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> git remote set<span class="token punctuation">-</span>url origin https<span class="token punctuation">:</span>//merge<span class="token punctuation">-</span>token<span class="token punctuation">:</span>$<span class="token punctuation">{</span>MERGE_TOKEN<span class="token punctuation">}</span>@gitlab.com/$<span class="token punctuation">{</span>CI_PROJECT_NAMESPACE<span class="token punctuation">}</span>/$<span class="token punctuation">{</span>CI_PROJECT_NAME<span class="token punctuation">}</span>.git<br /> <span class="token punctuation">-</span> git pull origin main<br /> <span class="token punctuation">-</span> git checkout main<br /> <span class="token punctuation">-</span> git merge origin/dev<br /> <span class="token punctuation">-</span> git push origin main<br /> <span class="token key atrule">stage</span><span class="token punctuation">:</span> deploy<br /> <br /><span class="token key atrule">deploy</span><span class="token punctuation">:</span><br /> <span class="token key atrule">only</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> main<br /> <span class="token key atrule">stage</span><span class="token punctuation">:</span> deploy<br /> <span class="token key atrule">script</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> echo "//registry.npmjs.org/<span class="token punctuation">:</span>_authToken=$<span class="token punctuation">{</span>NPM_TOKEN<span class="token punctuation">}</span>" <span class="token punctuation">></span><span class="token punctuation">></span> .npmrc<br /> <span class="token punctuation">-</span> npm publish</code></pre>
<p>If you're still confused, refer to the
<a href="https://webbureaucrat.gitlab.io/articles/better-continuous-deployment-with-gitlab-ci-cd/">previous article</a>,
which builds this file step by step (or, as always,
<a href="https://gitlab.com/groups/webbureaucrat/-/issues">open an issue</a>).</p>
<h3>Scaffolding our new deployment step</h3>
<p>Let's start by creating a new deployment script which runs on the main branch
in the deployment stage.</p>
<pre class="language-yml"><code class="language-yml"><span class="token key atrule">gitlab-deploy</span><span class="token punctuation">:</span><br /> <span class="token key atrule">only</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> main<br /> <span class="token key atrule">script</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> echo "Hello<span class="token punctuation">,</span> world."<br /> <span class="token key atrule">stage</span><span class="token punctuation">:</span> deploy</code></pre>
<p>So now we've defined the circumstances under which we want to run.</p>
<h3>Managing your package namespace and name</h3>
<p>I would expect that you'd want to publish your package under the same name
on GitLab as <a href="http://npmjs.com/">npmjs.com</a>, but the GitLab package registry requires that all
packages be namespaced by their repository namespace.</p>
<p>So we need to swap out our old package name with our new
namespaced package name. For me,
since I am working in ReScript and need to rename in <em>bsconfig.json</em> as well
as the package files, I've found the easiest way to reconfigure my namespace
is through the npm package <code>file-find-replace-cli</code>.</p>
<p>(You could also use <code>sed</code> if you're smart enough, but I've decided my life is
too short for that.)</p>
<p><code>file-find-replace-cli</code> uses a json file to define its commands. This can be
predefined and checked into source control, but it's more fun to <code>echo</code> it out
from environment variables (which will make our <em>.gitlab.yml</em> more reusable).</p>
<pre class="language-yml"><code class="language-yml"><span class="token key atrule">gitlab-deploy</span><span class="token punctuation">:</span><br /> <span class="token key atrule">only</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> main<br /> <span class="token key atrule">script</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> npm i <span class="token punctuation">-</span>g file<span class="token punctuation">-</span>find<span class="token punctuation">-</span>replace<span class="token punctuation">-</span>cli<br /> <span class="token punctuation">-</span> echo "<span class="token punctuation">{</span> \"find\"<span class="token punctuation">:</span>\"$CI_PROJECT_NAME\"<span class="token punctuation">,</span>" <span class="token punctuation">></span><span class="token punctuation">></span> replace.json<br /> <span class="token punctuation">-</span> echo "\"replace\"<span class="token punctuation">:</span>\"@$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME\" <span class="token punctuation">}</span>" <span class="token punctuation">></span><span class="token punctuation">></span> replace.json<br /> <span class="token key atrule">stage</span><span class="token punctuation">:</span> deploy</code></pre>
<p>With the package installed and the <em>replace.json</em> file in place, you can run
the <code>find-replace</code> command against your <em>package.json</em> and <em>package-lock.json</em>
files using <code>package*.json</code> (and <em>bsconfig.json</em> if you, like me, are using
ReScript).</p>
<pre class="language-yml"><code class="language-yml"><span class="token key atrule">gitlab-deploy</span><span class="token punctuation">:</span><br /> <span class="token key atrule">only</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> main<br /> <span class="token key atrule">script</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> npm i <span class="token punctuation">-</span>g file<span class="token punctuation">-</span>find<span class="token punctuation">-</span>replace<span class="token punctuation">-</span>cli<br /> <span class="token punctuation">-</span> echo "<span class="token punctuation">{</span> \"find\"<span class="token punctuation">:</span>\"$CI_PROJECT_NAME\"<span class="token punctuation">,</span>" <span class="token punctuation">></span><span class="token punctuation">></span> replace.json<br /> <span class="token punctuation">-</span> echo "\"replace\"<span class="token punctuation">:</span>\"@$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME\" <span class="token punctuation">}</span>" <span class="token punctuation">></span><span class="token punctuation">></span> replace.json<br /> <span class="token punctuation">-</span> find<span class="token punctuation">-</span>replace replace.json <span class="token punctuation">-</span>f 'bsconfig.json' <span class="token punctuation">-</span>f 'package<span class="token important">*.json'</span><br /> <span class="token punctuation">-</span> npm config set @$<span class="token punctuation">{</span>CI_PROJECT_NAMESPACE<span class="token punctuation">}</span><span class="token punctuation">:</span>registry https<span class="token punctuation">:</span>//gitlab.com/api/v4/projects/$<span class="token punctuation">{</span>CI_PROJECT_ID<span class="token punctuation">}</span>/packages/npm/<br /> <span class="token punctuation">-</span> npm config set <span class="token punctuation">-</span><span class="token punctuation">-</span> '//gitlab.com/api/v4/projects/$<span class="token punctuation">{</span>CI_PROJECT_ID<span class="token punctuation">}</span>/packages/npm/<span class="token punctuation">:</span>_authToken' "$<span class="token punctuation">{</span>CI_JOB_TOKEN<span class="token punctuation">}</span>"<br /> <span class="token punctuation">-</span> npm publish<br /> <span class="token key atrule">stage</span><span class="token punctuation">:</span> deploy</code></pre>
<p>Next, set the package registry to GitLab for the namespace you just changed
to, and authenticate with the built-in <code>CI_JOB_TOKEN</code>, and, finally, publish.</p>
<pre class="language-yml"><code class="language-yml"><span class="token key atrule">gitlab-deploy</span><span class="token punctuation">:</span><br /> <span class="token key atrule">only</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> main<br /> <span class="token key atrule">script</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> npm i <span class="token punctuation">-</span>g file<span class="token punctuation">-</span>find<span class="token punctuation">-</span>replace<span class="token punctuation">-</span>cli<br /> <span class="token punctuation">-</span> echo "<span class="token punctuation">{</span> \"find\"<span class="token punctuation">:</span>\"$CI_PROJECT_NAME\"<span class="token punctuation">,</span>" <span class="token punctuation">></span><span class="token punctuation">></span> replace.json<br /> <span class="token punctuation">-</span> echo "\"replace\"<span class="token punctuation">:</span>\"w$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME\" <span class="token punctuation">}</span>" <span class="token punctuation">></span><span class="token punctuation">></span> replace.json<br /> <span class="token punctuation">-</span> find<span class="token punctuation">-</span>replace replace.json <span class="token punctuation">-</span>f 'bsconfig.json' <span class="token punctuation">-</span>f 'package<span class="token important">*.json'</span><br /> <span class="token punctuation">-</span> npm config set @$<span class="token punctuation">{</span>CI_PROJECT_NAMESPACE<span class="token punctuation">}</span><span class="token punctuation">:</span>registry https<span class="token punctuation">:</span>//gitlab.com/api/v4/projects/$<span class="token punctuation">{</span>CI_PROJECT_ID<span class="token punctuation">}</span>/packages/npm/<br /> <span class="token punctuation">-</span> npm config set <span class="token punctuation">-</span><span class="token punctuation">-</span> '//gitlab.com/api/v4/projects/$<span class="token punctuation">{</span>CI_PROJECT_ID<span class="token punctuation">}</span>/packages/npm/<span class="token punctuation">:</span>_authToken' "$<span class="token punctuation">{</span>CI_JOB_TOKEN<span class="token punctuation">}</span>"<br /> <span class="token punctuation">-</span> npm publish<br /> <span class="token key atrule">stage</span><span class="token punctuation">:</span> deploy</code></pre>
<h3>Publish or perish</h3>
<p>I hope this has been useful to developers trying to deploy to GitLab's
registry or deploy to multiple registries. The default <a href="http://npmjs.org/">npmjs.org</a> registry is
an invaluable developer resource, but it's always a good idea to have
multiple baskets for one's proverbial eggs. (More on this in a future article.)</p>
De-Microsofting Your Development Environment2022-07-02T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/de-microsofting-your-environment/<p>Anyone who has been
following the web ecosystem over the last few
years knows it has been rapidly consolidating
under Microsoft. That consolidation
economically threatens the open web.
Diversifying your tooling could protect you
as much as diversifying any investment, and
it's much more convenient than you might
think.</p>
<h3>Why not just use all the Microsoft tooling?</h3>
<p>Too often, anything even remotely critical of
Microsoft gets dismissed as archaeic,
Ballmer-era neckbeard
bias. I want to be clear here:
I do not hate Microsoft.
I'm very well aware that Nadella's
Microsoft is probably among the most
open-source friendly companies, especially
for its size. Microsoft's climate goals are
second to none. Microsoft's F# programming
language is a <em>pretty good language,</em> which
is a big statement coming from me, a person who hates
almost every programming language.</p>
<p>That said, I still have reasons for wanting
to diversify my tooling, and I think those
reasons are broadly applicable.</p>
<ul>
<li><strong>Policies can change.</strong> Microsoft's shift toward better corporate
citizenship over the years shows how business practices from change over time.
The next CEO might be as different from Nadella as Nadella is from his
predecessors.</li>
<li><strong>Pricing can change.</strong> Vendor lock-in is an insideous thing. Before using
any vendor-specific features of a platform, ask yourself how much of a price
hike your business could reasonably tolerate and, beyond that price,
what the engineering cost of the shift away would be.</li>
<li><strong>Licenses can change.</strong> Remember when Facebook tried to change the terms of
React.js? Small development teams were scrambling to switch, knowing Facebook's
legal team could bury them if they wanted. Facebook backed down because
React as a product isn't a big part of Facebook's business and because the
market for free front-end frameworks is famously (infamously?) oversaturated. A
company with actual market power over the open web, however, would be a real
threat.</li>
<li><strong>Interoperability is expensive.</strong> Microsoft will often face an engineering
cost to adhering to standards it doesn't control in-house. Changing open
standards requires consortia and drafts and meetings and RFCs--slow,
democratic processes. And if the open standards require implementing features
it doesn't care about, it will still have to implement them to remain
interoperable. Therefore, we should assume that the long-term incentives are
against open standards.</li>
<li><strong>Discouraging ICE contracts is still important.</strong> I realize this hasn't
been in the headlines for a long time, but it's still true that Microsoft has
relationships with DoD and DHS which are troubling to conscientious consumers.
One reason I avoid Microsoft products is that I want to believe that violating
human rights is <em>expensive</em> to their business.</li>
<li><strong>Important technical decisions should never be made by default.</strong> My
entire career has been spent on the .NET stack, regardless of requirements.
Don't do that! Figure out what tools will be best for <em>your</em> use case, and
don't assume it's what you've always done (and if your developers can't learn
new things, hire better developers).</li>
</ul>
<p>None of this is to say that no one should ever use any Microsoft product, but
I would encourage anyone to manage their risk just by considering some
alternatives, some of the time. I'm going to try to avoid the obvious
clickbait-y answers ("Use Firefox and LibreOffice and AWS!") and hopefully
provide some value off the beaten path.</p>
<h3>Backend-as-a-service</h3>
<p>The selling point of Azure--its easy-to-use, seamless integrations--are also
its danger. Consider these alternatives if you don't want to take risks with
price increases.</p>
<ul>
<li><strong>Parse Platform</strong> is an impressive little open source back end. <strong>Back4App</strong>
offers Parse Platform hosting, but it's portable, so if Back4App ever goes
away or hikes prices, you can simply spin up your own instance and move your
data. Notably, it has a built-in authentication platform if you want a
secure way to avoid privacy-hostile third-party OAuth integrations.</li>
<li><strong>Jelastic</strong> is pretty much my favorite cloud platform because it <em>truly</em>
commodifies cloud hosting. If you're not familiar, Jelastic is a company that
licenses cloud platform software to anyone with a server farm. You can shop
by price or server region, and if you don't like the service, you can always
choose to do business elsewhere.</li>
<li><strong>PostgREST</strong> doesn't fit neatly into this category, but it's a close
substitute if you want a quick-and-easy REST API with a little more control
than you might have in a traditional BaaS.</li>
</ul>
<h3>Text editors</h3>
<p>Visual Studio Code has taken the
development world by storm, and not without
good reason. Microsoft's fork of Atom is pluggable
and keyboard-navigable without sacrificing
beginner-friendliness.</p>
<p>But I'm still a little surprised by its
popularity. It's my considered opinion
that for most uses, good ol' reliable emacs
has it beat.</p>
<p>Much digital ink has been spilt against
the abysmal resource management of
Electron apps, so I won't dwell on it here,
but I will say that it's a little absurd
that in the current year we still have to
think about the speed and memory consumption
of text editors. Visual Studio and Visual
Studio Code aren't that slow realtive to
other full-blown IDEs, but once you free
yourself from having to actually wait for
your editor to open, it's hard to go back.</p>
<p>But it's not just about <em>computing</em> speed--
it's also about human speed. Mousing around
is slow and, in my opinion, unpleasant
because it breaks the state of flow.</p>
<p>As an additional benefit, I really think
every developer should learn one CLI
text-editor really well for the times when
getting a GUI set up would be inconvenient or
unrealistic. The last time I tried to use
the SSH feature on VSCode, it was a buggy
mess. I'd rather just use a battle-tested
editor like emacs.</p>
<h3>Web Framework</h3>
<p>It would be silly to list a bunch of well-known web frameworks for an
audience of developers, but there is one that's worth highlighting:
Play Framework. Play Framework is based on <a href="http://asp.net/">ASP.NET</a> MVC, and any <a href="http://asp.net/">ASP.NET</a> MVC
developer would feel right at home, but there are a few reasons why I
prefer Play:</p>
<ul>
<li>
<p>C# is, bless its heart, trying to be a better functional language, but it's
burdened with the legacy of its many years trying to be a Java clone. Scala
is also both object-oriented and functional, but it's more opinionated in its
approach, allowing for cleaner, more uniform code.</p>
</li>
<li>
<p>Play is necessarily stateless, so it scales easily by default.</p>
</li>
<li>
<p>Twirl templates (the Play answer to Razor templates) act as functions that
take parametric inputs and evaluate to HTML. This is much more intuitive
and flexible than the object-oriented model-binding in <a href="http://asp.net/">ASP.NET</a>.</p>
</li>
</ul>
<h3>JavaScript that Scales</h3>
<p>It's troubling to me that the ongoing (neverending?) debate about static types
on the browser platform has defaulted to "TypeScript or Not TypeScript."
Conversely, the conversation around "Should you adopt TypeScript?" usually
seems to begin and end with "Do you want to use static types in the browser?"</p>
<p>This is nonsense, honestly. There are lots of mature, production-ready
languages with robust ecosystems that offer static typing in the browser, and
it's pretty irresponsible to just default to TypeScript without weighing the
benefits against the alternatives. Further, if you <em>are</em> so inclined toward
compile-time safety checks, TypeScript frankly offers the least safety in
favor of being the most JavaScript-like.</p>
<ul>
<li>
<p><strong>Elm</strong> is a combination framework-and-language which guarantees no runtime
errors except for stack overflows (infinite recursion) or through outside
JavaScript interop. Mutation is not supported by the language at all, which,
like static types, significantly decreases the mental load. If you aren't
familiar with Haskell-like languages, it may have a significant learning
curve, but the ability to write completely safe UIs in the browser usually
makes it worth it.</p>
</li>
<li>
<p><strong>PureScript</strong> is another Haskell-like language with a high degree of safety,
but without a baked-in architecture. Despite its safety it has a relatively
short learning curve because it's not overloaded with features.</p>
</li>
<li>
<p><strong>ReScript</strong> is an OCaml dialect with a very TypeScript-like syntax. If
you're interested in the safety of functional programming but miss your
semicolons and braces, this could be a good language for you.</p>
</li>
<li>
<p>And, of course a ton of other languages which didn't start in the browser
have been adapted to it through transpilation or WASM-compilation. ScalaJS and
Fable and Blazor and Rust and js_of_ocaml and... you get the idea. And look,
if out of all those you still think TypeScript is your best bet, that's great!
But we're <em>way</em> past the point in time where it makes sense to treat it as a
default.</p>
</li>
</ul>
<h3>Git Hosting</h3>
<p>This one just seems obvious to me. If you're considering making just one
switch, I recommend it be this one because it's just so easy.</p>
<p>Outside of network effects, I can't see any reason why anyone would
pick GitHub over GitLab. There are only three significant differences between
GitHub and GitLab as far as I can see:</p>
<ol>
<li>GitLab is open source.</li>
<li>GitLab has a more mature devops platform.</li>
<li>GitHub has an ICE contract.</li>
</ol>
<p>end of list.</p>
<p>When you consider that it only takes two (2) commands to move an entire
repository from one hosting service to another, it just eludes me why people
use GitHub.</p>
<p>I have seen maybe one or two job applications that ask for a GitHub username,
specifically, but my answer to that is just to leave my GitHub profile blank
except for a link to my GitLab (and I've only seen this about twice out of
hundreds of job applications).</p>
<h3>Catch-all Consumer Collaboration</h3>
<p>Whenever I find myself looking for a privacy-friendly alternative to a
browser-based service, I tend to start with
<a href="https://disroot.org/en/#services">disroot.org</a>.
In a nutshell, they provide access to free instances of open source online
software, including email, conferencing, and several different services for
document collaboration. They also have developer-focused services like git
hosting and issue board software.</p>
<p>I highlight Disroot specifically because it's unusual for a service to be both
very batteries-included consumer-friendly software and also be so open source
and privacy friendly. By default, I don't want to self-host everything and
administer my own server, but I like knowing that option is open to me if I
want it.</p>
<h3>Hitting "end task" on lock-in</h3>
<p>It's not my intention to moralize at anyone, and I know that there are shops
where a broad Microsoft service contract makes sense, but if you like
exploring your options and you want to feel like you really <em>own</em> your
projects, then I hope I've provided some good starting points in your search
for alternatives.</p>
Running Play Framework on NixOS using JDK 112022-08-01T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/running-play-framework-on-nixos-using-jdk-11/<p>Good morning! First, some personal news: I'm switching to NixOS, and I'm kind
of excited about it, so expect some articles to that effect. Today, I'm
celebrating the ease with which I got a Play Framework environment up and
running, including installing sbt and downgrading to JDK 11.</p>
<p>As a software developer, I do not at appreciate having to fight with my
environment, so when I heard that NixOS uses a purely functional, declarative
approach to maintaining packages, I was interested. Recently, my approach
maintaining a clean desktop has
<a href="https://webbureaucrat.gitlab.io/articles/containerizing-your-cli-tools-for-a-clean-development-experience/">revolved around docker</a>, so I'm open to less
hacky approaches for sure.</p>
<p>The real test is sbt, which in my experience is a finicky piece of software.
Anything with a Java dependency can get complicated already, and sbt is
particularly stateful as it downloads its dependencies on the fly. So far I
haven't been successful getting the Docker-and-alias trick above to work.</p>
<h3>Installing <code>sbt</code> on NixOS</h3>
<p>...is pleasantly trivial. There is an
<a href="https://search.nixos.org/packages?channel=22.05&from=0&size=50&sort=relevance&type=packages&query=sbt"><code>sbt</code> package in the official
repository</a>
which can be installed in the usual NixOS
way in the packages list in the <em>/etc/nixos/configuration.nix</em> file.</p>
<p><em>/etc/nixos/configuration.nix</em></p>
<pre class="language-nix"><code class="language-nix"> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><br /> <br /> environment<span class="token punctuation">.</span>systemPackages <span class="token operator">=</span> <span class="token keyword">with</span> pkgs<span class="token punctuation">;</span> <span class="token punctuation">[</span><br /> docker<br /> emacs<br /> firefox<br /> git<br /> gnome<span class="token punctuation">.</span>gnome<span class="token operator">-</span>tweaks<br /> sbt<br /> wget<br /> <span class="token punctuation">]</span><span class="token punctuation">;</span><br /><br /> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span></code></pre>
<p>Then, to make the change take effect, run <code>sudo nixos-rebuild switch</code>.</p>
<p>After this, I can run <code>sbt</code> against my existing project and it works smoothly.</p>
<p>But it's not compatible with Play at runtime.</p>
<h3>UncheckedExecutionException: java.lang.IllegalStateException: Unable to load cache item</h3>
<p>When I run the application, it serves exceptions:</p>
<p><em>console output</em></p>
<pre><code>--- (Running the application, auto-reloading is enabled) ---
p.c.s.AkkaHttpServer - Listening for HTTP on /[0:0:0:0:0:0:0:0]:9000
(Server started, use Enter to stop and go back to the console...)
2022-07-04 12:46:00 ERROR p.api.http.DefaultHttpErrorHandler
! @7o7k89hhe - Internal server error, for (GET) [/] ->
play.api.UnexpectedException: Unexpected exception[UncheckedExecutionException: java.lang.IllegalStateException: Unable to load cache item]
at play.core.server.DevServerStart$$anon$1.reload(DevServerStart.scala:254)
at play.core.server.DevServerStart$$anon$1.get(DevServerStart.scala:148)
at play.core.server.AkkaHttpServer.handleRequest(AkkaHttpServer.scala:302)
at play.core.server.AkkaHttpServer.$anonfun$createServerBinding$1(AkkaHttpServer.scala:224)
at akka.stream.impl.fusing.MapAsync$$anon$30.onPush(Ops.scala:1307)
at akka.stream.impl.fusing.GraphInterpreter.processPush(GraphInterpreter.scala:542)
...
</code></pre>
<p><em>stack trace from error page</em></p>
<pre><code>com.google.common.util.concurrent.UncheckedExecutionException: java.lang.IllegalStateException: Unable to load cache item
com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2051)
com.google.common.cache.LocalCache.get(LocalCache.java:3962)
com.google.common.cache.LocalCache.getOrLoad(LocalCache.java:3985)
com.google.common.cache.LocalCache$LocalLoadingCache.get(LocalCache.java:4946)
com.google.common.cache.LocalCache$LocalLoadingCache.getUnchecked(LocalCache.java:4952)
com.google.inject.internal.FailableCache.get(FailableCache.java:54)
com.google.inject.internal.ConstructorInjectorStore.get(ConstructorInjectorStore.java:49)
com.google.inject.internal.ConstructorBindingImpl.initialize(ConstructorBindingImpl.java:155)
com.google.inject.internal.InjectorImpl.initializeBinding(InjectorImpl.java:592)
</code></pre>
<p>Both stack traces given go on for ages winding through various Play, Akka,
Google, and even core Java libraries, far downstack from the actual
application code.</p>
<h3>Play Framework Compatibility</h3>
<p>A quick search tells me that other people have this problem when running
Play Framework against incompatible Java versions and that Play Framework 2.8
(the current version at the time of this writing) is compatible with JDK 8 and
JDK 11. Sure enough, when I watched <code>sbt</code> startup, I saw that it used
JDK 17, so
I knew I needed to downgrade to JDK 11.</p>
<p>I tried installing the JDK 11 package using the same <em>/etc/configuration.nix</em>,
but of course it's a feature of Nix that that's not how that works--each
package comes with it's <em>own</em> dependencies, and sbt uses JDK 17 still.</p>
<p><strong>So the question is: How do I downgrade the JDK version that sbt uses from
JDK 17 to JDK 11?</strong></p>
<p>And this makes me a little itchy. Is this going to be hard? Am I going to have
to fork the sbt package? Because the reason why I switched to NixOS in the
first place is to avoid going to such lengths.</p>
<h3>Downgrading the JDK version used by sbt</h3>
<p><a href="https://discourse.nixos.org/t/remove-hardcoded-dependency-of-sbt-on-jdk8/9053/6">It turns out it's easy.</a>
The Java version used by the sbt package can be overridden declaratively
within that same line in the configuration file.</p>
<p><em>/etc/nixos/configuration.nix</em></p>
<pre class="language-nix"><code class="language-nix"> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><br /> <br /> environment<span class="token punctuation">.</span>systemPackages <span class="token operator">=</span> <span class="token keyword">with</span> pkgs<span class="token punctuation">;</span> <span class="token punctuation">[</span><br /> docker<br /> emacs<br /> firefox<br /> git<br /> gnome<span class="token punctuation">.</span>gnome<span class="token operator">-</span>tweaks<br /> <span class="token punctuation">(</span>sbt<span class="token punctuation">.</span>override <span class="token punctuation">{</span> jre <span class="token operator">=</span> pkgs<span class="token punctuation">.</span>jdk11<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> wget<br /> <span class="token punctuation">]</span><span class="token punctuation">;</span><br /><br /> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span></code></pre>
<p>Then just <code>nixos-rebuild switch</code> and you're good to go!</p>
<h3>In Conclusion... I think I like this!</h3>
<p>On the one hand, I've been an Ubuntu user for almost ten
years. I know how to be productive there. When things
break, I can generally fix them, and if I can't, I can
generally figure out what would be good search terms to
start with. On the other hand, I think I'm just getting a glimpse
of just how powerful <code>nix</code> really is. If I can
make a perfectly reproducible Scala environment with a
single line, that alone might be worth sticking around
for. More to come, I'm sure.</p>
Why Every Programming Language is Terrible2023-03-05T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/why-every-programming-language-is-terrible/<p>Tell me you've never hated your programming language. Tell me it doesn't have
any features you wouldn't be <em>caught dead</em> actually using. Tell me there's a
programming language so dear to you that you not only enjoy writing it in the
moment but enjoy reading what you've written in the somewhat distant past.
Tell me—and this is key—that you not only enjoy reading what <em>you've</em> written,
but what others have written.</p>
<p>Now for the extreme version: is there more than one language you can say that
about? More than two? Out of how many languages are you familiar with?</p>
<h3>The more things change</h3>
<p>How can this be? How do I know so many languages but really enjoy so few of
them?</p>
<p>I'm at least proficient in C#, Elm, Java, JavaScript, OCaml, Python, Reason,
ReScript, Scala, Scheme, TypeScript, and Visual BASIC. I've also dabbled in C,
C++, F#, Haskell, Kotlin, and Ruby. I used to really like learning new
languages, but it's honestly all a waste of time at some point.</p>
<p>Why is it a waste of time? Because of something all senior devs known to be
true: programming languages are basically the same. If you know, like, three
programming languages that are fairly different from each other, you have
learned all there is to know about programming, and everything else is so far
in the weeds it doesn't matter. C# is just Java with a silly naming
convention. Java is just overly verbose Kotlin. Kotlin is just statically-typed
Ruby. Ruby is just Python with braces. Yawn.</p>
<p>This is why Haskell has such a cult following among intermediate-level
programmers and why it's known as something that can "make you a better
programmer" even in other programming languages. It's true. If you
feel your knowledge of programming start to plateau, learning Haskell or
something Haskell-inspired is kind of a breath of fresh air that allows you
to see things in a slightly different way. But again we have to ask: why is
this experience of programming languages so rare?</p>
<p>And how can it be that most programming languages are so similar to one another
when they're all adding features all the time? Have you seen the sheer
<em>size</em> of the most recent C# specification?</p>
<h3>The more they stay the same</h3>
<p>What's actually new in your language(s) of choice in the past few years?
Do you remember where you were when Python added support for static type
checking? How did you take the news that ReasonML was rebranding as "ReScript"
and adopting a TypeScript-like syntax? Were you regularly DuckDuckGo-ing "C#
strict null checking when?" Did lambda expressions change how you wrote Java?</p>
<p>Now ask yourself: why did your languages make the changes they made? What
reasons did they give? Overwhelmingly, the announcement blog post
goes something like this:</p>
<blockquote>
<p>
We're adding a feature that <em>so many</em> of you have been
asking for!
Historically, we have been a language that does not use this concept,
but we are introducing this concept because many
programmers are coming from a
language that does have this concept, and we want to
make the transition as
smooth as possible.
</p>
<p>
This new feature will allow us to target use cases which we haven't
covered in the past. At long last, we can truly say we are a
<em>general purpose programming language</em>, something that
suits everyone's
use case and makes everyone happy!
</p>
<p>
Also, we've added a JavaScript compilation target! 🎉
</p>
</blockquote>
<p>Oh.</p>
<h3>Rust is just a C-like language in the category of memory safety. What's the problem?</h3>
<p>It's reasonable to ask whether the problem I'm describing is really a
<em>problem</em>. Maybe it's a neutral thing, or maybe it's actually a very good
thing that languages are (1) responsive to user suggestions and (2) borrowing
each other's best ideas. It is good in theory, but...</p>
<p>In practice, it means nothing works <em>well</em>. Every language
wants to be Swiss Army
knife, but have you ever tried to use a Swiss Army knife for something
non-trivial? They're brittle and difficult to grasp and never have the size
of flathead you need. They're absolutely the right tool if you can't carry a
toolbox, but if virtually every
tool maker announced their intention to only produce only <em>general purpose</em>
tools, I'd never get anything done.</p>
<p>So every dynamically typed language adds optional safety checks and every
statically typed language adds optional dynamic statements, so no one can rely
on their checks actually checking 100% of the time.
JavaScript adds object orientation as a
syntactic sugar over closures, but the <code>this</code> keyword (a pretty important
thing in object orientation!) works counterintutively to anyone familiar with
the languages they're copying. Everyone and their dog adds support for
targeting node and browser runtimes, but their standard libraries
throw exceptions left and right
because they rely on sys calls
not available in those ecosystems.</p>
<p>A second problem is that bloat matters in every piece of software, including
compilers and interpreters. More code means more to maintain, potentially
longer release cycles, and more potential for bugs and security
vulnerabilities.</p>
<p>Again, general purpose programming languages are not bad, but it's worth
considering why every programming language is racing to admit that they have
no particular purpose. Whatever happened to using <em>the right tool for the
job?</em> What's particularly sad is that a lot of these languages started
out solving a particular problem and did it well.</p>
<p>If nothing else, think of it this way: don't you wish you loved anything as
much as a Lisp programmer loves lisp or an Elm programmer loves Elm?</p>
<h3>How do we blow it up? There's always a way to do that.</h3>
<p>I think a number of incremental engineering best practices and cultural
changes can help curb the issue.</p>
<ol>
<li>
<p><strong>Think about your stack before you start typing code.</strong> So much of the
clamoring for features comes from organizations who chose bad tools to begin
with and hit a wall after they're locked in. Is the tool you use really
<em>general purpose</em> or does it have some underpublicized limitations? And would
you be better off using some more specialized tools?</p>
</li>
<li>
<p><strong>Don't take, "We just don't do that here" as an answer.</strong> Organizational
inertia contributes to the above problem. Point out these problems to
management when they say, "Oh, we can't use X because we're a Y shop."</p>
</li>
<li>
<p><strong>Don't be a jerk about the specialized languages you hate.</strong> I'm going
to say the quiet
part out loud: just like there should be more languages that very, very closely
match your preferences, there should also be a lot more languages that don't
suit hardly <em>any</em> of your preferences. Don't discourage people from using them,
and don't just say they're bad. Instead, try to make a good-faith effort into
examine why someone would want to use a certain tool, and explain why someone
with <em>your</em> use case would not want to use such a thing.</p>
</li>
<li>
<p><strong>Push back when people float bad ideas.</strong> It's easy to hear about some
new fad and think, "I'd never need that, but if it's what other people want,
it probably can't hurt." Oftentimes, having a bloated language does hurt, but
it hurts in subtle ways over time, so we have collective action problems
wherein lots of people are clamoring for <em>more</em> new features and not enough
people are clamoring for <em>fewer.</em> Ask yourself: would you want to see a
bunch of merge requests into your code that use these new "features?" If not,
advocate that your language remain specialized.</p>
</li>
<li>
<p><strong>Applaud languages that decline to add new features.</strong> Ultimately, this
is about changing the incentives for language developers (a lot of whom aren't
exactly raking in the cash from their passion project). Even just a "Thank you"
can go a long way. A few dollars' tip with it can go miles.</p>
</li>
</ol>
<p>If enough people take a more thoughtful approach to long-term language
maintenance, I'm confident we can build more appropriate tools and leaner,
more innovative language designs.</p>
Hello, Idris World! and Why I'm Excited for a Total Programming Language2023-04-02T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/hello-idris-world/<p>This article represents my first attempt to get up and running with the
Idris programming language. It will document not only how to make an
executable Idris package but also the basics of what Idris is and why it's not
like any other programming language you're likely to have used before. From
a practical perspective, it will cover some basic syntax as well as how to
compile and run an Idris 2 program, including .ipkg usage.</p>
<p>Unlike most other Idris learning resources, I will <em>not</em> assume you already
know Haskell, not least because, frankly, I don't know Haskell very well. By
the way, I also don't know Idris. I'm a beginner, and, <em>caveat emptor</em>, I
definitely allow for the possibility that I could get things wrong in this
article. You should allow for that possibility, too, and watch out for errors
on my part.</p>
<h3>What's the big deal about Idris programming language?</h3>
<p>Are you the sort of person who <em>really</em> likes static types? Do you love being
able to offload the hard work of making a program actually correct onto the
compiler? If so, I recommend continuing to seek more robust type systems, and
the logical conclusion of that search is Idris. Idris has two (related)
innovations.</p>
<p>The first is called <strong>dependent types</strong>, which means that types can be
dependent on other values that we don't normally think of as being types.
For example,
let's say you have a list with a length represented by an integer. (This is
<a href="https://idris2.readthedocs.io/en/latest/tutorial/typesfuns.html#list-and-vect">called a <code>Vect</code></a>
in Idris.) If the type
system is aware of the length of the list, it can prevent you from accessing
an index of that list outside that range at <em>compile time</em> instead of runtime.
Again, if you're the sort of person who likes a very vocal compiler that
doesn't let you do bad things, this sort of feature might be appealing to you.
Of course, it doesn't begin to scratch the surface of what dependent types
can do for you. But this will:</p>
<p>The other is called a <strong>totality checker</strong>, which means that Idris can check
that a function or program will exit with a result (without error) in a finite
amount of time for every possible input. All 'exceptions' are handled. All
corner cases are accounted for. This is the ultimate
<a href="https://www.youtube.com/watch?v=GqmsQeSzMdw">liberating constraint</a> that
frees you from worrying about whether or not your program is right and allows
you to focus on your business logic.</p>
<p>How this all works is beyond the scope of a "Hello, world" article, but
if you think you're the sort of person who would enjoy <strong>total programming</strong>, I
would encourage you to keep reading.</p>
<p>And so, without further ado:</p>
<h3>Structuring your directory in Idris</h3>
<ol>
<li>After installing Idris 2 on your platform of choice, make a new directory
for your Idris project.</li>
<li>Make a source directory inside your project directory called <em>src</em>.</li>
<li>Open a new file in this directory. Let's call it <em>Main.idr</em>.</li>
<li>Declare the greeting module by typing <code>module Main</code> at the top. (By
convention, the module name should match the file name.)</li>
</ol>
<h3>Learning some basic syntax in Idris</h3>
<p>Idris uses Haskell-like syntax. That means</p>
<ul>
<li>The type signature sits on the line above the parameter declarations.</li>
<li>Functions don't enclose parameters and arguments in parentheses or separate
them with commas. Instead, parameters and arguments are separated by
whitespace.</li>
<li>Functions definitions come after an equals sign, and whitespace is
significant thereafter.</li>
</ul>
<p>With that in mind, let's write some Idris.</p>
<p>The first thing we need is <code>putStrLn</code> which takes a string and returns an
<code>IO</code>. IO is a monad that allows for interacting with the world outside the
Idris programming language, like writing to the console. (If you need a primer
on monads, I wrote this handy article on
<a href="https://webbureaucrat.gitlab.io/articles/safer-data-parsing-with-try-monads/">monads for object-oriented
programmers</a></p>
<p><em>Main.idr</em></p>
<pre class="language-idris"><code class="language-idris"><span class="token keyword">module</span> <span class="token constant">Main</span><br /><br /><span class="token hvariable">main</span> <span class="token operator">:</span> <span class="token constant">IO</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">--type signature which takes no arguments and returns IO ().</span><br /><span class="token hvariable">main</span> <span class="token operator">=</span> <span class="token hvariable">putStrLn</span> <span class="token string">"Hello, world."</span> <span class="token comment">--call `putStrLn` with the string argument.</span></code></pre>
<p>If you're not familiar with Haskell or a similar language like Elm, this is
very difficult to read, but don't worry. If you're having trouble reading the
type signature, think of it like this:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> main <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token constant">IO</span><span class="token operator"><</span><span class="token keyword">void</span><span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span> <br /> <span class="token keyword">return</span> <span class="token function">putStrLn</span><span class="token punctuation">(</span><span class="token string">"Hello, world"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>(Don't get tripped up on the empty parentheses, though: they mean different
things in the two examples. In Idris, they mean that the IO takes no
parameters, not the function.)</p>
<h3>Compiling and running Idris code</h3>
<p>If you have Idris 2 installed, you can compile this program and run it.</p>
<h4>Compiling Idris source files directly</h4>
<p>From inside your <em>src</em> directory, run <code>idris2 Greeting.idr -o greeting</code>.
If successful, this will create a new directory called, "build". You can run
your program by running
<code>./build/exec/greeting</code>.</p>
<h4>Compiling Idris using an Idris <em>ipkg</em> file</h4>
<p>Idris also has an optional package configuration file which will be much more
useful in most situations. Let's back out of our <em>src</em> directory and go
to our project directory to see this work.</p>
<pre><code>package greeting
authors = "eleanorofs"
bugtracker = "https://gitlab.com/eleanorofs/idris-kata/-/issues"
executable = "greeting"
homepage = "https://gitlab.com/eleanorofs/idris-kata"
main = Main
maintainers = "eleanorofs"
opts = "--warnpartial"
readme = "./README.md"
sourcedir = "./src"
sourceloc = "https://gitlab.com/eleanorofs/idris-kata.git"
version = 0.0.1
</code></pre>
<p>This is a good starting template for an executable package. For a library
package, we would not include the <code>executable</code> or the <code>main</code> properties. As you
can see, the <code>opts</code> property allows for passing additional compiler options.
As an example, I'm passing one which encourages totality. Most of the rest of
these properties are just metadata strings.</p>
<p>We can compile according to this configuration using</p>
<pre class="language-bash"><code class="language-bash">idris2 <span class="token parameter variable">--build</span> greeting.ipkg</code></pre>
<p>Our executable will still be <code>./build/exec/greeting</code>.</p>
<h3>Parameters in Idris</h3>
<p>Our program isn't very interesting, though. Let's refactor this program to
make it a little more flexible, and, in the process, let's learn how to declare
parameters.</p>
<p><em>Greeting.idr</em></p>
<pre class="language-idris"><code class="language-idris"><span class="token keyword">module</span> <span class="token constant">Main</span><br /><br /><span class="token hvariable">greet</span> <span class="token operator">:</span> <span class="token constant">String</span> <span class="token operator">-></span> <span class="token constant">String</span> <span class="token operator">-></span> <span class="token constant">String</span><br /><span class="token hvariable">greet</span> <span class="token hvariable">greeting</span> <span class="token hvariable">greetable</span> <span class="token operator">=</span> <span class="token hvariable">greeting</span> <span class="token operator">++</span> <span class="token string">", "</span> <span class="token operator">++</span> <span class="token hvariable">greetable</span> <span class="token operator">++</span> <span class="token string">". "</span><br /><br /><span class="token hvariable">main</span> <span class="token operator">:</span> <span class="token constant">IO</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">--type signature which takes no arguments and returns IO ().</span><br /><span class="token hvariable">main</span> <span class="token operator">=</span> <br /> <span class="token hvariable">putStrLn</span> <span class="token punctuation">(</span><span class="token hvariable">greet</span> <span class="token string">"Hello"</span> <span class="token string">"world"</span><span class="token punctuation">)</span> <span class="token comment">--call `putStrLn` with the string arguments.</span></code></pre>
<p>Now we have a more robust function that can print things like, "Howdy, Idris."
or "Here's looking at you, kid."
More interestingly, we have our first example of declaring parameters.
<code>String -> String -> String</code> can be read as, "takes a string parameter and
another string parameter and returns another string."</p>
<h3>Dollar sign syntax for arguments in Idris</h3>
<p>This example is trivial, but sometimes
(nested (parentheses (can (get (messy))))) in languages like this, which is
why we have syntactic sugars. We can use the dollar sign (<code>$</code>) to get rid of
parentheses and (maybe) make our code look a little nicer.</p>
<p><em>Main.idr</em></p>
<pre class="language-idris"><code class="language-idris"><span class="token keyword">module</span> <span class="token constant">Main</span><br /><br /><span class="token hvariable">greet</span> <span class="token operator">:</span> <span class="token constant">String</span> <span class="token operator">-></span> <span class="token constant">String</span> <span class="token operator">-></span> <span class="token constant">String</span><br /><span class="token hvariable">greet</span> <span class="token hvariable">greeting</span> <span class="token hvariable">greetable</span> <span class="token operator">=</span> <span class="token hvariable">greeting</span> <span class="token operator">++</span> <span class="token string">", "</span> <span class="token operator">++</span> <span class="token hvariable">greetable</span> <span class="token operator">++</span> <span class="token string">". "</span><br /><br /><span class="token hvariable">main</span> <span class="token operator">:</span> <span class="token constant">IO</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">--type signature which takes no arguments and returns IO ().</span><br /><span class="token hvariable">main</span> <span class="token operator">=</span> <br /> <span class="token hvariable">putStrLn</span> <span class="token operator">$</span> <span class="token hvariable">greet</span> <span class="token string">"Hello"</span> <span class="token string">"world"</span> <span class="token comment">--call `putStrLn` with the string arguments.</span></code></pre>
<p>Much better! (Well, okay, maybe not for this example, but things like this
can be useful, and the dollar sign is used liberally in the Idris 2
documentation, so I thought it was important to bring up anyway.)</p>
<h3>and just one more little thing... totality checking in Idris!</h3>
<p>Let's not forget what makes Idris so special. Since we've written a function
we know will succeed, let's celebrate and declare it so by placing the <code>total</code>
modifier above our main function.</p>
<p><em>Main.idr</em></p>
<pre class="language-idris"><code class="language-idris"><span class="token keyword">module</span> <span class="token constant">Main</span><br /><br /><span class="token hvariable">greet</span> <span class="token operator">:</span> <span class="token constant">String</span> <span class="token operator">-></span> <span class="token constant">String</span> <span class="token operator">-></span> <span class="token constant">String</span><br /><span class="token hvariable">greet</span> <span class="token hvariable">greeting</span> <span class="token hvariable">greetable</span> <span class="token operator">=</span> <span class="token hvariable">greeting</span> <span class="token operator">++</span> <span class="token string">", "</span> <span class="token operator">++</span> <span class="token hvariable">greetable</span> <span class="token operator">++</span> <span class="token string">". "</span><br /><br /><span class="token keyword">total</span><br /><span class="token hvariable">main</span> <span class="token operator">:</span> <span class="token constant">IO</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">--type signature which takes no arguments and returns IO ().</span><br /><span class="token hvariable">main</span> <span class="token operator">=</span><br /> <span class="token hvariable">putStrLn</span> <span class="token operator">$</span> <span class="token hvariable">greet</span> <span class="token string">"Hello"</span> <span class="token string">"world"</span> <span class="token comment">--call `putStrLn` with the string arguments.</span></code></pre>
<p>This tells the compiler we think we have a total program--a program for which
all paths of its behavior are defined. If the program still compiles, we know
we're right about that.</p>
<h3>Here's hoping Idris takes off!</h3>
<p>This article has addressed a little of:</p>
<ul>
<li>basic syntax</li>
<li>IO monads</li>
<li>package configuration</li>
<li>string concatenation</li>
<li>totality checking</li>
</ul>
<p>And you know what? I think that's plenty. Let's just take a beat and not try
to cover all of theorem proving or dependent types or vim keybindings in one
go.</p>
<p>Anyway, I hope this has been helpful. As someone who loves the idea of
Idris but is
worried about the lack of learning resources, I feel like we're on this
journey together. My faint hope is that Idris eventually becomes mainstream
enough that we can use it at work and make all of our lives much easier, and
while we're nowhere near that now, I know every tutorial someone cranks out
gets us a little closer to that goal.</p>
<p>Follow <a href="https://webbureaucrat.gitlab.io/feed/feed.xml">my RSS Feed</a> or
<a href="https://dev.to/webbureaucrat">my Dev.to</a>
if you're still reading this far! This is
becoming an Idris tutorial website for the forseeable future; my drafts
folder is already stacked with beginner-friendly Idris tutorials.</p>
Idris FizzBuzz Part I: Monads, Comments, and `assert_smaller`2023-05-01T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/idris-fizzbuzz-part-i-monads-comments-assert-smaller/<p>My journey of learning the Idris programming language continues! This article
will start a tutorial series detailing how to write FizzBuzz in Idris. As
before, my goal is to write without the assumption that all Idris learners
already know Haskell. We will start by implementing <code>modulo</code> in Idris, and
toward that goal we will go painfully slow because frankly this stuff is hard.</p>
<p>For an overview of why I'm so excited about Idris as well as a little
information on totality checking and some basic syntax, I recommend
my
<a href="https://webbureaucrat.gitlab.io/articles/hello-idris-world/">previous article</a>.</p>
<h3>An overview of the problem: FizzBuzz</h3>
<p>If you're not familiar with FizzBuzz, it's the name of a common entry-level
programming interview problem. The requirements go like this:</p>
<p>From one to 100 (or 1000 or so on), print each number, with a couple
modifications:</p>
<ul>
<li>For every number evenly divisible by three, print "Fizz" instead of the
number.</li>
<li>For every number evenly divisible by five, print "Buzz" instead of the
number.</li>
<li>For every number evenly divisible by both three and five, print "FizzBuzz"
instead of the number.</li>
</ul>
<p>There are a couple reasons why this problem is so popular. The requirements are
very simple and widely known, so they aren't likely to trip up a good candidate
who made a small mistake. Even so, for such a simple problem, you're
testing for:</p>
<ul>
<li>numeric range generation</li>
<li>basic arithmetic</li>
<li>conditional logic</li>
<li>iteration</li>
</ul>
<p>That's <em>just enough</em> to show you know the syntax of a language without having
to know the standard library off the top of your head. These are all good
reasons why I think it will be a good example from which to learn Idris. By
the end of this multi-part tutorial, we should know enough of the
Idris programming language to be productive or
know enough that the learning starts to come a little easier.</p>
<h3>Understanding modulus</h3>
<p>In order to know if a number is easily divisible by three or five, we need to
be able to take the modulus of that number. Usually, this is pure syntax, the
<code>%</code> operator, but Idris is a little more complicated.</p>
<p>I say that because there are a lot of numeric types in Idris, and for this
program, I think the most sensible one is <code>Nat</code>, the set of natural numbers, or
the set of zero and the positive integers. I recommend reading the
<a href="https://idris2.readthedocs.io/en/latest/reference/builtins.html">documentation for <code>Nat</code></a>
before proceeding, though you don't have to fully understand it all.</p>
<p>So we will be implementing our own modulus that is defined for the natural
numbers, but there's a catch. Modulus isn't really defined for zero, so we need
a <strong>monad</strong>. You can think of a monad like a box. The box is an abstraction
around whether or not something inside it exists. Whether the box is empty or
full, it's still box-shaped, which makes it easy to work with when we're
expecting a value but might not have one.</p>
<p>In this case, we need a monad that either does or doesn't contain the result of
the <code>modulo</code> function. Idris has a monad for such an occasion called a
<a href="https://idris2.readthedocs.io/en/latest/tutorial/typesfuns.html#maybe"><code>Maybe</code> monad</a>.
In most cases, we will get a <code>Just</code> with a remainder inside. if the divisor
is zero, however, we will get a <code>Nothing</code>. Either way, we'll be getting a
monad back, so we know we won't have to deal with errors, which is what makes
monads so appealing as a solution.</p>
<h3>Thinking about modulo</h3>
<p>There are a number of ways we could set up <code>modulo</code>. We could, for example,
compare the dividend with the product of the divisor and the integer quotient,
but (for reasons I won't get into) that's just not that appealing right now.
what I'm going to do instead is something like this:</p>
<p><em>pseudocode</em></p>
<pre class="language-typescript"><code class="language-typescript"><span class="token keyword">function</span> <span class="token function">modulo</span><span class="token punctuation">(</span>dividend<span class="token operator">:</span> Nat<span class="token punctuation">,</span> divisor<span class="token operator">:</span> Nat<span class="token punctuation">)</span><span class="token operator">:</span> Maybe<span class="token operator"><</span>Nat<span class="token operator">></span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>divisor <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Nothing</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>dividend <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Just</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><br /> <span class="token keyword">while</span> <span class="token punctuation">(</span>dividend <span class="token operator">>=</span> divisor<span class="token punctuation">)</span><br /> <span class="token punctuation">{</span><br /> dividend <span class="token operator">=</span> dividend <span class="token operator">-</span> divisor<span class="token punctuation">;</span> <br /> <span class="token punctuation">}</span><br /> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Just</span><span class="token punctuation">(</span>dividend<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span></code></pre>
<p>The idea here is to subtract off divisor as many times as I can, and whatever
is left over is the remainder.</p>
<p>I'm including this code because the unusual, Haskell-like
syntax can be intimidating for people who aren't used to it, and learning a
whole new programming paradigm is plenty hard enough without an added
language barrier.</p>
<h3>Setting up the problem</h3>
<p>So we need a function <code>modulo</code> that takes a dividend and a divisor and returns
the remainder. Recall from the "Hello world" example that Idris puts type
signatures on top and separates function parameters and the return type with
arrows. Then, on the next line, the definition begins.</p>
<p><em>src/Division.idr</em></p>
<pre class="language-idris"><code class="language-idris"><span class="token keyword">module</span> <span class="token constant">Division</span><br /><br /><span class="token hvariable">modulo</span> <span class="token operator">:</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Maybe</span> <span class="token constant">Nat</span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">dividend</span> <span class="token hvariable">divisor</span> <span class="token operator">=</span> <span class="token constant">Nothing</span></code></pre>
<h3>Type signatures in Idris</h3>
<p>Idris supports case matching in function definitions, meaning that we can
supply different definitions for different possible inputs. With <code>Nat</code>
numbers, that enables us to handle the zero case effectively by case-matching
it against the type <code>Z</code>.</p>
<p>When the divisor is zero, it means it's safe to ignore the dividend because we
know the result will be <code>Nothing</code> (because we can't divide by zero) so we can
use the <code>_</code> to mean "ignore this parameter in this case."</p>
<p>With that, let's split up our function:</p>
<p><em>src/Division.idr</em></p>
<pre class="language-idris"><code class="language-idris"><span class="token keyword">module</span> <span class="token constant">Division</span><br /><br /><span class="token hvariable">modulo</span> <span class="token operator">:</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Maybe</span> <span class="token constant">Nat</span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">_</span> <span class="token constant">Z</span> <span class="token operator">=</span> <span class="token constant">Nothing</span><br /><span class="token hvariable">modulo</span> <span class="token constant">Z</span> <span class="token hvariable">dividend</span> <span class="token operator">=</span> <span class="token constant">Nothing</span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">dividend</span> <span class="token hvariable">divisor</span> <span class="token operator">=</span> <span class="token constant">Nothing</span></code></pre>
<h3>Types of comments in Idris</h3>
<p>Again, no shame if you're fighting with the sytax. In fact, let's take the
opportunity to put in some comments. Three bars (<code>|||</code>) represent a
documentation comment which will show up in generated documentation or even
the IDE, while comments enclosed in <code>{-</code> / <code>-}</code> or beginning with
<code>--</code> are for the reader of the function itself.</p>
<p><em>src/Division.idr</em></p>
<pre class="language-idris"><code class="language-idris"><span class="token keyword">module</span> <span class="token constant">Division</span><br /><br /><span class="token comment">||| Returns a `Just` of the remainder of two numbers, or `Nothing` if the </span><br /><span class="token comment">||| divisor is zero.</span><br /><span class="token hvariable">modulo</span> <span class="token operator">:</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Maybe</span> <span class="token constant">Nat</span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">_</span> <span class="token constant">Z</span> <span class="token operator">=</span> <span class="token constant">Nothing</span> <span class="token comment">-- can't divide by zero, so nothing</span><br /><span class="token hvariable">modulo</span> <span class="token constant">Z</span> <span class="token hvariable">_</span> <span class="token operator">=</span> <span class="token constant">Nothing</span> <span class="token comment">--this case is a placeholder</span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">dividend</span> <span class="token hvariable">divisor</span> <span class="token operator">=</span> <span class="token constant">Nothing</span> <span class="token comment">--this case is a placeholder</span><br /><br /><span class="token comment">{- What we have here is a type signature followed by three different cases. In<br /> the first case, we throw away the dividend argument (_) and match on the <br /> divisor when the divisor is zero and return `Nothing` because you can't <br /> divide by zero. The second case matches on the divisor when the divisor is<br /> zero. The last will catch all other cases. <br />-}</span></code></pre>
<h3>Case matching in Idris</h3>
<p>Cases match from top to bottom, so we can assume that in the second and third
cases, the divisor is not zero because it will always match with the first
case. Let's fill in the next case, which is also trivial because if the
dividend is zero and the divisor is not, we can assume the remainder is zero.</p>
<p>Recall, though, that we can't just return zero. We are using <code>Maybe</code>, so
we will wrap our result in a <code>Just</code>.</p>
<p><em>src/Division.idr</em></p>
<pre class="language-idris"><code class="language-idris"><span class="token comment">||| Returns a `Just` of the remainder of two numbers, or `Nothing` if the </span><br /><span class="token comment">||| divisor is zero.</span><br /><span class="token hvariable">modulo</span> <span class="token operator">:</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Maybe</span> <span class="token constant">Nat</span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">_</span> <span class="token constant">Z</span> <span class="token operator">=</span> <span class="token constant">Nothing</span> <span class="token comment">-- can't divide by zero, so nothing</span><br /><span class="token hvariable">modulo</span> <span class="token constant">Z</span> <span class="token hvariable">_</span> <span class="token operator">=</span> <span class="token constant">Just</span> <span class="token number">0</span> <span class="token comment">-- if the dividend is zero return zero wrapped in Just. </span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">dividend</span> <span class="token hvariable">divisor</span> <span class="token operator">=</span> <span class="token constant">Nothing</span> <span class="token comment">-- this is a placeholder</span></code></pre>
<p>Now we're almost there with just one more case to cover. Just like our while
loop in typescript-like pseudocode above, we will subtract until dividend is
less than divisor. However, unlike the while loop above, we're writing
functional code, so let's use recursion instead.</p>
<p><em>src/Division.idr</em></p>
<pre class="language-idris"><code class="language-idris"><span class="token comment">||| Returns a `Just` of the remainder of two numbers, or `Nothing` if the </span><br /><span class="token comment">||| divisor is zero.</span><br /><span class="token hvariable">modulo</span> <span class="token operator">:</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Maybe</span> <span class="token constant">Nat</span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">_</span> <span class="token constant">Z</span> <span class="token operator">=</span> <span class="token constant">Nothing</span> <span class="token comment">-- can't divide by zero, so nothing</span><br /><span class="token hvariable">modulo</span> <span class="token constant">Z</span> <span class="token hvariable">_</span> <span class="token operator">=</span> <span class="token constant">Just</span> <span class="token number">0</span> <span class="token comment">-- if the dividend is zero return zero wrapped in Just. </span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">dividend</span> <span class="token hvariable">divisor</span> <span class="token operator">=</span><br /> <span class="token keyword">if</span> <span class="token hvariable">dividend</span> <span class="token operator">>=</span> <span class="token hvariable">divisor</span> <br /> <span class="token keyword">then</span> <span class="token hvariable">modulo</span> <span class="token punctuation">(</span><span class="token hvariable">minus</span> <span class="token hvariable">dividend</span> <span class="token hvariable">divisor</span><span class="token punctuation">)</span> <span class="token hvariable">divisor</span><br /> <span class="token keyword">else</span> <span class="token constant">Just</span> <span class="token hvariable">dividend</span> </code></pre>
<p>Every time the dividend is greater than the divisor, subtract off the divisor
again and try again, just like above. Make sense?</p>
<h3>Using <code>assert_smaller</code> to help check totality in Idris</h3>
<p>Now there's just one more matter to attend to: totality checking! (I always
get so excited about this part!)</p>
<p>Unfortunately, if we check this for totality, we get an error.</p>
<pre class="language-shell"><code class="language-shell">Error: modulo is not total, possibly not terminating due to recursive<br />path Division.modulo -<span class="token operator">></span> Division.modulo</code></pre>
<p>The key word here is "possibly." Actually, this is a total function, and we
can prove it.</p>
<ul>
<li>The case where the divisor is zero is covered elsewhere, and therefore, the
divisor is not zero.</li>
<li>If we subtract a positive <code>Nat</code> from a larger positive <code>Nat</code>, we get a
smaller <code>Nat</code>.</li>
</ul>
<p>However, Idris is having some trouble seeing that in our code. Let's give it a
little help with <code>assert_smaller</code>.</p>
<p><code>assert_smaller</code> takes two arguments and always returns the second, but it also
tells the compiler that the second argument is always going to be smaller than
the first. Since we're doing that subtraction, we can make that assertion
safely. Then we can add <code>total</code> to the front of our function to tell
the compiler to check the totality of this function. Let's do that now.</p>
<p><em>src/Division.idr</em></p>
<pre class="language-idris"><code class="language-idris"><span class="token comment">||| Returns a `Just` of the remainder of two numbers, or `Nothing` if the </span><br /><span class="token comment">||| divisor is zero.</span><br /><span class="token keyword">total</span><br /><span class="token hvariable">modulo</span> <span class="token operator">:</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Maybe</span> <span class="token constant">Nat</span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">_</span> <span class="token constant">Z</span> <span class="token operator">=</span> <span class="token constant">Nothing</span><br /><span class="token hvariable">modulo</span> <span class="token constant">Z</span> <span class="token hvariable">dividend</span> <span class="token operator">=</span> <span class="token constant">Just</span> <span class="token number">0</span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">dividend</span> <span class="token hvariable">divisor</span> <span class="token operator">=</span> <br /> <span class="token keyword">if</span> <span class="token hvariable">dividend</span> <span class="token operator">>=</span> <span class="token hvariable">divisor</span><br /> <span class="token keyword">then</span> <span class="token hvariable">modulo</span> <span class="token punctuation">(</span><span class="token hvariable">assert_smaller</span> <span class="token hvariable">dividend</span> <span class="token punctuation">(</span><span class="token hvariable">minus</span> <span class="token hvariable">dividend</span> <span class="token hvariable">divisor</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token hvariable">divisor</span><br /> <span class="token keyword">else</span> <span class="token constant">Just</span> <span class="token hvariable">dividend</span></code></pre>
<p>This does exactly the same thing as the previous, but now we have totality. 🎉</p>
<h3>Wrapping up <code>modulo</code> for natural numbers</h3>
<p>In conclusion, I hope this has been helpful. I set out to try to explain
Idris for non-Haskellers and guess what? It turns out it's really hard! And I
know these tutorials alone won't be enough to teach someone everything they
need to know about Idris, but it's my hope that having just a few more
resources out there will make things just a little easier and pave the way
for a few more resources until Idris is broadly accessible to learn.</p>
<p>Stay tuned! In the next tutorial, I plan to cover case matching monads and
more.</p>
Idris FizzBuzz Part II: Maybes, Infix Notation, and Idris Holes2023-05-03T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/idris-fizzbuzz-part-ii-maybes-infix-notation-idris-holes/<p>Let's conntinue on our journey of writing FizzBuzz in the Idris programming
language. If you haven't already, I encourage you to read
<a href="https://webbureaucrat.gitlab.io/articles/idris-fizzbuzz-part-i-monads-comments-assert-smaller/">Part I</a>
for an introduction to the problem as well as some basic syntax, and,
importantly, information on totality checking.</p>
<h3>Setting up the problem: calling <code>modulo</code></h3>
<p>In the previous post, we wrote a <code>modulo</code> function that takes two <code>Nat</code>
numbers and returns a <code>Maybe</code> monad of the remainder. We defined it as follows:</p>
<p><em>src/Division.idr</em></p>
<pre class="language-idris"><code class="language-idris"><span class="token keyword">module</span> <span class="token constant">Division</span><br /><br /><span class="token comment">||| Returns a `Just of the remainder of two numbers, or `Nothing` if the </span><br /><span class="token comment">||| divisor is zero.</span><br /><span class="token keyword">total</span><br /><span class="token hvariable">modulo</span> <span class="token operator">:</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Maybe</span> <span class="token constant">Nat</span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">_</span> <span class="token constant">Z</span> <span class="token operator">=</span> <span class="token constant">Nothing</span> <span class="token comment">--can't divide by zero, so Nothing</span><br /><span class="token hvariable">modulo</span> <span class="token constant">Z</span> <span class="token hvariable">_</span> <span class="token operator">=</span> <span class="token constant">Just</span> <span class="token number">0</span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">dividend</span> <span class="token hvariable">divisor</span> <span class="token operator">=</span> <br /> <span class="token keyword">if</span> <span class="token hvariable">dividend</span> <span class="token operator">>=</span> <span class="token hvariable">divisor</span><br /> <span class="token keyword">then</span> <span class="token hvariable">modulo</span> <span class="token punctuation">(</span><span class="token hvariable">assert_smaller</span> <span class="token hvariable">dividend</span> <span class="token punctuation">(</span><span class="token hvariable">minus</span> <span class="token hvariable">dividend</span> <span class="token hvariable">divisor</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token hvariable">divisor</span><br /> <span class="token keyword">else</span> <span class="token constant">Just</span> <span class="token hvariable">dividend</span><br /></code></pre>
<p>Recall the reason why we needed this: because for FizzBuzz, we need to define
whether or not a number is divisible by three, five, or both, so in this
article, we will write a <code>divides</code> function that tells us just that by calling
our <code>modulo</code> function.</p>
<p>Consistent with Idris's type-driven development, let's start by thinking about
types. Whenever we call on a function that uses a <code>Maybe</code> monad, we have to
decide: will we also return a monad, or will we handle the conditional
internally to our new function?</p>
<p>For the purposes of FizzBuzz, I'm going to make a simplifying assumption about
<code>divides</code>, which is that <code>divides</code> <em>is</em> defined where the divisor is zero, and
the answer is just <code>False</code>. Mathematicians, feel free to get in my mentions,
but I'm going to say that zero doesn't evenly <em>divide</em> anything because
division isn't defined there at all.</p>
<h3>Type Signatures and Holes in Idris</h3>
<p>Since I'm saying that <code>divides</code> is defined (as <code>False</code>) for zero (<code>Z</code>), this
simplifies our type signature. The type signature for divides will be a
<code>Nat</code> divisor and a <code>Nat</code> dividend, returning a <code>Bool</code>. This is written as
<code>Nat -> Nat -> Bool</code>.</p>
<p>Let's start to write our function with an Idris <code>?hole</code>. Holes are an Idris
feature I haven't mentioned yet, but they are useful in development. They're
a feature of the syntax that serve as placeholders for logic that hasn't been
written yet. This means that we can write:</p>
<pre class="language-idris"><code class="language-idris"><span class="token keyword">module</span> <span class="token constant">Division</span><br /><br /><span class="token comment">||| Returns a `Just of the remainder of two numbers, or `Nothing` if the </span><br /><span class="token comment">||| divisor is zero.</span><br /><span class="token keyword">total</span><br /><span class="token hvariable">modulo</span> <span class="token operator">:</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Maybe</span> <span class="token constant">Nat</span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">_</span> <span class="token constant">Z</span> <span class="token operator">=</span> <span class="token constant">Nothing</span> <span class="token comment">--can't divide by zero, so Nothing</span><br /><span class="token hvariable">modulo</span> <span class="token constant">Z</span> <span class="token hvariable">_</span> <span class="token operator">=</span> <span class="token constant">Just</span> <span class="token number">0</span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">dividend</span> <span class="token hvariable">divisor</span> <span class="token operator">=</span> <br /> <span class="token keyword">if</span> <span class="token hvariable">dividend</span> <span class="token operator">>=</span> <span class="token hvariable">divisor</span><br /> <span class="token keyword">then</span> <span class="token hvariable">modulo</span> <span class="token punctuation">(</span><span class="token hvariable">assert_smaller</span> <span class="token hvariable">dividend</span> <span class="token punctuation">(</span><span class="token hvariable">minus</span> <span class="token hvariable">dividend</span> <span class="token hvariable">divisor</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token hvariable">divisor</span><br /> <span class="token keyword">else</span> <span class="token constant">Just</span> <span class="token hvariable">dividend</span><br /><br /><span class="token comment">||| Returns `True` if the first number divides the second evenly, otherwise </span><br /><span class="token comment">||| returns `False`. Also returns `False` if the divisor is zero. </span><br /><span class="token hvariable">divides</span> <span class="token operator">:</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Bool</span><br /><span class="token hvariable">divides</span> <span class="token hvariable">divisor</span> <span class="token hvariable">dividend</span> <span class="token operator">=</span> <span class="token operator">?</span><span class="token hvariable">thisIsAHole</span></code></pre>
<p>...and it type checks. (Though, clearly, this isn't a total function.)</p>
<h3>Handling <code>Maybe</code>s with <code>case</code> / <code>of</code> syntax in Idris</h3>
<p>We know we'll be calling our <code>modulo</code> function and getting back our <code>Maybe</code>
monad. Then we need to determine whether or not our <code>Maybe</code> actually contains
a value (<code>Just</code>) or whether it returned <code>Nothing</code></p>
<p>We can handle union types like <code>Maybe</code> using a special Idris syntax
using <code>case</code> and <code>of</code> keywords. Let's fill in a little with that syntax now.</p>
<p><em>src/Division.idr</em></p>
<pre class="language-idris"><code class="language-idris"><span class="token keyword">module</span> <span class="token constant">Division</span><br /><br /><span class="token comment">||| Returns a `Just of the remainder of two numbers, or `Nothing` if the </span><br /><span class="token comment">||| divisor is zero.</span><br /><span class="token keyword">total</span><br /><span class="token hvariable">modulo</span> <span class="token operator">:</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Maybe</span> <span class="token constant">Nat</span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">_</span> <span class="token constant">Z</span> <span class="token operator">=</span> <span class="token constant">Nothing</span> <span class="token comment">--can't divide by zero, so Nothing</span><br /><span class="token hvariable">modulo</span> <span class="token constant">Z</span> <span class="token hvariable">_</span> <span class="token operator">=</span> <span class="token constant">Just</span> <span class="token number">0</span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">dividend</span> <span class="token hvariable">divisor</span> <span class="token operator">=</span> <br /> <span class="token keyword">if</span> <span class="token hvariable">dividend</span> <span class="token operator">>=</span> <span class="token hvariable">divisor</span><br /> <span class="token keyword">then</span> <span class="token hvariable">modulo</span> <span class="token punctuation">(</span><span class="token hvariable">assert_smaller</span> <span class="token hvariable">dividend</span> <span class="token punctuation">(</span><span class="token hvariable">minus</span> <span class="token hvariable">dividend</span> <span class="token hvariable">divisor</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token hvariable">divisor</span><br /> <span class="token keyword">else</span> <span class="token constant">Just</span> <span class="token hvariable">dividend</span><br /><br /><span class="token comment">||| Returns `True` if the first number divides the second evenly, otherwise </span><br /><span class="token comment">||| returns `False`. Also returns `False` if the divisor is zero. </span><br /><span class="token hvariable">divides</span> <span class="token operator">:</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Bool</span><br /><span class="token hvariable">divides</span> <span class="token hvariable">divisor</span> <span class="token hvariable">dividend</span> <span class="token operator">=</span> <br /> <span class="token keyword">case</span> <span class="token hvariable">modulo</span> <span class="token hvariable">dividend</span> <span class="token hvariable">divisor</span> <span class="token keyword">of</span> <br /> <span class="token constant">Nothing</span> <span class="token operator">=></span> <span class="token operator">?</span><span class="token hvariable">nothingCase</span><br /> <span class="token constant">Just</span> <span class="token hvariable">remainder</span> <span class="token operator">=></span> <span class="token operator">?</span><span class="token hvariable">somethingCase</span></code></pre>
<p>Did you catch all that? We're calling <code>modulo</code> on <code>dividend</code> and <code>divisor</code>
(sort of like <code>modulo(dividend, divisor)</code> in some other languages) and then
wrapping that function call in <code>case</code> ... <code>of</code>. Then we see the possible cases
listed below, sort of like a <code>switch</code> statement in languages that support
<code>switch</code>. In the <code>Just</code> case, we get access to a value we've named
<code>remainder</code> that we can access on the other side of the arrow.</p>
<h3>Infix notation in Idris</h3>
<p>Idris allows functions to be called by infix notation, meaning we can put the
function call after the first argument by using backticks.</p>
<p>A good example of such a function call is the <code>modulo</code> function. We could say,
"modulo of dividend and divisor" but that's awkward. In a mathematics class,
it would feel more natural to say "dividend modulo divisor." Let's rearrange
our <code>modulo</code> call accordingly:</p>
<p><em>src/Division.idr</em></p>
<pre class="language-idris"><code class="language-idris"><span class="token keyword">module</span> <span class="token constant">Division</span><br /><br /><span class="token comment">||| Returns a `Just of the remainder of two numbers, or `Nothing` if the </span><br /><span class="token comment">||| divisor is zero.</span><br /><span class="token keyword">total</span><br /><span class="token hvariable">modulo</span> <span class="token operator">:</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Maybe</span> <span class="token constant">Nat</span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">_</span> <span class="token constant">Z</span> <span class="token operator">=</span> <span class="token constant">Nothing</span> <span class="token comment">--can't divide by zero, so Nothing</span><br /><span class="token hvariable">modulo</span> <span class="token constant">Z</span> <span class="token hvariable">_</span> <span class="token operator">=</span> <span class="token constant">Just</span> <span class="token number">0</span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">dividend</span> <span class="token hvariable">divisor</span> <span class="token operator">=</span> <br /> <span class="token keyword">if</span> <span class="token hvariable">dividend</span> <span class="token operator">>=</span> <span class="token hvariable">divisor</span><br /> <span class="token keyword">then</span> <span class="token hvariable">modulo</span> <span class="token punctuation">(</span><span class="token hvariable">assert_smaller</span> <span class="token hvariable">dividend</span> <span class="token punctuation">(</span><span class="token hvariable">minus</span> <span class="token hvariable">dividend</span> <span class="token hvariable">divisor</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token hvariable">divisor</span><br /> <span class="token keyword">else</span> <span class="token constant">Just</span> <span class="token hvariable">dividend</span><br /><br /><span class="token comment">||| Returns `True` if the first number divides the second evenly, otherwise </span><br /><span class="token comment">||| returns `False`. Also returns `False` if the divisor is zero. </span><br /><span class="token keyword">total</span><br /><span class="token hvariable">divides</span> <span class="token operator">:</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Bool</span><br /><span class="token hvariable">divides</span> <span class="token hvariable">divisor</span> <span class="token hvariable">dividend</span> <span class="token operator">=</span> <br /> <span class="token keyword">case</span> <span class="token hvariable">dividend</span> <span class="token operator">`modulo`</span> <span class="token hvariable">divisor</span> <span class="token keyword">of</span> <br /> <span class="token constant">Nothing</span> <span class="token operator">=></span> <span class="token operator">?</span><span class="token hvariable">nothingCase</span><br /> <span class="token constant">Just</span> <span class="token hvariable">remainder</span> <span class="token operator">=></span> <span class="token operator">?</span><span class="token hvariable">somethingCase</span></code></pre>
<p>Most of the time, I'm not crazy about this syntax, but for arithmetic it does
make sense.</p>
<h3>Replacing the last Idris holes</h3>
<p>Both cases in our <code>case</code> expression should evaluate to our return type. Since
we're saying that <code>divides</code> is <code>False</code> where <code>modulo</code> is not defined, we can
say that the first case is <code>False</code>, and the second depends on whether or not
the <code>remainder</code> is <code>0</code>.</p>
<p><em>src/Division.idr</em></p>
<pre class="language-idris"><code class="language-idris"><span class="token keyword">module</span> <span class="token constant">Division</span><br /><br /><span class="token comment">||| Returns a `Just of the remainder of two numbers, or `Nothing` if the </span><br /><span class="token comment">||| divisor is zero.</span><br /><span class="token keyword">total</span><br /><span class="token hvariable">modulo</span> <span class="token operator">:</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Maybe</span> <span class="token constant">Nat</span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">_</span> <span class="token constant">Z</span> <span class="token operator">=</span> <span class="token constant">Nothing</span> <span class="token comment">--can't divide by zero, so Nothing</span><br /><span class="token hvariable">modulo</span> <span class="token constant">Z</span> <span class="token hvariable">_</span> <span class="token operator">=</span> <span class="token constant">Just</span> <span class="token number">0</span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">dividend</span> <span class="token hvariable">divisor</span> <span class="token operator">=</span> <br /> <span class="token keyword">if</span> <span class="token hvariable">dividend</span> <span class="token operator">>=</span> <span class="token hvariable">divisor</span><br /> <span class="token keyword">then</span> <span class="token hvariable">modulo</span> <span class="token punctuation">(</span><span class="token hvariable">assert_smaller</span> <span class="token hvariable">dividend</span> <span class="token punctuation">(</span><span class="token hvariable">minus</span> <span class="token hvariable">dividend</span> <span class="token hvariable">divisor</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token hvariable">divisor</span><br /> <span class="token keyword">else</span> <span class="token constant">Just</span> <span class="token hvariable">dividend</span><br /><br /><span class="token comment">||| Returns `True` if the first number divides the second evenly, otherwise </span><br /><span class="token comment">||| returns `False`. Also returns `False` if the divisor is zero. </span><br /><span class="token keyword">total</span><br /><span class="token hvariable">divides</span> <span class="token operator">:</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Bool</span><br /><span class="token hvariable">divides</span> <span class="token hvariable">divisor</span> <span class="token hvariable">dividend</span> <span class="token operator">=</span> <br /> <span class="token keyword">case</span> <span class="token hvariable">dividend</span> <span class="token operator">`modulo`</span> <span class="token hvariable">divisor</span> <span class="token keyword">of</span> <br /> <span class="token constant">Nothing</span> <span class="token operator">=></span> <span class="token constant">False</span><br /> <span class="token constant">Just</span> <span class="token hvariable">remainder</span> <span class="token operator">=></span> <span class="token hvariable">remainder</span> <span class="token operator">==</span> <span class="token number">0</span></code></pre>
<p>As you can see above, I also took the liberty of labeling this a <code>total</code>
function because for all possible values of <code>divisor</code> and <code>dividend</code>, this
program will terminate with an answer of <code>True</code> or <code>False</code> in finite time, and
the compiler knows this.</p>
<h3>Wrapping up Maybe monads, infix notation, and holes</h3>
<p>At this point, I'm starting to read and write Idris a little easier, and I hope
you are, too. Hopefully, you're also starting to see the value constructs like
monads, which are like succinct, generically-typed null-object design patterns
that make sure you're accounting for potential missing data (without
introducing "NullPointerExceptions" or some such horror).
If you're struggling to follow, let me know! Again, my goal is
to make Idris accessible to non-Haskellers. If you're still excited about
total programming, get ready! In our next example, we're going to create our
own FizzBuzz-specific type.</p>
Idris FizzBuzz Part III: Defining Types and Importing Modules2023-05-10T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/idris-fizzbuzz-part-iii-types-and-importing-modules/<p>This is Part III of my attempt to explain how to use the Idris programming
language through a tutorial on FizzBuzz, the common software development
hiring interview problem. If you're new to Idris and haven't read
<a href="https://webbureaucrat.gitlab.io/articles/idris-fizzbuzz-part-i-monads-comments-assert-smaller/">Part I</a> and
<a href="https://webbureaucrat.gitlab.io/articles/idris-fizzbuzz-part-ii-maybes-infix-notation-idris-holes/">Part II</a>,
I recommend doing that now. This article will go over defining our
own types and importing our own modules as well as recap what we've learned
about case-matching and basic syntax.</p>
<p>Just to recap, here's what we have so far:</p>
<pre class="language-idris"><code class="language-idris"><span class="token keyword">module</span> <span class="token constant">Division</span><br /><br /><span class="token comment">||| Returns a `Just of the remainder of two numbers, or `Nothing` if the </span><br /><span class="token comment">||| divisor is zero.</span><br /><span class="token keyword">total</span><br /><span class="token hvariable">modulo</span> <span class="token operator">:</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Maybe</span> <span class="token constant">Nat</span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">_</span> <span class="token constant">Z</span> <span class="token operator">=</span> <span class="token constant">Nothing</span> <span class="token comment">--can't divide by zero, so Nothing</span><br /><span class="token hvariable">modulo</span> <span class="token constant">Z</span> <span class="token hvariable">_</span> <span class="token operator">=</span> <span class="token constant">Just</span> <span class="token number">0</span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">dividend</span> <span class="token hvariable">divisor</span> <span class="token operator">=</span> <br /> <span class="token keyword">if</span> <span class="token hvariable">dividend</span> <span class="token operator">>=</span> <span class="token hvariable">divisor</span><br /> <span class="token keyword">then</span> <span class="token hvariable">modulo</span> <span class="token punctuation">(</span><span class="token hvariable">assert_smaller</span> <span class="token hvariable">dividend</span> <span class="token punctuation">(</span><span class="token hvariable">minus</span> <span class="token hvariable">dividend</span> <span class="token hvariable">divisor</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token hvariable">divisor</span><br /> <span class="token keyword">else</span> <span class="token constant">Just</span> <span class="token hvariable">dividend</span><br /><br /><span class="token comment">||| Returns `True` if the first number divides the second evenly, otherwise </span><br /><span class="token comment">||| returns `False`. Also returns `False` if the divisor is zero. </span><br /><span class="token keyword">total</span><br /><span class="token hvariable">divides</span> <span class="token operator">:</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Bool</span><br /><span class="token hvariable">divides</span> <span class="token hvariable">divisor</span> <span class="token hvariable">dividend</span> <span class="token operator">=</span> <br /> <span class="token keyword">case</span> <span class="token hvariable">dividend</span> <span class="token operator">`modulo`</span> <span class="token hvariable">divisor</span> <span class="token keyword">of</span> <br /> <span class="token constant">Nothing</span> <span class="token operator">=></span> <span class="token constant">False</span><br /> <span class="token constant">Just</span> <span class="token hvariable">remainder</span> <span class="token operator">=></span> <span class="token hvariable">remainder</span> <span class="token operator">==</span> <span class="token number">0</span></code></pre>
<p>We have completed
our Divides module, which holds all of the basic arithmetic we need
for our program, so now we get to work on FizzBuzz specific logic. Let's put
that logic in a different module and call it FizzBuzz.</p>
<p>When we think about what we need for FizzBuzz, one of the first things we
should notice is that we're classifying numbers and specifying different
behavior based on their properties. In object orientation, we might define a
<code>Printable</code> interface and have <code>Fizz</code> <code>Buzz</code>, <code>FizzBuzz</code> and regular <code>Number</code>
implementations, but in Idris we can simply define a union type like so:</p>
<p><em>src/FizzBuzz.idr</em></p>
<pre class="language-idris"><code class="language-idris"><span class="token keyword">module</span> <span class="token constant">FizzBuzz</span><br /><br /><span class="token keyword">data</span> <span class="token constant">FizzBuzzNumber</span> <span class="token operator">=</span> <span class="token constant">FizzBuzz</span> <span class="token operator">|</span> <span class="token constant">Fizz</span> <span class="token operator">|</span> <span class="token constant">Buzz</span> <span class="token operator">|</span> <span class="token constant">Number</span> <span class="token constant">Nat</span></code></pre>
<p>Note that <code>Number</code> has a <code>Nat</code> data member just like <code>Just</code> had a <code>Nat</code> data
member in our previous article.</p>
<p>This is a little terse, though. We can do better.</p>
<h3>Idris documentation comments in type definitions</h3>
<p>Recall that a comment defined with three bars (<code>|||</code>) is a documentation
comment in Idris, meaning it can show up in the Idris tooling and help the
user as well as the reader of the source. Let's fix this union type up with
documentation comments.</p>
<p><em>src/FizzBuzz.idr</em></p>
<pre class="language-idris"><code class="language-idris"><span class="token keyword">module</span> <span class="token constant">FizzBuzz</span><br /><br /><span class="token keyword">data</span> <span class="token constant">FizzBuzzNumber</span> <span class="token operator">=</span> <span class="token comment">||| Represents a number divisible by both 3 and 5.</span><br /> <span class="token constant">FizzBuzz</span> <br /> <span class="token operator">|</span> <span class="token comment">||| Represents a number divisible by 3. </span><br /> <span class="token constant">Fizz</span> <br /> <span class="token operator">|</span> <span class="token comment">||| Represents a number divisible by 5.</span><br /> <span class="token constant">Buzz</span> <br /> <span class="token operator">|</span> <span class="token comment">||| Represents a number divisible by neither 3 nor 5. </span><br /> <span class="token constant">Number</span> <span class="token constant">Nat</span><br /></code></pre>
<h3>Importing modules in the Idris programming language</h3>
<p>Next we need a way of getting from <code>Nat</code> numbers to <code>FizzBuzzNumber</code>s, which
means we need our <code>divides</code> function from the previous post. To start, let's
export just the <code>divides</code> function from the <code>Division</code> module by decorating
the <code>divides</code> function with the <code>export</code> keyword.</p>
<p><em>src/Division.idr</em></p>
<pre class="language-idris"><code class="language-idris"><span class="token keyword">module</span> <span class="token constant">Division</span><br /><br /><span class="token comment">||| Returns a `Just of the remainder of two numbers, or `Nothing` if the </span><br /><span class="token comment">||| divisor is zero.</span><br /><span class="token keyword">total</span><br /><span class="token hvariable">modulo</span> <span class="token operator">:</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Maybe</span> <span class="token constant">Nat</span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">_</span> <span class="token constant">Z</span> <span class="token operator">=</span> <span class="token constant">Nothing</span> <span class="token comment">--can't divide by zero, so Nothing</span><br /><span class="token hvariable">modulo</span> <span class="token constant">Z</span> <span class="token hvariable">_</span> <span class="token operator">=</span> <span class="token constant">Just</span> <span class="token number">0</span><br /><span class="token hvariable">modulo</span> <span class="token hvariable">dividend</span> <span class="token hvariable">divisor</span> <span class="token operator">=</span> <br /> <span class="token keyword">if</span> <span class="token hvariable">dividend</span> <span class="token operator">>=</span> <span class="token hvariable">divisor</span><br /> <span class="token keyword">then</span> <span class="token hvariable">modulo</span> <span class="token punctuation">(</span><span class="token hvariable">assert_smaller</span> <span class="token hvariable">dividend</span> <span class="token punctuation">(</span><span class="token hvariable">minus</span> <span class="token hvariable">dividend</span> <span class="token hvariable">divisor</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token hvariable">divisor</span><br /> <span class="token keyword">else</span> <span class="token constant">Just</span> <span class="token hvariable">dividend</span><br /><br /><span class="token comment">||| Returns `True` if the first number divides the second evenly, otherwise </span><br /><span class="token comment">||| returns `False`. Also returns `False` if the divisor is zero. </span><br /><span class="token keyword">export</span> <span class="token keyword">total</span> <br /><span class="token hvariable">divides</span> <span class="token operator">:</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">Bool</span><br /><span class="token hvariable">divides</span> <span class="token hvariable">divisor</span> <span class="token hvariable">dividend</span> <span class="token operator">=</span> <br /> <span class="token keyword">case</span> <span class="token hvariable">dividend</span> <span class="token operator">`modulo`</span> <span class="token hvariable">divisor</span> <span class="token keyword">of</span> <br /> <span class="token constant">Nothing</span> <span class="token operator">=></span> <span class="token constant">False</span><br /> <span class="token constant">Just</span> <span class="token hvariable">remainder</span> <span class="token operator">=></span> <span class="token hvariable">remainder</span> <span class="token operator">==</span> <span class="token number">0</span></code></pre>
<p>Now, we can go ahead and import the <code>Division</code> module by using the <code>import</code>
keyword in the <code>FizzBuzz</code> module.</p>
<pre class="language-idris"><code class="language-idris"><span class="token keyword">module</span> <span class="token constant">FizzBuzz</span><br /><br /><span class="token keyword">import</span> <span class="token import-statement">Division</span> <span class="token comment">--imports the `divides` function</span><br /><br /><span class="token keyword">data</span> <span class="token constant">FizzBuzzNumber</span> <span class="token operator">=</span> <span class="token comment">||| Represents a number divisible by both 3 and 5.</span><br /> <span class="token constant">FizzBuzz</span> <br /> <span class="token operator">|</span> <span class="token comment">||| Represents a number divisible by 3. </span><br /> <span class="token constant">Fizz</span> <br /> <span class="token operator">|</span> <span class="token comment">||| Represents a number divisible by 5.</span><br /> <span class="token constant">Buzz</span> <br /> <span class="token operator">|</span> <span class="token comment">||| Represents a number divisible by neither 3 nor 5. </span><br /> <span class="token constant">Number</span> <span class="token constant">Nat</span></code></pre>
<p>I went ahead and put in a comment explaining exactly what we're importing
because a peculiar thing about the Idris language is that, by default, Idris
exposes all <code>expose</code>d modules.</p>
<p>(Frankly, I'm not crazy about that as a default, so a comment is a good
practice.)</p>
<h3>Nested conditional logic in Idris</h3>
<p>I admit I'm torn on using the infix notation from the previous article. I'm not
crazy about filling up my code with a bunch of backticks, but for mathematical
expressions where the English really lends itself to saying something like,
"if 3 evenly divides n then..." I think infix makes sense.</p>
<pre class="language-idris"><code class="language-idris"><span class="token keyword">module</span> <span class="token constant">FizzBuzz</span><br /><br /><span class="token keyword">import</span> <span class="token import-statement">Division</span> <span class="token comment">--imports the `divides` function</span><br /><br /><span class="token keyword">data</span> <span class="token constant">FizzBuzzNumber</span> <span class="token operator">=</span> <span class="token comment">||| Represents a number divisible by both 3 and 5.</span><br /> <span class="token constant">FizzBuzz</span> <br /> <span class="token operator">|</span> <span class="token comment">||| Represents a number divisible by 3. </span><br /> <span class="token constant">Fizz</span> <br /> <span class="token operator">|</span> <span class="token comment">||| Represents a number divisible by 5.</span><br /> <span class="token constant">Buzz</span> <br /> <span class="token operator">|</span> <span class="token comment">||| Represents a number divisible by neither 3 nor 5. </span><br /> <span class="token constant">Number</span> <span class="token constant">Nat</span><br /><br /><span class="token comment">||| Given a number, returns a Fizzbuzz representation of the number depending</span><br /><span class="token comment">||| on whether it is divisible by three, five, or both, using the `divides` </span><br /><span class="token comment">||| function from the `Division` module.</span><br /><span class="token keyword">total</span><br /><span class="token hvariable">classify</span> <span class="token operator">:</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">FizzBuzzNumber</span><br /><span class="token hvariable">classify</span> <span class="token hvariable">n</span> <span class="token operator">=</span> <br /> <span class="token keyword">if</span> <span class="token number">15</span> <span class="token operator">`divides`</span> <span class="token hvariable">n</span> <br /> <span class="token keyword">then</span> <span class="token constant">FizzBuzz</span><br /> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token number">3</span> <span class="token operator">`divides`</span> <span class="token hvariable">n</span> <br /> <span class="token keyword">then</span> <span class="token constant">Fizz</span> <br /> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token number">5</span> <span class="token operator">`divides`</span> <span class="token hvariable">n</span> <span class="token keyword">then</span> <span class="token constant">Buzz</span> <span class="token keyword">else</span> <span class="token constant">Number</span> <span class="token hvariable">n</span> <br /></code></pre>
<p>Note again that we don't have to specify <code>Division.divides</code> in our code, though
in most cases I think I would, anyway.</p>
<p>As you can see, nested conditionals in Idris work a lot like they do in other
languages, or more precisely, like the ternary (<code>?:</code>) operators do in other
languages since both clauses evaluate to the return type.</p>
<h3>Converting a number to a <code>String</code> type in Idris</h3>
<p>Now we can pattern match on the type <code>FizzBuzzNumber</code> as we have before.</p>
<p><em>src/FizzBuzz.idr</em></p>
<pre class="language-idris"><code class="language-idris"><span class="token keyword">module</span> <span class="token constant">FizzBuzz</span><br /><br /><span class="token keyword">import</span> <span class="token import-statement">Division</span> <span class="token comment">--imports the `divides` function</span><br /><br /><span class="token keyword">data</span> <span class="token constant">FizzBuzzNumber</span> <span class="token operator">=</span> <span class="token comment">||| Represents a number divisible by both 3 and 5.</span><br /> <span class="token constant">FizzBuzz</span> <br /> <span class="token operator">|</span> <span class="token comment">||| Represents a number divisible by 3. </span><br /> <span class="token constant">Fizz</span> <br /> <span class="token operator">|</span> <span class="token comment">||| Represents a number divisible by 5.</span><br /> <span class="token constant">Buzz</span> <br /> <span class="token operator">|</span> <span class="token comment">||| Represents a number divisible by neither 3 nor 5. </span><br /> <span class="token constant">Number</span> <span class="token constant">Nat</span><br /><br /><span class="token comment">||| Given a number, returns a Fizzbuzz representation of the number depending</span><br /><span class="token comment">||| on whether it is divisible by three, five, or both, using the `divides` </span><br /><span class="token comment">||| function from the `Division` module.</span><br /><span class="token keyword">total</span><br /><span class="token hvariable">classify</span> <span class="token operator">:</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">FizzBuzzNumber</span><br /><span class="token hvariable">classify</span> <span class="token hvariable">n</span> <span class="token operator">=</span> <br /> <span class="token keyword">if</span> <span class="token number">15</span> <span class="token operator">`divides`</span> <span class="token hvariable">n</span> <br /> <span class="token keyword">then</span> <span class="token constant">FizzBuzz</span><br /> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token number">3</span> <span class="token operator">`divides`</span> <span class="token hvariable">n</span> <br /> <span class="token keyword">then</span> <span class="token constant">Fizz</span> <br /> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token number">5</span> <span class="token operator">`divides`</span> <span class="token hvariable">n</span> <span class="token keyword">then</span> <span class="token constant">Buzz</span> <span class="token keyword">else</span> <span class="token constant">Number</span> <span class="token hvariable">n</span> <br /><br /><span class="token keyword">total</span><br /><span class="token hvariable">fbnToString</span> <span class="token operator">:</span> <span class="token constant">FizzBuzzNumber</span> <span class="token operator">-></span> <span class="token constant">String</span><br /><span class="token hvariable">fbnToString</span> <span class="token constant">FizzBuzz</span> <span class="token operator">=</span> <span class="token string">"FizzBuzz"</span><br /><span class="token hvariable">fbnToString</span> <span class="token constant">Fizz</span> <span class="token operator">=</span> <span class="token string">"Fizz"</span><br /><span class="token hvariable">fbnToString</span> <span class="token constant">Buzz</span> <span class="token operator">=</span> <span class="token string">"Buzz"</span><br /><span class="token hvariable">fbnToString</span> <span class="token punctuation">(</span><span class="token constant">Number</span> <span class="token hvariable">n</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token hvariable">show</span> <span class="token hvariable">n</span> </code></pre>
<p>As you can see, for most numbers, I'm using the <code>show</code> function, which converts
a <code>Nat</code> to a <code>String</code> in Idris.</p>
<h3>Modular encapsulation in Idris</h3>
<p>Clearly, we're pretty close to being able to use our <code>FizzBuzz</code> module. It
would be great, though, if we we could hide the <code>FizzBuzzNumber</code> type entirely
by <code>expose</code>ing a function to convert a number to a FizzBuzz <code>String</code> directly.</p>
<p>Let's do that now:</p>
<p><em>src/FizzBuzz.idr</em></p>
<pre class="language-idris"><code class="language-idris"><span class="token keyword">module</span> <span class="token constant">FizzBuzz</span><br /><br /><span class="token keyword">import</span> <span class="token import-statement">Division</span> <span class="token comment">--imports the `divides` function</span><br /><br /><span class="token keyword">data</span> <span class="token constant">FizzBuzzNumber</span> <span class="token operator">=</span> <span class="token comment">||| Represents a number divisible by both 3 and 5.</span><br /> <span class="token constant">FizzBuzz</span> <br /> <span class="token operator">|</span> <span class="token comment">||| Represents a number divisible by 3. </span><br /> <span class="token constant">Fizz</span> <br /> <span class="token operator">|</span> <span class="token comment">||| Represents a number divisible by 5.</span><br /> <span class="token constant">Buzz</span> <br /> <span class="token operator">|</span> <span class="token comment">||| Represents a number divisible by neither 3 nor 5. </span><br /> <span class="token constant">Number</span> <span class="token constant">Nat</span><br /><br /><span class="token comment">||| Given a number, returns a Fizzbuzz representation of the number depending</span><br /><span class="token comment">||| on whether it is divisible by three, five, or both, using the `divides` </span><br /><span class="token comment">||| function from the `Division` module.</span><br /><span class="token keyword">total</span><br /><span class="token hvariable">classify</span> <span class="token operator">:</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">FizzBuzzNumber</span><br /><span class="token hvariable">classify</span> <span class="token hvariable">n</span> <span class="token operator">=</span> <br /> <span class="token keyword">if</span> <span class="token number">15</span> <span class="token operator">`divides`</span> <span class="token hvariable">n</span> <br /> <span class="token keyword">then</span> <span class="token constant">FizzBuzz</span><br /> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token number">3</span> <span class="token operator">`divides`</span> <span class="token hvariable">n</span> <br /> <span class="token keyword">then</span> <span class="token constant">Fizz</span> <br /> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token number">5</span> <span class="token operator">`divides`</span> <span class="token hvariable">n</span> <span class="token keyword">then</span> <span class="token constant">Buzz</span> <span class="token keyword">else</span> <span class="token constant">Number</span> <span class="token hvariable">n</span> <br /><br /><span class="token comment">||| Converts a `FizzBuzzNumber` value to a `String`. </span><br /><span class="token keyword">total</span><br /><span class="token hvariable">fbnToString</span> <span class="token operator">:</span> <span class="token constant">FizzBuzzNumber</span> <span class="token operator">-></span> <span class="token constant">String</span><br /><span class="token hvariable">fbnToString</span> <span class="token constant">FizzBuzz</span> <span class="token operator">=</span> <span class="token string">"FizzBuzz"</span><br /><span class="token hvariable">fbnToString</span> <span class="token constant">Fizz</span> <span class="token operator">=</span> <span class="token string">"Fizz"</span><br /><span class="token hvariable">fbnToString</span> <span class="token constant">Buzz</span> <span class="token operator">=</span> <span class="token string">"Buzz"</span><br /><span class="token hvariable">fbnToString</span> <span class="token punctuation">(</span><span class="token constant">Number</span> <span class="token hvariable">n</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token hvariable">show</span> <span class="token hvariable">n</span> <br /><br /><span class="token comment">||| Converts a `Nat` to a `String` according to the standard requirements of</span><br /><span class="token comment">||| FizzBuzz, displaying "Fizz" if the number is divisible by 3, "Buzz" if the</span><br /><span class="token comment">||| number is divisible by 5, or "Fizzbuzz" if the number is divisible by both</span><br /><span class="token comment">||| 3 and five. </span><br /><span class="token keyword">export</span> <span class="token keyword">total</span><br /><span class="token hvariable">numberToFBNString</span> <span class="token operator">:</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">String</span><br /><span class="token hvariable">numberToFBNString</span> <span class="token hvariable">n</span> <span class="token operator">=</span> <span class="token hvariable">fbnToString</span> <span class="token punctuation">(</span><span class="token hvariable">classify</span> <span class="token hvariable">n</span><span class="token punctuation">)</span><br /></code></pre>
<p>What we now have is effectively a small library that exports two functions:
<code>Division.divides</code> and <code>FizzBuzz.numbertoFBNString</code>. In the next article, we'll
set up a <em>.ipkg</em> package file and make the function available on our system.</p>
<h3>In conclusion</h3>
<p>In this tutorial, I hope you've learned the basics of declaring a union type
and how importing modules works in Idris. Let me know if there's anything I can
make any clearer. In the next module, we'll be looking at setting up our main
function and trying our hands at defining our own types!</p>
Idris FizzBuzz Part IV: Main and .ipkg Files2023-05-19T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/idris-fizzbuzz-part-iv-main-and-ipkg-files/<p>This is the fourth and final part of a walkthrough of FizzBuzz, a common
interview problem, in Idris. If you haven't read the other parts, you can
<a href="https://webbureaucrat.gitlab.io/articles/idris-fizzbuzz-part-i-monads-comments-assert-smaller/">start with Part I</a>. To show how to import and use libraries,
we're going to divide this project into a library and an executable. Along the
way, we will create <em>.ipkg</em> packages for both.</p>
<h3>Idris .ipkg files for libraries</h3>
<p>Start by running <code>idris2 --init</code> in the fizzbuzz project and fill in some
sensible defaults. This is what I ended up with:</p>
<p><em>fizzbuzz.ipkg</em></p>
<pre><code>package fizzbuzz
authors = "eleanorofs"
brief = "a fizzbuzz example in the Idris language."
bugtracker = "https://gitlab.com/eleanorofs/idris-fizzbuzz/-/issues"
-- packages to add to search path
-- depends =
-- name of executable
-- executable =
homepage = "https://gitlab.com/eleanorofs/idris-fizzbuzz"
license = "MIT"
-- main file (i.e. file to load at REPL)
-- main =
maintainers = "eleanorofs"
-- modules to install
modules = Division, FizzBuzz
opts = "--warnpartial"
readme = "./README.md"
sourcedir = "src"
sourceloc = "https://gitlab.com/eleanorofs/idris-fizzbuzz.git"
version = 0.0.1
</code></pre>
<p>The important thing here is that <code>main</code> and <code>executable</code> are not filled in
because this is a library.</p>
<h3>Writing an Idris executable</h3>
<p>Now we can create a new Idris package for the executable. As before, let's
start with the source.</p>
<p>Let's recall that we have a method <code>numberToFBNString</code> which takes a single
number and returns the string FizzBuzz equivalent of the number. Now, consider
how we might do that recursively for a list of numbers. One way might be to
combine the numbers into a long string and then print that string, but that
would result in a slow-feeling user experience for large numbers as it would
take a long time to concatenate the string. Instead, let's print the numbers
one at a time, using the <code>do</code> syntax and the <code>IO ()</code> monad.</p>
<p><em>idris-fizzbuzz-main/src/Main.idr</em></p>
<pre class="language-idris"><code class="language-idris"><span class="token keyword">module</span> <span class="token constant">Main</span><br /><br /><span class="token keyword">import</span> <span class="token import-statement">FizzBuzz</span><br /><br /><span class="token hvariable">printFizzBuzz</span> <span class="token operator">:</span> <span class="token constant">Vect</span> <span class="token hvariable">n</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">IO</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><br /><span class="token hvariable">printFizzBuzz</span> <span class="token constant">Nil</span> <span class="token operator">=</span> <span class="token hvariable">putStrLn</span> <span class="token string">""</span><br /><span class="token hvariable">printFizzBuzz</span> <span class="token punctuation">(</span><span class="token hvariable">x</span> <span class="token operator">::</span> <span class="token hvariable">xs</span><span class="token punctuation">)</span> <span class="token operator">=</span> <br /> <span class="token keyword">do</span> <span class="token hvariable">putStrLn</span> <span class="token operator">$</span> <span class="token hvariable">FizzBuzz<span class="token punctuation">.</span>numberToFBNString</span> <span class="token hvariable">x</span><br /> <span class="token hvariable">printFizzBuzz</span> <span class="token hvariable">xs</span> <span class="token comment">--recursive call on the rest of the list</span></code></pre>
<p>Now we can write a main function which calls <code>printFizzBuzz</code> on a long vector,
and we're done.</p>
<p><em>idris-fizzbuzz-main/src/Main.idr</em></p>
<pre class="language-idris"><code class="language-idris"><span class="token keyword">module</span> <span class="token constant">Main</span><br /><br /><span class="token keyword">import</span> <span class="token import-statement">Data<span class="token punctuation">.</span>Vect</span> <span class="token comment">--imports fromList</span><br /><span class="token keyword">import</span> <span class="token import-statement">FizzBuzz</span><br /><br /><span class="token hvariable">printFizzBuzz</span> <span class="token operator">:</span> <span class="token constant">Vect</span> <span class="token hvariable">n</span> <span class="token constant">Nat</span> <span class="token operator">-></span> <span class="token constant">IO</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><br /><span class="token hvariable">printFizzBuzz</span> <span class="token constant">Nil</span> <span class="token operator">=</span> <span class="token hvariable">putStrLn</span> <span class="token string">""</span><br /><span class="token hvariable">printFizzBuzz</span> <span class="token punctuation">(</span><span class="token hvariable">x</span> <span class="token operator">::</span> <span class="token hvariable">xs</span><span class="token punctuation">)</span> <span class="token operator">=</span> <br /> <span class="token keyword">do</span> <span class="token hvariable">putStrLn</span> <span class="token operator">$</span> <span class="token hvariable">FizzBuzz<span class="token punctuation">.</span>numberToFBNString</span> <span class="token hvariable">x</span><br /> <span class="token hvariable">printFizzBuzz</span> <span class="token hvariable">xs</span> <span class="token comment">--recursive call on the rest of the list</span><br /><br /><span class="token keyword">total</span><br /><span class="token hvariable">main</span> <span class="token operator">:</span> <span class="token constant">IO</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><br /><span class="token hvariable">main</span> <span class="token operator">=</span> <span class="token hvariable">printFizzBuzz</span> <span class="token operator">$</span> <span class="token hvariable">fromList</span> <span class="token punctuation">[</span> <span class="token number">1</span> <span class="token operator">..</span> <span class="token number">1000</span> <span class="token punctuation">]</span></code></pre>
<p>Note that <code>main</code> is a total function! We have a strong guarantee that it will
never error out.</p>
<h3>Idris <em>.ipkg</em> files for executables</h3>
<p>Now run <code>idris2 --init</code> again, but then change the values to an executable by
including the main file and the executable name. Then, add your <code>fizzbuzz</code>
library as a dependency using <code>depends</code>. You should end up with something
like this:</p>
<p><em>idris-fizzbuzz-main/fizzbuzz-main.ipkg</em></p>
<pre class="language-idris"><code class="language-idris"><span class="token hvariable">package</span> <span class="token hvariable">fizzbuzz</span><span class="token operator">-</span><span class="token hvariable">main</span><br /><span class="token hvariable">authors</span> <span class="token operator">=</span> <span class="token string">"eleanorofs"</span><br /><span class="token hvariable">brief</span> <span class="token operator">=</span> <span class="token string">"prints FizzBuzz from 1 to 1000."</span><br /><span class="token hvariable">license</span> <span class="token operator">=</span> <span class="token string">"MIT"</span><br /><span class="token hvariable">maintainers</span> <span class="token operator">=</span> <span class="token string">"eleanorofs"</span><br /><span class="token hvariable">readme</span> <span class="token operator">=</span> <span class="token string">"./README.md"</span><br /><span class="token comment">-- homepage = </span><br /><span class="token comment">-- sourceloc =</span><br /><span class="token comment">-- bugtracker =</span><br /><br /><span class="token comment">-- packages to add to search path</span><br /><span class="token hvariable">depends</span> <span class="token operator">=</span> <span class="token hvariable">fizzbuzz</span><br /><br /><span class="token comment">-- modules to install</span><br /><span class="token hvariable">modules</span> <span class="token operator">=</span> <span class="token constant">Main</span><br /><br /><span class="token comment">-- main file (i.e. file to load at REPL)</span><br /><span class="token hvariable">main</span> <span class="token operator">=</span> <span class="token constant">Main</span><br /><br /><span class="token comment">-- name of executable</span><br /><span class="token hvariable">executable</span> <span class="token operator">=</span> <span class="token string">"fizzbuzz-main"</span><br /><span class="token hvariable">opts</span> <span class="token operator">=</span> <span class="token string">"--warnpartial"</span><br /><span class="token hvariable">sourcedir</span> <span class="token operator">=</span> <span class="token string">"src"</span><br /><span class="token hvariable">version</span> <span class="token operator">=</span> <span class="token number">0.0</span><span class="token punctuation">.</span><span class="token number">1</span></code></pre>
<h3>Compiling and running an Idris executable</h3>
<p>With this configuration in place, we should be able to compile our executable
with <code>idris2 --build fizzbuzz-main.ipkg</code>. This will produce a <em>build</em> folder.
We can run the executable, then, as <code>./build/exec/fizzbuzz-main</code>.</p>
<p>You should see, well, FizzBuzz.</p>
<p>If something isn't quite right, have a look at this Nix shell
script which walks through all the steps needed to run this executable,
including downloading and installing the previous FizzBuzz library.</p>
<pre class="language-bash"><code class="language-bash"><span class="token shebang important">#!/usr/bin/env nix-shell</span><br /><span class="token comment">#! nix-shell -i bash --pure</span><br /><span class="token comment">#! nix-shell -p bash cacert chez git idris2</span><br /><br /><span class="token function">mkdir</span> dependencies<br /><span class="token builtin class-name">cd</span> dependencies<br /><span class="token function">git</span> clone https://gitlab.com/eleanorofs/idris-fizzbuzz<br /><span class="token builtin class-name">cd</span> idris-fizzbuzz<br />idris2 <span class="token parameter variable">--build</span> fizzbuzz.ipkg<br />idris2 <span class="token parameter variable">--install</span> fizzbuzz.ipkg<br /><span class="token builtin class-name">cd</span> <span class="token punctuation">..</span>/<span class="token punctuation">..</span><br />idris2 <span class="token parameter variable">--build</span> fizzbuzz-main.ipkg<br />./build/exec/fizzbuzz-main</code></pre>
<p>(This is something I love about Nix--scripts themselves are not only perfectly
reproducible but include self-documenting lists of dependencies.)</p>
<h3>Wrapping up FizzBuzz for Idris</h3>
<p>I hope this has been a useful introduction to Idris and its basic usage and
syntax. This was a fun little project for me, and, if you're reading this, I
hope to see what fun little projects you write in Idris, too.</p>
<ul>
<li><a href="https://gitlab.com/eleanorofs/idris-fizzbuzz">Full source for library project</a></li>
<li>[Full source for executable project)(<a href="https://gitlab.com/eleanorofs/idris-fizzbuzz-main">https://gitlab.com/eleanorofs/idris-fizzbuzz-main</a>)</li>
</ul>
Running and Connecting to Postgres Using Docker Compose2023-06-15T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/running-and-connecting-to-postgres-using-docker-compose/<p>Setting up a new development
environment from scratch can feel like a slog. This is a
quick copy-paste recipe for getting up and running quickly with Postgres
in <code>docker compose</code>.</p>
<h3>Start with a .env file</h3>
<p>At the very least, we know we will need a <code>POSTGRES_PASSWORD</code> environment
variable, and our .env file doesn't rely on any other files, so it's a good
place to start.</p>
<p>I personally am not crazy about hiding files by beginning them with periods, so
I'm going to call my file <em>database.env</em>.</p>
<p><em>database.env</em></p>
<pre class="language-bash"><code class="language-bash"><span class="token assign-left variable">POSTGRES_USER</span><span class="token operator">=</span>postgres_user<br /><span class="token assign-left variable">POSTGRES_PASSWORD</span><span class="token operator">=</span>postgres_password<br /><span class="token assign-left variable">POSTGRES_DB</span><span class="token operator">=</span>postgres_database</code></pre>
<p>It goes without saying, but this is for <em>local development</em>. Sensitive
information for any database you intend to stand up for a while should be
treated better than this. Use stronger passwords, and don't publish them
in a blog post on the Internet, etc., etc.</p>
<h3>Create database scripts</h3>
<p>This section is more or less the "Draw the rest of the owl" portion of this
article. The idea here is to create whatever database scripts--tables, stored
procedures, etc--in <em>./database/docker-entrypoint-initdb.d</em>. Any <em>.sql</em> or
compressed <em>.sql</em> file in this folder will be run by the database at startup.
(Other file extensions are supported. See
<a href="https://hub.docker.com/_/postgres/">the official documentation</a> for more.)</p>
<h3>Create a <em>docker-compose.yml</em> file</h3>
<p>Now we're ready to get started with our docker compose file. There's no magic
here--we're just exposing our ports, referencing our <em>database.env</em> file, and
feeding in our scripts from our scripts directory.</p>
<p><em>docker-compose.yml</em></p>
<pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">"3.8"</span><br /><span class="token key atrule">services</span><span class="token punctuation">:</span><br /> <span class="token key atrule">database</span><span class="token punctuation">:</span><br /> <span class="token key atrule">env_file</span><span class="token punctuation">:</span> <span class="token string">"database.env"</span><br /> <span class="token key atrule">image</span><span class="token punctuation">:</span> postgres<span class="token punctuation">:</span>latest<br /> <span class="token key atrule">ports</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> <span class="token string">"5432:5432"</span><br /> <span class="token key atrule">volumes</span><span class="token punctuation">:</span><br /> <span class="token punctuation">-</span> ./database/docker<span class="token punctuation">-</span>entrypoint<span class="token punctuation">-</span>initdb.d<span class="token punctuation">:</span>/docker<span class="token punctuation">-</span>entrypoint<span class="token punctuation">-</span>initdb.d</code></pre>
<p>You should now be able to run this with <code>docker compose up</code>.</p>
<h3>Connecting to a locally-running Postgres container</h3>
<p>With that compose file running, we can connect via SSH like so:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> compose <span class="token builtin class-name">exec</span> database <span class="token function">bash</span></code></pre>
<p>Note that "database" is the identifier chosen in the <em>docker-compose.yml</em>.</p>
<p>Lastly, we can run psql from within the container to run commands directly
inside the container. Use the values from your <em>database.env</em> file.</p>
<pre class="language-bash"><code class="language-bash">psql <span class="token parameter variable">--dbname</span> postgres_database <span class="token parameter variable">--host</span> localhost <span class="token parameter variable">--username</span> postgres_user</code></pre>
<p>This can be shortened to:</p>
<pre class="language-bash"><code class="language-bash">psql <span class="token parameter variable">-d</span> postgres_database <span class="token parameter variable">-h</span> localhost <span class="token parameter variable">-U</span> postgres_user</code></pre>
<p>From here, you can run Postgres queries against your database.</p>
<h3>Wrapping up</h3>
<p>Starting with a blank sheet of paper can be kind of daunting, so writing (and
having) basic tutorials like this to lay out some basic first steps to be a
good motivational and organizational tool. I hope this has been helpful.</p>
Writing a (Nix-Friendly) Hello World Web App in Haskell with Scotty2023-07-10T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/writing-a-hello-world-web-app-in-haskell-with-scotty/<p>Okay, so this article has been written before, and I don't have a lot new to
add, but some of these tutorials are getting pretty old, and the ones that
aren't use <code>stack</code>, which I personally am avoiding as it doesn't play well with
NixOS, so at least for me, for future reference, this tutorial will be useful.</p>
<h3>Prerequisites for Haskell and Scotty</h3>
<p>The first thing we need is a working development environment. I recommend Nix
for this because I've fallen in love with the reproducibility of Nix scripts.
Using Nix, we can download and run packages on the fly without messing with
the rest of our system. Whether we're using nix or not, we need
<code>cabal-install</code>, <code>ghc</code>, <code>git</code>, and <code>zlib</code>.</p>
<p>(A couple of those are not strictly intuitive, which is one reason
why I'm writing this tutorial. <code>git</code> is required for <code>cabal-install</code> to work
properly, and <code>zlib</code> is a dependency of Scotty.)</p>
<p>In Nix, we can simply start a new environment with</p>
<pre class="language-bash"><code class="language-bash">nix-shell <span class="token parameter variable">--packages</span> <span class="token function">bash</span> cabal-install ghc <span class="token function">git</span> zlib</code></pre>
<h3>Initializing a Hello World App in Haskell with <code>cabal</code></h3>
<p>We can initialize a new web app in Haskell with <code>cabal init</code>. I won't go
through all the steps because the defaults are generally very sensible.</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">mkdir</span> web<br /><span class="token builtin class-name">cd</span> web<br />cabal init</code></pre>
<p>For completeness, here's my sample <em>.cabal</em> file:</p>
<pre class="language-toml"><code class="language-toml">cabal-version: <span class="token number">3.0</span><br />-- The cabal-version field refers to the version of the <span class="token punctuation">.</span>cabal specification<span class="token punctuation">,</span><br />-- and can be different from the cabal-install (the tool) version and the<br />-- Cabal (the library) version you are using<span class="token punctuation">.</span> As such<span class="token punctuation">,</span> the Cabal (the library)<br />-- version used must be equal or greater than the version stated in this field<span class="token punctuation">.</span><br />-- Starting from the specification version <span class="token number">2.2</span><span class="token punctuation">,</span> the cabal-version field must be<br />-- the first thing in the cabal file<span class="token punctuation">.</span><br /><br />-- Initial package description <span class="token string">'web'</span> generated by<br />-- <span class="token string">'cabal init'</span><span class="token punctuation">.</span> For further documentation<span class="token punctuation">,</span> see:<br />-- http://haskell<span class="token punctuation">.</span>org/cabal/users-guide/<br />--<br />-- The name of the package<span class="token punctuation">.</span><br />name: web<br /><br />-- The package version<span class="token punctuation">.</span><br />-- See the Haskell package versioning policy (PVP) for standards<br />-- guiding when and how versions should be incremented<span class="token punctuation">.</span><br />-- https://pvp<span class="token punctuation">.</span>haskell<span class="token punctuation">.</span>org<br />-- PVP summary: +-+------- breaking API changes<br />-- | | +----- non-breaking API additions<br />-- | | | +--- code changes with no API change<br />version: <span class="token number">0.1</span><span class="token punctuation">.</span><span class="token number">0.0</span><br /><br />-- A short (one-line) description of the package<span class="token punctuation">.</span><br />-- synopsis:<br /><br />-- A longer description of the package<span class="token punctuation">.</span><br />-- description:<br /><br />-- The license under which the package is released<span class="token punctuation">.</span><br />license: GPL<span class="token number">-3.0</span>-or-later<br /><br />-- The file containing the license text<span class="token punctuation">.</span><br />license-file: LICENSE<br /><br />-- The package author(s)<span class="token punctuation">.</span><br />author: eleanorofs<br /><br />-- An email address to which users can send suggestions<span class="token punctuation">,</span> bug reports<span class="token punctuation">,</span> and patches<span class="token punctuation">.</span><br />maintainer: lu80mmgmt5nm@blurmail<span class="token punctuation">.</span>net<br /><br />-- A copyright notice<span class="token punctuation">.</span><br />-- copyright:<br />category: Web<br />build-type: Simple<br /><br />-- Extra doc files to be distributed with the package<span class="token punctuation">,</span> such as a CHANGELOG or a README<span class="token punctuation">.</span><br />extra-doc-files: CHANGELOG<span class="token punctuation">.</span>md<br /><br />-- Extra source files to be distributed with the package<span class="token punctuation">,</span> such as examples<span class="token punctuation">,</span> or a tutorial module<span class="token punctuation">.</span><br />-- extra-source-files:<br /><br />common warnings<br /> ghc-options: -Wall<br /><br />executable web<br /> -- Import common warning flags<span class="token punctuation">.</span><br /> import: warnings<br /><br /> -- <span class="token punctuation">.</span>hs or <span class="token punctuation">.</span>lhs file containing the Main module<span class="token punctuation">.</span><br /> main-is: Main<span class="token punctuation">.</span>hs<br /><br /> -- Modules included in this executable<span class="token punctuation">,</span> other than Main<span class="token punctuation">.</span><br /> -- other-modules:<br /><br /> -- LANGUAGE extensions used by modules in this package<span class="token punctuation">.</span><br /> -- other-extensions:<br /><br /> -- Other library packages from which modules are imported<span class="token punctuation">.</span><br /> build-depends: base ^><span class="token punctuation">=</span><span class="token number">4.15</span><span class="token punctuation">.</span><span class="token number">1.0</span><br /><br /> -- Directories containing source files<span class="token punctuation">.</span><br /> hs-source-dirs: app<br /><br /> -- Base language which the package is written in<span class="token punctuation">.</span><br /> default-language: Haskell2010</code></pre>
<p>To ensure your project is created properly, run:</p>
<pre class="language-bash"><code class="language-bash">cabal update <span class="token operator">&&</span> cabal run</code></pre>
<p>You should see "Hello Haskell" printed in addition to some other output.</p>
<h3>Adding Scotty to your Haskell project</h3>
<p>To make Scotty available for import, add it as a build dependency in your
<em>.cabal</em> file.</p>
<pre class="language-toml"><code class="language-toml">-- Other library packages from which modules are imported<span class="token punctuation">.</span><br />build-depends: base ^><span class="token punctuation">=</span><span class="token number">4.15</span><span class="token punctuation">.</span><span class="token number">1.0</span><span class="token punctuation">,</span> scotty</code></pre>
<p>You can test that your <em>.cabal</em> file is correct by going ahead and importing
<code>Web.Scotty</code> into your <em>app/Main.hs</em> file and running again with the command
above.</p>
<p><em>app/Main.hs</em></p>
<pre class="language-haskell"><code class="language-haskell"><span class="token keyword">module</span> <span class="token constant">Main</span> <span class="token keyword">where</span><br /><br /><span class="token import-statement"><span class="token keyword">import</span> <span class="token keyword">qualified</span> Web<span class="token punctuation">.</span>Scotty</span><br /><br /><span class="token hvariable">main</span> <span class="token operator">::</span> <span class="token constant">IO</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><br /><span class="token hvariable">main</span> <span class="token operator">=</span> <span class="token builtin">putStrLn</span> <span class="token string">"Hello, Haskell!"</span></code></pre>
<p>If your app compiles and runs
successfully, you know you're ready to start writing code
with Scotty.</p>
<h3>Writing a "Hello, world" action in Scotty</h3>
<p>In order to use Scotty (with reasonable developer ergonomics), you'll want
the <code>OverloadedStrings</code> language extension. This allows string literals to be
used as other string-like objects when appropriate.</p>
<p>Once you have <code>OverloadedStrings</code>, you can write a Scotty <code>ActionM</code> action
for greeting the world.</p>
<pre class="language-haskell"><code class="language-haskell"><span class="token comment">{-# LANGUAGE OverloadedStrings #-}</span><br /><br /><span class="token keyword">module</span> <span class="token constant">Main</span> <span class="token keyword">where</span><br /><br /><span class="token import-statement"><span class="token keyword">import</span> <span class="token keyword">qualified</span> Web<span class="token punctuation">.</span>Scotty</span><br /><br /><span class="token hvariable">homeAction</span> <span class="token operator">::</span> <span class="token constant">Web<span class="token punctuation">.</span>Scotty<span class="token punctuation">.</span>ActionM</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><br /><span class="token hvariable">homeAction</span> <span class="token operator">=</span> <span class="token hvariable">Web<span class="token punctuation">.</span>Scotty<span class="token punctuation">.</span>html</span> <span class="token string">"<h1>Hello, world</h1>"</span><br /></code></pre>
<p><code>ActionM</code>s are monads, which means technically we can write our action as a
<code>do</code> block like this:</p>
<p><em>app/Main.hs</em></p>
<pre class="language-haskell"><code class="language-haskell"><span class="token comment">{-# LANGUAGE OverloadedStrings #-}</span><br /><br /><span class="token keyword">module</span> <span class="token constant">Main</span> <span class="token keyword">where</span><br /><br /><span class="token import-statement"><span class="token keyword">import</span> <span class="token keyword">qualified</span> Web<span class="token punctuation">.</span>Scotty</span><br /><br /><span class="token hvariable">homeAction</span> <span class="token operator">::</span> <span class="token constant">Web<span class="token punctuation">.</span>Scotty<span class="token punctuation">.</span>ActionM</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><br /><span class="token hvariable">homeAction</span> <span class="token operator">=</span> <span class="token keyword">do</span><br /> <span class="token hvariable">Web<span class="token punctuation">.</span>Scotty<span class="token punctuation">.</span>html</span> <span class="token string">"<h1>Hello, world</h1>"</span></code></pre>
<p>...although this may look ugly for trivial examples like the one shown here.</p>
<h3>Specifying a port and running a server in Scotty</h3>
<p>Finally, we're ready to create our server. This example uses port 3000.</p>
<pre class="language-haskell"><code class="language-haskell"><span class="token comment">{-# LANGUAGE OverloadedStrings #-}</span><br /><br /><span class="token keyword">module</span> <span class="token constant">Main</span> <span class="token keyword">where</span><br /><br /><span class="token import-statement"><span class="token keyword">import</span> <span class="token keyword">qualified</span> Web<span class="token punctuation">.</span>Scotty</span><br /><br /><span class="token hvariable">homeAction</span> <span class="token operator">::</span> <span class="token constant">Web<span class="token punctuation">.</span>Scotty<span class="token punctuation">.</span>ActionM</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><br /><span class="token hvariable">homeAction</span> <span class="token operator">=</span> <span class="token keyword">do</span><br /> <span class="token hvariable">Web<span class="token punctuation">.</span>Scotty<span class="token punctuation">.</span>html</span> <span class="token string">"<h1>Hello, world</h1>"</span><br /><br /><span class="token hvariable">main</span> <span class="token operator">::</span> <span class="token constant">IO</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><br /><span class="token hvariable">main</span> <span class="token operator">=</span> <span class="token keyword">do</span><br /> <span class="token hvariable">Web<span class="token punctuation">.</span>Scotty<span class="token punctuation">.</span>scotty</span> <span class="token number">3000</span> <span class="token operator">$</span> <span class="token keyword">do</span><br /> <span class="token hvariable">Web<span class="token punctuation">.</span>Scotty<span class="token punctuation">.</span>get</span> <span class="token string">"/"</span> <span class="token operator">$</span> <span class="token keyword">do</span><br /> <span class="token hvariable">homeAction</span></code></pre>
<p>When you run this with <code>cabal run</code>, you should be able to see your server
running at <code>http://localhost:3000/</code>.</p>
<h3>In conclusion</h3>
<p>Whether you use Nix or not, I hope this has been helpful to people getting
started with Scotty.</p>
Setting and Reading Session Cookies in Rust with Actix Web2023-07-17T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/setting-and-reading-session-cookies-in-rust-with-actix-web/<p>In case it's not readily apparent, I've been having some trouble picking a
backend stack recently, but, at the risk of jinxing it, I really think Rust is
the backend for me, and Actix Web is a blindingly fast MVC framework for Rust
with an elegant and functional middleware system. As with all web apps, my
first challenge is persisting and managing state between requests, so let's
get into user session management in Actix Web.</p>
<h3>Starting with Hello World in Rust with Actix Web</h3>
<p>For completeness, I'm going to start with <code>cargo new</code>. Feel free to skip
ahead.</p>
<p>You can start with <code>cargo new actix-kata</code>.</p>
<p>If you enter your directory, you should find a "Hello, world" program. If you
<code>cargo run</code> it, you should get "Hello, world" printed to the screen.</p>
<p>To add Actix Web to your project, run <code>cargo add actix-web</code>. If you <code>cargo build</code> or <code>cargo run</code> again, it should install Actix Web and all its
dependencies.</p>
<p>Then replace <em>src/main.rs</em> with the following:</p>
<pre class="language-rust"><code class="language-rust"><span class="token keyword">use</span> actix_web<span class="token punctuation">;</span><br /><br /><span class="token attribute attr-name">#[actix_web::get(<span class="token string">"/"</span>)]</span><br /><span class="token keyword">async</span> <span class="token keyword">fn</span> <span class="token function-definition function">index</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token keyword">impl</span> <span class="token namespace">actix_web<span class="token punctuation">::</span></span><span class="token class-name">Responder</span> <span class="token punctuation">{</span><br /> <span class="token namespace">actix_web<span class="token punctuation">::</span></span><span class="token class-name">HttpResponse</span><span class="token punctuation">::</span><span class="token class-name">Ok</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">body</span><span class="token punctuation">(</span><span class="token string">"Hello, world"</span><span class="token punctuation">)</span><br /><span class="token punctuation">}</span><br /><br /><span class="token attribute attr-name">#[actix_web::main]</span><br /><span class="token keyword">async</span> <span class="token keyword">fn</span> <span class="token function-definition function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token namespace">std<span class="token punctuation">::</span>io<span class="token punctuation">::</span></span><span class="token class-name">Result</span><span class="token operator"><</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">></span> <span class="token punctuation">{</span><br /> <span class="token namespace">actix_web<span class="token punctuation">::</span></span><span class="token class-name">HttpServer</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span> <span class="token closure-params"><span class="token closure-punctuation punctuation">|</span><span class="token closure-punctuation punctuation">|</span></span> <span class="token punctuation">{</span><br /> <span class="token namespace">actix_web<span class="token punctuation">::</span></span><span class="token class-name">App</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">service</span><span class="token punctuation">(</span>index<span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token string">"127.0.0.1"</span><span class="token punctuation">,</span> <span class="token number">3000</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token operator">?</span><br /> <span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token keyword">await</span><br /><span class="token punctuation">}</span></code></pre>
<h3>Getting started with <code>actix-session</code></h3>
<p><code>actix-session</code> is it's own Rust crate, separate from <code>actix-web</code>. It has a
feature flag for cookie sessions. To use it,
install it with <code>cargo add actix-session --features cookie-session</code>.</p>
<p>Our goal will be to build out a <code>SessionMiddleware<CookieSessionStore></code>. We can
do that with a
<a href="https://docs.rs/actix-session/latest/actix_session/config/struct.SessionMiddlewareBuilder.html"><code>SessionMiddlewareBuilder</code></a>.
To put a fine point on it, let's break it out into its own function.</p>
<pre class="language-rust"><code class="language-rust"><span class="token keyword">use</span> <span class="token namespace">actix_session<span class="token punctuation">::</span></span><span class="token punctuation">{</span> <span class="token class-name">SessionMiddleware</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">use</span> <span class="token namespace">actix_session<span class="token punctuation">::</span>storage<span class="token punctuation">::</span></span><span class="token punctuation">{</span> <span class="token class-name">CookieSessionStore</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">use</span> actix_web<span class="token punctuation">;</span><br /><span class="token keyword">use</span> <span class="token namespace">actix_web<span class="token punctuation">::</span>cookie<span class="token punctuation">::</span></span><span class="token punctuation">{</span> <span class="token class-name">Key</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token attribute attr-name">#[actix_web::get(<span class="token string">"/"</span>)]</span><br /><span class="token keyword">async</span> <span class="token keyword">fn</span> <span class="token function-definition function">index</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token keyword">impl</span> <span class="token namespace">actix_web<span class="token punctuation">::</span></span><span class="token class-name">Responder</span> <span class="token punctuation">{</span><br /> <span class="token namespace">actix_web<span class="token punctuation">::</span></span><span class="token class-name">HttpResponse</span><span class="token punctuation">::</span><span class="token class-name">Ok</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">body</span><span class="token punctuation">(</span><span class="token string">"Hello, world"</span><span class="token punctuation">)</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">fn</span> <span class="token function-definition function">session_middleware</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token class-name">SessionMiddleware</span><span class="token operator"><</span><span class="token class-name">CookieSessionStore</span><span class="token operator">></span> <span class="token punctuation">{</span><br /> <span class="token class-name">SessionMiddleware</span><span class="token punctuation">::</span><span class="token function">builder</span><span class="token punctuation">(</span><br /> <span class="token class-name">CookieSessionStore</span><span class="token punctuation">::</span><span class="token function">default</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">Key</span><span class="token punctuation">::</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token operator">&</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">;</span> <span class="token number">64</span><span class="token punctuation">]</span><span class="token punctuation">)</span><br /> <span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /><span class="token punctuation">}</span><br /><br /><span class="token attribute attr-name">#[actix_web::main]</span><br /><span class="token keyword">async</span> <span class="token keyword">fn</span> <span class="token function-definition function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token namespace">std<span class="token punctuation">::</span>io<span class="token punctuation">::</span></span><span class="token class-name">Result</span><span class="token operator"><</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">></span> <span class="token punctuation">{</span><br /> <span class="token namespace">actix_web<span class="token punctuation">::</span></span><span class="token class-name">HttpServer</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span> <span class="token closure-params"><span class="token closure-punctuation punctuation">|</span><span class="token closure-punctuation punctuation">|</span></span> <span class="token punctuation">{</span><br /> <span class="token namespace">actix_web<span class="token punctuation">::</span></span><span class="token class-name">App</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">wrap</span><span class="token punctuation">(</span><span class="token function">session_middleware</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">//register session middleware</span><br /> <span class="token punctuation">.</span><span class="token function">service</span><span class="token punctuation">(</span>index<span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token string">"127.0.0.1"</span><span class="token punctuation">,</span> <span class="token number">3000</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token operator">?</span><br /> <span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token keyword">await</span><br /><span class="token punctuation">}</span><br /></code></pre>
<p>This accepts all defaults and registers the session middleware in the
application.</p>
<h3>Building out a cookie session in Actix Web</h3>
<p>Now let's fill in some details using the provided builder methods. (Some of
these are set by default, but in the interest of a good demonstration, let's
make as much explicit as we can.)</p>
<p>Importantly, many possible configuration items are matters of security both
for the application and for the user, so it is a very good idea to familiarize
oneself with each item in the documentation.</p>
<pre class="language-rust"><code class="language-rust"><span class="token keyword">use</span> <span class="token namespace">actix_session<span class="token punctuation">::</span></span><span class="token punctuation">{</span> <span class="token class-name">SessionMiddleware</span><span class="token punctuation">,</span> <span class="token class-name">Session</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">use</span> <span class="token namespace">actix_session<span class="token punctuation">::</span>config<span class="token punctuation">::</span></span><span class="token punctuation">{</span> <span class="token class-name">BrowserSession</span><span class="token punctuation">,</span> <span class="token class-name">CookieContentSecurity</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">use</span> <span class="token namespace">actix_session<span class="token punctuation">::</span>storage<span class="token punctuation">::</span></span><span class="token punctuation">{</span> <span class="token class-name">CookieSessionStore</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">use</span> actix_web<span class="token punctuation">;</span><br /><span class="token keyword">use</span> <span class="token namespace">actix_web<span class="token punctuation">::</span>cookie<span class="token punctuation">::</span></span><span class="token punctuation">{</span> <span class="token class-name">Key</span><span class="token punctuation">,</span> <span class="token class-name">SameSite</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token attribute attr-name">#[actix_web::get(<span class="token string">"/"</span>)]</span><br /><span class="token keyword">async</span> <span class="token keyword">fn</span> <span class="token function-definition function">index</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token keyword">impl</span> <span class="token namespace">actix_web<span class="token punctuation">::</span></span><span class="token class-name">Responder</span> <span class="token punctuation">{</span><br /> <span class="token namespace">actix_web<span class="token punctuation">::</span></span><span class="token class-name">HttpResponse</span><span class="token punctuation">::</span><span class="token class-name">Ok</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">body</span><span class="token punctuation">(</span><span class="token string">"Hello, world"</span><span class="token punctuation">)</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">fn</span> <span class="token function-definition function">session_middleware</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token class-name">SessionMiddleware</span><span class="token operator"><</span><span class="token class-name">CookieSessionStore</span><span class="token operator">></span> <span class="token punctuation">{</span><br /> <span class="token class-name">SessionMiddleware</span><span class="token punctuation">::</span><span class="token function">builder</span><span class="token punctuation">(</span><br /> <span class="token class-name">CookieSessionStore</span><span class="token punctuation">::</span><span class="token function">default</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">Key</span><span class="token punctuation">::</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token operator">&</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">;</span> <span class="token number">64</span><span class="token punctuation">]</span><span class="token punctuation">)</span><br /> <span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">cookie_name</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">::</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token string">"my-kata-cookie"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// arbitrary name</span><br /> <span class="token punctuation">.</span><span class="token function">cookie_secure</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token comment">// https only</span><br /> <span class="token punctuation">.</span><span class="token function">session_lifecycle</span><span class="token punctuation">(</span><span class="token class-name">BrowserSession</span><span class="token punctuation">::</span><span class="token function">default</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// expire at end of session</span><br /> <span class="token punctuation">.</span><span class="token function">cookie_same_site</span><span class="token punctuation">(</span><span class="token class-name">SameSite</span><span class="token punctuation">::</span><span class="token class-name">Strict</span><span class="token punctuation">)</span> <br /> <span class="token punctuation">.</span><span class="token function">cookie_content_security</span><span class="token punctuation">(</span><span class="token class-name">CookieContentSecurity</span><span class="token punctuation">::</span><span class="token class-name">Private</span><span class="token punctuation">)</span> <span class="token comment">// encrypt</span><br /> <span class="token punctuation">.</span><span class="token function">cookie_http_only</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token comment">// disallow scripts from reading</span><br /> <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /><span class="token punctuation">}</span><br /><br /><span class="token attribute attr-name">#[actix_web::main]</span><br /><span class="token keyword">async</span> <span class="token keyword">fn</span> <span class="token function-definition function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token namespace">std<span class="token punctuation">::</span>io<span class="token punctuation">::</span></span><span class="token class-name">Result</span><span class="token operator"><</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">></span> <span class="token punctuation">{</span><br /> <span class="token namespace">actix_web<span class="token punctuation">::</span></span><span class="token class-name">HttpServer</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span> <span class="token closure-params"><span class="token closure-punctuation punctuation">|</span><span class="token closure-punctuation punctuation">|</span></span> <span class="token punctuation">{</span><br /> <span class="token namespace">actix_web<span class="token punctuation">::</span></span><span class="token class-name">App</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">wrap</span><span class="token punctuation">(</span><span class="token function">session_middleware</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">service</span><span class="token punctuation">(</span>index<span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token string">"127.0.0.1"</span><span class="token punctuation">,</span> <span class="token number">3000</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token operator">?</span><br /> <span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token keyword">await</span><br /><span class="token punctuation">}</span></code></pre>
<h3>Reading from a cookie in Actix Web</h3>
<p>Reading from and writing to cookies is very simple with the <code>Session</code>
extractor. Just include a parameter typed as <code>Session</code>, and the framework
will take care of the rest.</p>
<pre class="language-rust"><code class="language-rust"><span class="token keyword">use</span> <span class="token namespace">actix_session<span class="token punctuation">::</span></span><span class="token punctuation">{</span> <span class="token class-name">SessionMiddleware</span><span class="token punctuation">,</span> <span class="token class-name">Session</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">use</span> <span class="token namespace">actix_session<span class="token punctuation">::</span>config<span class="token punctuation">::</span></span><span class="token punctuation">{</span> <span class="token class-name">BrowserSession</span><span class="token punctuation">,</span> <span class="token class-name">CookieContentSecurity</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">use</span> <span class="token namespace">actix_session<span class="token punctuation">::</span>storage<span class="token punctuation">::</span></span><span class="token punctuation">{</span> <span class="token class-name">CookieSessionStore</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">use</span> <span class="token namespace">actix_web<span class="token punctuation">::</span></span><span class="token punctuation">{</span> <span class="token class-name">HttpResponse</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">use</span> <span class="token namespace">actix_web<span class="token punctuation">::</span>cookie<span class="token punctuation">::</span></span><span class="token punctuation">{</span> <span class="token class-name">Key</span><span class="token punctuation">,</span> <span class="token class-name">SameSite</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">use</span> <span class="token namespace">actix_web<span class="token punctuation">::</span>web<span class="token punctuation">::</span></span><span class="token punctuation">{</span> <span class="token class-name">Json</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token attribute attr-name">#[actix_web::get(<span class="token string">"get_session"</span>)]</span><br /><span class="token keyword">async</span> <span class="token keyword">fn</span> <span class="token function-definition function">get_session</span><span class="token punctuation">(</span>session<span class="token punctuation">:</span> <span class="token class-name">Session</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token keyword">impl</span> <span class="token namespace">actix_web<span class="token punctuation">::</span></span><span class="token class-name">Responder</span> <span class="token punctuation">{</span><br /> <span class="token keyword">match</span> session<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">::</span><span class="token operator"><</span><span class="token class-name">String</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token string">"message"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token class-name">Ok</span><span class="token punctuation">(</span>message_option<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">match</span> message_option <span class="token punctuation">{</span><br /> <span class="token class-name">Some</span><span class="token punctuation">(</span>message<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token class-name">HttpResponse</span><span class="token punctuation">::</span><span class="token class-name">Ok</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">body</span><span class="token punctuation">(</span>message<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token class-name">None</span> <span class="token operator">=></span> <span class="token class-name">HttpResponse</span><span class="token punctuation">::</span><span class="token class-name">NotFound</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">body</span><span class="token punctuation">(</span><span class="token string">"Not set."</span><span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /> <span class="token class-name">Err</span><span class="token punctuation">(</span>_<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token class-name">HttpResponse</span><span class="token punctuation">::</span><span class="token class-name">InternalServerError</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">body</span><span class="token punctuation">(</span><span class="token string">"Session error."</span><span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token attribute attr-name">#[actix_web::get(<span class="token string">"/"</span>)]</span><br /><span class="token keyword">async</span> <span class="token keyword">fn</span> <span class="token function-definition function">index</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token keyword">impl</span> <span class="token namespace">actix_web<span class="token punctuation">::</span></span><span class="token class-name">Responder</span> <span class="token punctuation">{</span><br /> <span class="token class-name">HttpResponse</span><span class="token punctuation">::</span><span class="token class-name">Ok</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">body</span><span class="token punctuation">(</span><span class="token string">"Hello, world"</span><span class="token punctuation">)</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">fn</span> <span class="token function-definition function">session_middleware</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token class-name">SessionMiddleware</span><span class="token operator"><</span><span class="token class-name">CookieSessionStore</span><span class="token operator">></span> <span class="token punctuation">{</span><br /> <span class="token class-name">SessionMiddleware</span><span class="token punctuation">::</span><span class="token function">builder</span><span class="token punctuation">(</span><br /> <span class="token class-name">CookieSessionStore</span><span class="token punctuation">::</span><span class="token function">default</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">Key</span><span class="token punctuation">::</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token operator">&</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">;</span> <span class="token number">64</span><span class="token punctuation">]</span><span class="token punctuation">)</span><br /> <span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">cookie_name</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">::</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token string">"my-kata-cookie"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">cookie_secure</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">session_lifecycle</span><span class="token punctuation">(</span><span class="token class-name">BrowserSession</span><span class="token punctuation">::</span><span class="token function">default</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">cookie_same_site</span><span class="token punctuation">(</span><span class="token class-name">SameSite</span><span class="token punctuation">::</span><span class="token class-name">Strict</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">cookie_content_security</span><span class="token punctuation">(</span><span class="token class-name">CookieContentSecurity</span><span class="token punctuation">::</span><span class="token class-name">Private</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">cookie_http_only</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /><span class="token punctuation">}</span><br /><br /><span class="token attribute attr-name">#[actix_web::main]</span><br /><span class="token keyword">async</span> <span class="token keyword">fn</span> <span class="token function-definition function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token namespace">std<span class="token punctuation">::</span>io<span class="token punctuation">::</span></span><span class="token class-name">Result</span><span class="token operator"><</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">></span> <span class="token punctuation">{</span><br /> <span class="token namespace">actix_web<span class="token punctuation">::</span></span><span class="token class-name">HttpServer</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span> <span class="token closure-params"><span class="token closure-punctuation punctuation">|</span><span class="token closure-punctuation punctuation">|</span></span> <span class="token punctuation">{</span><br /> <span class="token namespace">actix_web<span class="token punctuation">::</span></span><span class="token class-name">App</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">wrap</span><span class="token punctuation">(</span><span class="token function">session_middleware</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">service</span><span class="token punctuation">(</span>index<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">service</span><span class="token punctuation">(</span>get_session<span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token string">"127.0.0.1"</span><span class="token punctuation">,</span> <span class="token number">3000</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token operator">?</span><br /> <span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token keyword">await</span><br /><span class="token punctuation">}</span></code></pre>
<h3>Setting the session cookie in Actix Web</h3>
<p>We can use <code>serde</code> to create an endpoint to set the session. We'll also need
the <code>derive</code> feature.</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">cargo</span> <span class="token function">add</span> serde <span class="token parameter variable">--features</span> derive</code></pre>
<p>At this point, your <em>Cargo.toml</em> should look like this:</p>
<pre class="language-toml"><code class="language-toml"><span class="token punctuation">[</span><span class="token table class-name">package</span><span class="token punctuation">]</span><br /><span class="token key property">name</span> <span class="token punctuation">=</span> <span class="token string">"actix-kata"</span><br /><span class="token key property">version</span> <span class="token punctuation">=</span> <span class="token string">"0.1.0"</span><br /><span class="token key property">edition</span> <span class="token punctuation">=</span> <span class="token string">"2021"</span><br /><br /><span class="token comment"># See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html</span><br /><br /><span class="token punctuation">[</span><span class="token table class-name">dependencies</span><span class="token punctuation">]</span><br /><span class="token key property">actix-session</span> <span class="token punctuation">=</span> <span class="token punctuation">{</span> <span class="token key property">version</span> <span class="token punctuation">=</span> <span class="token string">"0.7.2"</span><span class="token punctuation">,</span> <span class="token key property">features</span> <span class="token punctuation">=</span> <span class="token punctuation">[</span><span class="token string">"cookie-session"</span><span class="token punctuation">]</span> <span class="token punctuation">}</span><br /><span class="token key property">actix-web</span> <span class="token punctuation">=</span> <span class="token string">"4.3.1"</span><br /><span class="token key property">serde</span> <span class="token punctuation">=</span> <span class="token punctuation">{</span> <span class="token key property">version</span> <span class="token punctuation">=</span> <span class="token string">"1.0.164"</span><span class="token punctuation">,</span> <span class="token key property">features</span> <span class="token punctuation">=</span> <span class="token punctuation">[</span><span class="token string">"derive"</span><span class="token punctuation">]</span> <span class="token punctuation">}</span></code></pre>
<p>With <code>serde</code>, we can make a deserializable <code>struct</code> to model our input and then
extract it simply by putting it in the signature of our method. The framework
will handle the rest.</p>
<p>Then we can insert the message using <code>session.insert</code>.</p>
<p><em><a href="http://main.rs/">main.rs</a></em></p>
<pre class="language-rust"><code class="language-rust"><span class="token keyword">use</span> <span class="token namespace">actix_session<span class="token punctuation">::</span></span><span class="token punctuation">{</span> <span class="token class-name">SessionMiddleware</span><span class="token punctuation">,</span> <span class="token class-name">Session</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">use</span> <span class="token namespace">actix_session<span class="token punctuation">::</span>config<span class="token punctuation">::</span></span><span class="token punctuation">{</span> <span class="token class-name">BrowserSession</span><span class="token punctuation">,</span> <span class="token class-name">CookieContentSecurity</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">use</span> <span class="token namespace">actix_session<span class="token punctuation">::</span>storage<span class="token punctuation">::</span></span><span class="token punctuation">{</span> <span class="token class-name">CookieSessionStore</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">use</span> <span class="token namespace">actix_web<span class="token punctuation">::</span></span><span class="token punctuation">{</span> <span class="token class-name">HttpResponse</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">use</span> <span class="token namespace">actix_web<span class="token punctuation">::</span>cookie<span class="token punctuation">::</span></span><span class="token punctuation">{</span> <span class="token class-name">Key</span><span class="token punctuation">,</span> <span class="token class-name">SameSite</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">use</span> <span class="token namespace">actix_web<span class="token punctuation">::</span>web<span class="token punctuation">::</span></span><span class="token punctuation">{</span> <span class="token class-name">Json</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">use</span> serde<span class="token punctuation">;</span><br /><br /><span class="token attribute attr-name">#[derive(serde::Deserialize)]</span><br /><span class="token keyword">struct</span> <span class="token type-definition class-name">CookieModel</span> <span class="token punctuation">{</span><br /> message<span class="token punctuation">:</span> <span class="token class-name">String</span><br /><span class="token punctuation">}</span><br /><br /><span class="token attribute attr-name">#[actix_web::get(<span class="token string">"get_session"</span>)]</span><br /><span class="token keyword">async</span> <span class="token keyword">fn</span> <span class="token function-definition function">get_session</span><span class="token punctuation">(</span>session<span class="token punctuation">:</span> <span class="token class-name">Session</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token keyword">impl</span> <span class="token namespace">actix_web<span class="token punctuation">::</span></span><span class="token class-name">Responder</span> <span class="token punctuation">{</span><br /> <span class="token keyword">match</span> session<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">::</span><span class="token operator"><</span><span class="token class-name">String</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token string">"message"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token class-name">Ok</span><span class="token punctuation">(</span>message_option<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">match</span> message_option <span class="token punctuation">{</span><br /> <span class="token class-name">Some</span><span class="token punctuation">(</span>message<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token class-name">HttpResponse</span><span class="token punctuation">::</span><span class="token class-name">Ok</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">body</span><span class="token punctuation">(</span>message<span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token class-name">None</span> <span class="token operator">=></span> <span class="token class-name">HttpResponse</span><span class="token punctuation">::</span><span class="token class-name">NotFound</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">body</span><span class="token punctuation">(</span><span class="token string">"Not set."</span><span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /> <span class="token class-name">Err</span><span class="token punctuation">(</span>_<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token class-name">HttpResponse</span><span class="token punctuation">::</span><span class="token class-name">InternalServerError</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">body</span><span class="token punctuation">(</span><span class="token string">"Error."</span><span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token attribute attr-name">#[actix_web::get(<span class="token string">"/"</span>)]</span><br /><span class="token keyword">async</span> <span class="token keyword">fn</span> <span class="token function-definition function">index</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token keyword">impl</span> <span class="token namespace">actix_web<span class="token punctuation">::</span></span><span class="token class-name">Responder</span> <span class="token punctuation">{</span><br /> <span class="token class-name">HttpResponse</span><span class="token punctuation">::</span><span class="token class-name">Ok</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">body</span><span class="token punctuation">(</span><span class="token string">"Hello, world"</span><span class="token punctuation">)</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">async</span> <span class="token keyword">fn</span> <span class="token function-definition function">set_session</span><span class="token punctuation">(</span>session<span class="token punctuation">:</span> <span class="token class-name">Session</span><span class="token punctuation">,</span> model<span class="token punctuation">:</span> <span class="token class-name">Json</span><span class="token operator"><</span><span class="token class-name">CookieModel</span><span class="token operator">></span><span class="token punctuation">)</span><br /> <span class="token punctuation">-></span> <span class="token keyword">impl</span> <span class="token namespace">actix_web<span class="token punctuation">::</span></span><span class="token class-name">Responder</span><br /><span class="token punctuation">{</span><br /> <span class="token keyword">match</span> session<span class="token punctuation">.</span><span class="token function">insert</span><span class="token punctuation">(</span><span class="token string">"message"</span><span class="token punctuation">,</span> model<span class="token punctuation">.</span>message<span class="token punctuation">.</span><span class="token function">clone</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token class-name">Ok</span><span class="token punctuation">(</span>_<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token class-name">HttpResponse</span><span class="token punctuation">::</span><span class="token class-name">Created</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">body</span><span class="token punctuation">(</span><span class="token string">"Created."</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br /> <span class="token class-name">Err</span><span class="token punctuation">(</span>_<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token class-name">HttpResponse</span><span class="token punctuation">::</span><span class="token class-name">InternalServerError</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">body</span><span class="token punctuation">(</span><span class="token string">"Error."</span><span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">fn</span> <span class="token function-definition function">session_middleware</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token class-name">SessionMiddleware</span><span class="token operator"><</span><span class="token class-name">CookieSessionStore</span><span class="token operator">></span> <span class="token punctuation">{</span><br /> <span class="token class-name">SessionMiddleware</span><span class="token punctuation">::</span><span class="token function">builder</span><span class="token punctuation">(</span><br /> <span class="token class-name">CookieSessionStore</span><span class="token punctuation">::</span><span class="token function">default</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">Key</span><span class="token punctuation">::</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token operator">&</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">;</span> <span class="token number">64</span><span class="token punctuation">]</span><span class="token punctuation">)</span><br /> <span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">cookie_name</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">::</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token string">"my-kata-cookie"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">cookie_secure</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">session_lifecycle</span><span class="token punctuation">(</span><span class="token class-name">BrowserSession</span><span class="token punctuation">::</span><span class="token function">default</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">cookie_same_site</span><span class="token punctuation">(</span><span class="token class-name">SameSite</span><span class="token punctuation">::</span><span class="token class-name">Strict</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">cookie_content_security</span><span class="token punctuation">(</span><span class="token class-name">CookieContentSecurity</span><span class="token punctuation">::</span><span class="token class-name">Private</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">cookie_http_only</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /><span class="token punctuation">}</span><br /><br /><span class="token attribute attr-name">#[actix_web::main]</span><br /><span class="token keyword">async</span> <span class="token keyword">fn</span> <span class="token function-definition function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token namespace">std<span class="token punctuation">::</span>io<span class="token punctuation">::</span></span><span class="token class-name">Result</span><span class="token operator"><</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">></span> <span class="token punctuation">{</span><br /> <span class="token namespace">actix_web<span class="token punctuation">::</span></span><span class="token class-name">HttpServer</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span> <span class="token closure-params"><span class="token closure-punctuation punctuation">|</span><span class="token closure-punctuation punctuation">|</span></span> <span class="token punctuation">{</span><br /> <span class="token namespace">actix_web<span class="token punctuation">::</span></span><span class="token class-name">App</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">wrap</span><span class="token punctuation">(</span><span class="token function">session_middleware</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">service</span><span class="token punctuation">(</span>index<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">service</span><span class="token punctuation">(</span>get_session<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">route</span><span class="token punctuation">(</span><span class="token string">"set_session"</span><span class="token punctuation">,</span> <span class="token namespace">actix_web<span class="token punctuation">::</span>web<span class="token punctuation">::</span></span><span class="token function">post</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">to</span><span class="token punctuation">(</span>set_session<span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token string">"127.0.0.1"</span><span class="token punctuation">,</span> <span class="token number">3000</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token operator">?</span><br /> <span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token keyword">await</span><br /><span class="token punctuation">}</span></code></pre>
<h3>Wrapping up</h3>
<p>And that's really it. Make sure you set your mime type for the <code>/set_session</code>
request to <code>application/json</code> if you're testing. This has been helpful to me
as I get into Actix Web middleware for the first time, so I hope it has been
helpful to you.</p>
Iced for Desktop Development in Rust2023-09-25T00:00:00Zhttps://webbureaucrat.gitlab.io/articles/iced-for-desktop-development-in-rust/<p>As someone who loves Elm and has recently fallen in love with Rust, I was
delighted to learn there are not just one but <em>two</em> Rust frameworks modeled
on the Elm Architecture. If you're not familiar, the Elm Architecture is
a pain-free way of developing fast, responsive, asynchronous-by-default
front-ends. Let's see it in action by developing a simple "Hello, world"
app.</p>
<p><em>Note: I'm using <code>iced</code> version <code>0.10.0</code>. Iced is currently under rapid
development with frequent breaking changes. It's possible these directions
won't work as expected in a future release.</em> As always, if something breaks,
<a href="https://gitlab.com/webbureaucrat/webbureaucrat.gitlab.io/-/issues">open an issue</a>
or submit a pull request.</p>
<h3>Setup</h3>
<p>Let's start with a fresh application:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">cargo</span> init hello-iced<br /><span class="token builtin class-name">cd</span> hello-iced<br /><span class="token function">cargo</span> <span class="token function">add</span> iced<br /><span class="token function">cargo</span> build</code></pre>
<p>If the build succeeds, we're off to a good start.</p>
<h3>Writing a State Model for Iced in Rust</h3>
<p>The easiest part of the Elm Architecture to understand is the model state, so
let's start there. The model state represents the whole state of the
application--everything you would need to know in order to draw any given
application screen. As you can imagine, state models are often quite large, but
our application will need only one piece of information: whom to greet (by
default: the world). That's easy.</p>
<pre class="language-rust"><code class="language-rust"><span class="token keyword">struct</span> <span class="token type-definition class-name">Hello</span> <span class="token punctuation">{</span><br /> addressee<span class="token punctuation">:</span> <span class="token class-name">String</span><br /><span class="token punctuation">}</span></code></pre>
<h3>Writing a Message Enum for Iced in Rust</h3>
<p>Next, we need our Message. A message is an enum that represents all the ways
that our model can <em>update</em> according to the logic of our application. For
example, you might have a counter that can increment or a counter that can
either increment and decrement, or a counter that can increment, decrement, or
reset, all depending on the logic of your application.</p>
<p>In our case, we only have one field in our model <code>struct</code>, and it can only be
updated through a text input, so let's write an enum to that effect:</p>
<pre class="language-rust"><code class="language-rust"><span class="token attribute attr-name">#[derive(Clone, Debug)]</span><br /><span class="token keyword">enum</span> <span class="token type-definition class-name">HelloMessage</span> <span class="token punctuation">{</span><br /> <span class="token class-name">TextBoxChange</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">)</span><br /><span class="token punctuation">}</span></code></pre>
<p>We can see that our enum takes a <code>String</code>. This represents the new string to
which we will update our model. Also note that it derives <code>Clone</code> and <code>Debug</code>.
This is required by the framework.</p>
<h3>Implementing <code>iced::Application</code></h3>
<p>Now the real work begins: turning our model into an Iced <code>Application</code>. Let's
begin:</p>
<pre class="language-rust"><code class="language-rust"><span class="token keyword">struct</span> <span class="token type-definition class-name">Hello</span> <span class="token punctuation">{</span><br /> addressee<span class="token punctuation">:</span> <span class="token class-name">String</span><span class="token punctuation">,</span><br /><span class="token punctuation">}</span><br /> <br /><span class="token attribute attr-name">#[derive(Clone, Debug)]</span><br /><span class="token keyword">enum</span> <span class="token type-definition class-name">HelloMessage</span> <span class="token punctuation">{</span><br /> <span class="token class-name">TextBoxChange</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">)</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">impl</span> <span class="token namespace">iced<span class="token punctuation">::</span></span><span class="token class-name">Application</span> <span class="token keyword">for</span> <span class="token class-name">Hello</span> <span class="token punctuation">{</span><br /><span class="token punctuation">}</span></code></pre>
<p>We need four types for our application.</p>
<ul>
<li><code>Executor</code> - The only type provided by the framework is <code>Default</code>, which will
suffice for our purposes.</li>
<li><code>Flags</code> - represents any data we want to initialize our application. We have
no flags to pass, so the unit type will suffice.</li>
<li><code>Message</code> - this is the <code>HelloMessage</code> we defined earlier.</li>
<li><code>Theme</code> - the theme of the application.</li>
</ul>
<pre class="language-rust"><code class="language-rust"><span class="token keyword">struct</span> <span class="token type-definition class-name">Hello</span> <span class="token punctuation">{</span><br /> addressee<span class="token punctuation">:</span> <span class="token class-name">String</span><span class="token punctuation">,</span><br /><span class="token punctuation">}</span><br /> <br /><span class="token attribute attr-name">#[derive(Clone, Debug)]</span><br /><span class="token keyword">enum</span> <span class="token type-definition class-name">HelloMessage</span> <span class="token punctuation">{</span><br /> <span class="token class-name">TextBoxChange</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">)</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">impl</span> <span class="token namespace">iced<span class="token punctuation">::</span></span><span class="token class-name">Application</span> <span class="token keyword">for</span> <span class="token class-name">Hello</span> <span class="token punctuation">{</span><br /> <span class="token keyword">type</span> <span class="token type-definition class-name">Executor</span> <span class="token operator">=</span> <span class="token namespace">iced<span class="token punctuation">::</span>executor<span class="token punctuation">::</span></span><span class="token class-name">Default</span><span class="token punctuation">;</span><br /> <span class="token keyword">type</span> <span class="token type-definition class-name">Flags</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">type</span> <span class="token type-definition class-name">Message</span> <span class="token operator">=</span> <span class="token class-name">HelloMessage</span><span class="token punctuation">;</span><br /> <span class="token keyword">type</span> <span class="token type-definition class-name">Theme</span> <span class="token operator">=</span> <span class="token namespace">iced<span class="token punctuation">::</span></span><span class="token class-name">Theme</span><span class="token punctuation">;</span><br /><br /><span class="token punctuation">}</span></code></pre>
<p>Next we need a function that initializes our model. Our model will default to
greeting, well, the world.</p>
<p>Start by adding <code>use iced::Command;</code> at the top. A command is an asynchronous
action that the framework will take on next. We don't need to start any
commands on startup, so we'll initialize with <code>Command::none()</code>.</p>
<pre class="language-rust"><code class="language-rust"><span class="token keyword">impl</span> <span class="token namespace">iced<span class="token punctuation">::</span></span><span class="token class-name">Application</span> <span class="token keyword">for</span> <span class="token class-name">Hello</span> <span class="token punctuation">{</span><br /> <span class="token keyword">type</span> <span class="token type-definition class-name">Executor</span> <span class="token operator">=</span> <span class="token namespace">iced<span class="token punctuation">::</span>executor<span class="token punctuation">::</span></span><span class="token class-name">Default</span><span class="token punctuation">;</span><br /> <span class="token keyword">type</span> <span class="token type-definition class-name">Flags</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">type</span> <span class="token type-definition class-name">Message</span> <span class="token operator">=</span> <span class="token class-name">HelloMessage</span><span class="token punctuation">;</span><br /> <span class="token keyword">type</span> <span class="token type-definition class-name">Theme</span> <span class="token operator">=</span> <span class="token namespace">iced<span class="token punctuation">::</span></span><span class="token class-name">Theme</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">fn</span> <span class="token function-definition function">new</span><span class="token punctuation">(</span>_flags<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token punctuation">(</span><span class="token class-name">Hello</span><span class="token punctuation">,</span> <span class="token class-name">Command</span><span class="token operator"><</span><span class="token keyword">Self</span><span class="token punctuation">::</span><span class="token class-name">Message</span><span class="token operator">></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token punctuation">(</span> <span class="token class-name">Hello</span> <span class="token punctuation">{</span> addressee<span class="token punctuation">:</span> <span class="token class-name">String</span><span class="token punctuation">::</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token string">"world"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token class-name">Command</span><span class="token punctuation">::</span><span class="token function">none</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><br /> <span class="token punctuation">}</span></code></pre>
<p>We also need a function which takes the <code>&self</code> state model and returns a title
for the title bar at the top of the application.</p>
<pre class="language-rust"><code class="language-rust"><span class="token keyword">impl</span> <span class="token namespace">iced<span class="token punctuation">::</span></span><span class="token class-name">Application</span> <span class="token keyword">for</span> <span class="token class-name">Hello</span> <span class="token punctuation">{</span><br /> <span class="token keyword">type</span> <span class="token type-definition class-name">Executor</span> <span class="token operator">=</span> <span class="token namespace">iced<span class="token punctuation">::</span>executor<span class="token punctuation">::</span></span><span class="token class-name">Default</span><span class="token punctuation">;</span><br /> <span class="token keyword">type</span> <span class="token type-definition class-name">Flags</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">type</span> <span class="token type-definition class-name">Message</span> <span class="token operator">=</span> <span class="token class-name">HelloMessage</span><span class="token punctuation">;</span><br /> <span class="token keyword">type</span> <span class="token type-definition class-name">Theme</span> <span class="token operator">=</span> <span class="token namespace">iced<span class="token punctuation">::</span></span><span class="token class-name">Theme</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">fn</span> <span class="token function-definition function">new</span><span class="token punctuation">(</span>_flags<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token punctuation">(</span><span class="token class-name">Hello</span><span class="token punctuation">,</span> <span class="token class-name">Command</span><span class="token operator"><</span><span class="token keyword">Self</span><span class="token punctuation">::</span><span class="token class-name">Message</span><span class="token operator">></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token punctuation">(</span> <span class="token class-name">Hello</span> <span class="token punctuation">{</span> addressee<span class="token punctuation">:</span> <span class="token class-name">String</span><span class="token punctuation">::</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token string">"world"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token class-name">Command</span><span class="token punctuation">::</span><span class="token function">none</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token keyword">fn</span> <span class="token function-definition function">title</span><span class="token punctuation">(</span><span class="token operator">&</span><span class="token keyword">self</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token class-name">String</span> <span class="token punctuation">{</span><br /> <span class="token class-name">String</span><span class="token punctuation">::</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token string">"Greet the World"</span><span class="token punctuation">)</span><br /> <span class="token punctuation">}</span></code></pre>
<p>Now we're getting to the real meat of the application: the <code>update</code> and <code>view</code>
functions.</p>
<p>The <code>update</code> function takes our state model and mutates it based on the given
message. It also gives us the opportunity to start another <code>Command</code> after
mutating our state. For example, a common use case for a submit action message
is to change part of the state to represent that the state is "Loading..." and
then to start a command to fetch some data based on the submission.
We only have one kind of message, so our update function will be very
simple.</p>
<pre class="language-rust"><code class="language-rust"><span class="token keyword">fn</span> <span class="token function-definition function">update</span><span class="token punctuation">(</span><span class="token operator">&</span><span class="token keyword">mut</span> <span class="token keyword">self</span><span class="token punctuation">,</span> message<span class="token punctuation">:</span> <span class="token keyword">Self</span><span class="token punctuation">::</span><span class="token class-name">Message</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token class-name">Command</span><span class="token operator"><</span><span class="token keyword">Self</span><span class="token punctuation">::</span><span class="token class-name">Message</span><span class="token operator">></span> <span class="token punctuation">{</span><br /> <span class="token keyword">match</span> message <span class="token punctuation">{</span><br /> <span class="token class-name">HelloMessage</span><span class="token punctuation">::</span><span class="token class-name">TextBoxChange</span><span class="token punctuation">(</span>string<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">self</span><span class="token punctuation">.</span>addressee <span class="token operator">=</span> string<span class="token punctuation">;</span><br /> <span class="token class-name">Command</span><span class="token punctuation">::</span><span class="token function">none</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Finally, we can write our view. Let's start by <code>use</code>ing some relevant parts.</p>
<pre class="language-rust"><code class="language-rust"><span class="token keyword">use</span> <span class="token namespace">iced<span class="token punctuation">::</span></span><span class="token punctuation">{</span><span class="token class-name">Command</span><span class="token punctuation">,</span> <span class="token class-name">Element</span><span class="token punctuation">,</span> <span class="token class-name">Length</span><span class="token punctuation">,</span> <span class="token class-name">Renderer</span><span class="token punctuation">,</span> <span class="token class-name">Settings</span><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">use</span> <span class="token namespace">iced<span class="token punctuation">::</span>widget<span class="token punctuation">::</span></span><span class="token punctuation">{</span><span class="token class-name">Column</span><span class="token punctuation">,</span> <span class="token class-name">Row</span><span class="token punctuation">,</span> <span class="token class-name">Text</span><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>We build out our view programmatically using the
<code>push</code> method, and then convert the final result to an <code>Element</code> using
<code>into()</code>.</p>
<pre class="language-rust"><code class="language-rust"><span class="token keyword">fn</span> <span class="token function-definition function">view</span><span class="token punctuation">(</span><span class="token operator">&</span><span class="token keyword">self</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token class-name">Element</span><span class="token operator"><</span><span class="token keyword">Self</span><span class="token punctuation">::</span><span class="token class-name">Message</span><span class="token operator">></span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> text <span class="token operator">=</span> <span class="token class-name">Text</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token macro property">format!</span><span class="token punctuation">(</span><span class="token string">"Hello, {0}."</span><span class="token punctuation">,</span> <span class="token keyword">self</span><span class="token punctuation">.</span>addressee<span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">width</span><span class="token punctuation">(</span><span class="token class-name">Length</span><span class="token punctuation">::</span><span class="token class-name">Fill</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">horizontal_alignment</span><span class="token punctuation">(</span><span class="token namespace">iced<span class="token punctuation">::</span>alignment<span class="token punctuation">::</span></span><span class="token class-name">Horizontal</span><span class="token punctuation">::</span><span class="token class-name">Center</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">let</span> row1 <span class="token operator">=</span> <span class="token class-name">Row</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>text<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">let</span> text_input<span class="token punctuation">:</span> <span class="token namespace">iced<span class="token punctuation">::</span>widget<span class="token punctuation">::</span></span><span class="token class-name">TextInput</span><span class="token operator"><</span><span class="token lifetime-annotation symbol">'_</span><span class="token punctuation">,</span> <span class="token class-name">HelloMessage</span><span class="token punctuation">,</span> <span class="token class-name">Renderer</span><span class="token operator">></span> <span class="token operator">=</span><br /> <span class="token namespace">iced<span class="token punctuation">::</span>widget<span class="token punctuation">::</span></span><span class="token function">text_input</span><span class="token punctuation">(</span><span class="token string">"world"</span><span class="token punctuation">,</span> <span class="token keyword">self</span><span class="token punctuation">.</span>addressee<span class="token punctuation">.</span><span class="token function">as_str</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">on_input</span><span class="token punctuation">(</span><span class="token class-name">HelloMessage</span><span class="token punctuation">::</span><span class="token class-name">TextBoxChange</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">let</span> row2 <span class="token operator">=</span> <span class="token class-name">Row</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>text_input<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token class-name">Column</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>row1<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>row2<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">into</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /> <span class="token punctuation">}</span></code></pre>
<h3>Writing a <code>main</code> function to <code>run</code> an Iced <code>Application</code></h3>
<p>Finally, we need to <code>run</code> our application from the <code>main</code> function. Make sure
to <code>use</code> <code>iced::Application</code> to bring the <code>run</code> function into scope.</p>
<pre class="language-rust"><code class="language-rust"><span class="token keyword">use</span> <span class="token namespace">iced<span class="token punctuation">::</span></span><span class="token punctuation">{</span><span class="token class-name">Application</span><span class="token punctuation">,</span> <span class="token class-name">Command</span><span class="token punctuation">,</span> <span class="token class-name">Element</span><span class="token punctuation">,</span> <span class="token class-name">Length</span><span class="token punctuation">,</span> <span class="token class-name">Renderer</span><span class="token punctuation">,</span> <span class="token class-name">Settings</span><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">use</span> <span class="token namespace">iced<span class="token punctuation">::</span>widget<span class="token punctuation">::</span></span><span class="token punctuation">{</span><span class="token class-name">Column</span><span class="token punctuation">,</span> <span class="token class-name">Row</span><span class="token punctuation">,</span> <span class="token class-name">Text</span><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">pub</span> <span class="token keyword">fn</span> <span class="token function-definition function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token namespace">iced<span class="token punctuation">::</span></span><span class="token class-name">Result</span> <span class="token punctuation">{</span><br /> <span class="token class-name">Hello</span><span class="token punctuation">::</span><span class="token function">run</span><span class="token punctuation">(</span><span class="token class-name">Settings</span><span class="token punctuation">::</span><span class="token function">default</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /><span class="token punctuation">}</span></code></pre>
<h3>Wrapping up</h3>
<p>In conclusion, I hope this has been a helpful introduction to the Iced
framework and the Elm Architecture. The architecture has a learning curve, so
I figure the more learning resources out there, the better.</p>
<p>For reference, this is the full source code:</p>
<p><em>src/main.rs</em></p>
<pre class="language-rust"><code class="language-rust"><span class="token keyword">use</span> <span class="token namespace">iced<span class="token punctuation">::</span></span><span class="token punctuation">{</span><span class="token class-name">Application</span><span class="token punctuation">,</span> <span class="token class-name">Command</span><span class="token punctuation">,</span> <span class="token class-name">Element</span><span class="token punctuation">,</span> <span class="token class-name">Length</span><span class="token punctuation">,</span> <span class="token class-name">Renderer</span><span class="token punctuation">,</span> <span class="token class-name">Settings</span><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><span class="token keyword">use</span> <span class="token namespace">iced<span class="token punctuation">::</span>widget<span class="token punctuation">::</span></span><span class="token punctuation">{</span><span class="token class-name">Column</span><span class="token punctuation">,</span> <span class="token class-name">Row</span><span class="token punctuation">,</span> <span class="token class-name">Text</span><span class="token punctuation">}</span><span class="token punctuation">;</span><br /><br /><span class="token keyword">pub</span> <span class="token keyword">fn</span> <span class="token function-definition function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token namespace">iced<span class="token punctuation">::</span></span><span class="token class-name">Result</span> <span class="token punctuation">{</span><br /> <span class="token class-name">Hello</span><span class="token punctuation">::</span><span class="token function">run</span><span class="token punctuation">(</span><span class="token class-name">Settings</span><span class="token punctuation">::</span><span class="token function">default</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">struct</span> <span class="token type-definition class-name">Hello</span> <span class="token punctuation">{</span><br /> addressee<span class="token punctuation">:</span> <span class="token class-name">String</span><span class="token punctuation">,</span><br /><span class="token punctuation">}</span><br /> <br /><span class="token attribute attr-name">#[derive(Clone, Debug)]</span><br /><span class="token keyword">enum</span> <span class="token type-definition class-name">HelloMessage</span> <span class="token punctuation">{</span><br /> <span class="token class-name">TextBoxChange</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">)</span><br /><span class="token punctuation">}</span><br /><br /><span class="token keyword">impl</span> <span class="token namespace">iced<span class="token punctuation">::</span></span><span class="token class-name">Application</span> <span class="token keyword">for</span> <span class="token class-name">Hello</span> <span class="token punctuation">{</span><br /> <span class="token keyword">type</span> <span class="token type-definition class-name">Executor</span> <span class="token operator">=</span> <span class="token namespace">iced<span class="token punctuation">::</span>executor<span class="token punctuation">::</span></span><span class="token class-name">Default</span><span class="token punctuation">;</span><br /> <span class="token keyword">type</span> <span class="token type-definition class-name">Flags</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">type</span> <span class="token type-definition class-name">Message</span> <span class="token operator">=</span> <span class="token class-name">HelloMessage</span><span class="token punctuation">;</span><br /> <span class="token keyword">type</span> <span class="token type-definition class-name">Theme</span> <span class="token operator">=</span> <span class="token namespace">iced<span class="token punctuation">::</span></span><span class="token class-name">Theme</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">fn</span> <span class="token function-definition function">new</span><span class="token punctuation">(</span>_flags<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token punctuation">(</span><span class="token class-name">Hello</span><span class="token punctuation">,</span> <span class="token class-name">Command</span><span class="token operator"><</span><span class="token keyword">Self</span><span class="token punctuation">::</span><span class="token class-name">Message</span><span class="token operator">></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token punctuation">(</span> <span class="token class-name">Hello</span> <span class="token punctuation">{</span> addressee<span class="token punctuation">:</span> <span class="token class-name">String</span><span class="token punctuation">::</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token string">"world"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token class-name">Command</span><span class="token punctuation">::</span><span class="token function">none</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token keyword">fn</span> <span class="token function-definition function">title</span><span class="token punctuation">(</span><span class="token operator">&</span><span class="token keyword">self</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token class-name">String</span> <span class="token punctuation">{</span><br /> <span class="token class-name">String</span><span class="token punctuation">::</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token string">"Greet the World"</span><span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /><br /> <span class="token keyword">fn</span> <span class="token function-definition function">update</span><span class="token punctuation">(</span><span class="token operator">&</span><span class="token keyword">mut</span> <span class="token keyword">self</span><span class="token punctuation">,</span> message<span class="token punctuation">:</span> <span class="token keyword">Self</span><span class="token punctuation">::</span><span class="token class-name">Message</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token class-name">Command</span><span class="token operator"><</span><span class="token keyword">Self</span><span class="token punctuation">::</span><span class="token class-name">Message</span><span class="token operator">></span> <span class="token punctuation">{</span><br /> <span class="token keyword">match</span> message <span class="token punctuation">{</span><br /> <span class="token class-name">HelloMessage</span><span class="token punctuation">::</span><span class="token class-name">TextBoxChange</span><span class="token punctuation">(</span>string<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">self</span><span class="token punctuation">.</span>addressee <span class="token operator">=</span> string<span class="token punctuation">;</span><br /> <span class="token class-name">Command</span><span class="token punctuation">::</span><span class="token function">none</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">}</span><br /> <br /> <span class="token keyword">fn</span> <span class="token function-definition function">view</span><span class="token punctuation">(</span><span class="token operator">&</span><span class="token keyword">self</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token class-name">Element</span><span class="token operator"><</span><span class="token keyword">Self</span><span class="token punctuation">::</span><span class="token class-name">Message</span><span class="token operator">></span> <span class="token punctuation">{</span><br /> <span class="token keyword">let</span> text <span class="token operator">=</span> <span class="token class-name">Text</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token macro property">format!</span><span class="token punctuation">(</span><span class="token string">"Hello, {0}."</span><span class="token punctuation">,</span> <span class="token keyword">self</span><span class="token punctuation">.</span>addressee<span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">width</span><span class="token punctuation">(</span><span class="token class-name">Length</span><span class="token punctuation">::</span><span class="token class-name">Fill</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">horizontal_alignment</span><span class="token punctuation">(</span><span class="token namespace">iced<span class="token punctuation">::</span>alignment<span class="token punctuation">::</span></span><span class="token class-name">Horizontal</span><span class="token punctuation">::</span><span class="token class-name">Center</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">let</span> row1 <span class="token operator">=</span> <span class="token class-name">Row</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>text<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">let</span> text_input<span class="token punctuation">:</span> <span class="token namespace">iced<span class="token punctuation">::</span>widget<span class="token punctuation">::</span></span><span class="token class-name">TextInput</span><span class="token operator"><</span><span class="token lifetime-annotation symbol">'_</span><span class="token punctuation">,</span> <span class="token class-name">HelloMessage</span><span class="token punctuation">,</span> <span class="token class-name">Renderer</span><span class="token operator">></span> <span class="token operator">=</span><br /> <span class="token namespace">iced<span class="token punctuation">::</span>widget<span class="token punctuation">::</span></span><span class="token function">text_input</span><span class="token punctuation">(</span><span class="token string">"world"</span><span class="token punctuation">,</span> <span class="token keyword">self</span><span class="token punctuation">.</span>addressee<span class="token punctuation">.</span><span class="token function">as_str</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">on_input</span><span class="token punctuation">(</span><span class="token class-name">HelloMessage</span><span class="token punctuation">::</span><span class="token class-name">TextBoxChange</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">let</span> row2 <span class="token operator">=</span> <span class="token class-name">Row</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>text_input<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token class-name">Column</span><span class="token punctuation">::</span><span class="token function">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>row1<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>row2<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">into</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>