<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"
    xmlns:dc="http://purl.org/dc/elements/1.1/">
    <channel>
        <title>Quasimal</title>
        <link>http://quasimal.com</link>
        <description><![CDATA[There are no musings here.]]></description>
        <atom:link href="http://quasimal.com/feed.xml" rel="self"
                   type="application/rss+xml" />
        <lastBuildDate>Sun, 01 Feb 2026 00:00:00 UT</lastBuildDate>
        <item>
    <title>Diamond Air Taxis' Flights API</title>
    <link>http://quasimal.com/projects/DAT.html</link>
    <description><![CDATA[<article class="project">
  <header>
    <h1>Diamond Air Taxis' Flights API</h1>
    
    <clearfix></clearfix>
  </header>
  <section><p><img src="../images/Screenshot%202026-02-01%20at%2019-49-34%20Book%20Outbound%20–%20Diamond%20Air%20Taxis.webp" /></p>
<p>I created a backend service for the <a href="https://diamondairtaxis.com/"><em>Diamond Air
Taxis</em></a> Australian private/charter flights
service, which provides possible flight itineraries for use on the
search/booking pages, and then performs checking of booked itineraries until
departure. If you want to see how those flights look, have a look at the
<a href="https://www.youtube.com/@diamondairtaxis">YouTube channel</a>, which Steve Boyd
updates from time to time with recordings of his flights.</p></section>
  
</article>
]]></description>
    <pubDate>2026-02-01</pubDate>
    <guid>http://quasimal.com/projects/DAT.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>Experiences with support of Cabal's public sublibrary feature</title>
    <link>http://quasimal.com/posts/2026-01-31-cabal-sublibraries.html</link>
    <description><![CDATA[<article class="post">
  <header>
    <h1>Experiences with support of Cabal's public sublibrary feature</h1>
    <time datetime="2026-01-31">January 31, 2026 </time>
    <clearfix></clearfix>
  </header>
  <section><p>This is not a very high quality post: I don’t introduce anything that hasn’t
been written about before already, and I don’t feel like I looked very deeply
into the subject. It’s just an experience report, but hopefully that experience
is useful to someone out there.</p>
<h1 id="pre-ramble">Pre-ramble</h1>
<p>This is a brief rundown of the state of Cabal’s “public sublibrary” feature from
the perspective of a me. I normally develop Haskell projects within a Nix shell.
The level of reproducibility is extremely attractive. I recently span out a few
libraries I’ve developed into their own open-source repositories; see <a href="https://gitlab.com/_mike/vector-pull">the</a> <a href="https://gitlab.com/_mike/streaming-json">flurry</a>
<span class="spurious-link" target="gitlab.com/_mike/aeson-structure-of-arrays"><em>of</em></span> <a href="https://gitlab.com/_mike/aeson-record-as-tuple/">activity</a> (not to be mistaken for productivity; <code>mv</code> did most of the work) on <a href="https://gitlab.com/_mike">my GitLab</a>.</p>
<p>For <code>streaming-json</code> I think the use-case of it is for web servers which produce
large amounts of JSON. It’s structured in a way that makes it easier to do less
allocation while producing said JSON, just writing to handles directly. There
are a lot of web server libraries in the Haskell ecosystem and it’d be nice to
support as many of them as I can.</p>
<p>The standard way to accomplish this is to just make separate Cabal packages for
each interop library; they can all live in harmony in monorepo. When you have
your Git repo cloned somewhere, you ultimately have a directory structure that
looks like this:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">~/Code/streaming-json/.git</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="ex">~/Code/streaming-json/LICENSE</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="ex">~/Code/streaming-json/README</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="ex">~/Code/streaming-json/streaming-json/streaming-json.cabal</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="ex">~/Code/streaming-json/streaming-json/src/Streaming/JSON.hs</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="ex">~/Code/streaming-json/streaming-json/src/Streaming/JSON/Lines.hs</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="ex">~/Code/streaming-json/streaming-json-servant/streaming-json-servant.cabal</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a><span class="ex">~/Code/streaming-json/streaming-json-servant/src/Streaming/JSON/Servant.hs</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a><span class="ex">~/Code/streaming-json/streaming-json-yesod/streaming-json-yesod.cabal</span></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a><span class="ex">~/Code/streaming-json/streaming-json-yesod/src/Streaming/JSON/Yesod.hs</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="ex">...</span></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a><span class="co"># haha, &quot;streaming-json/streaming-json/streaming-json&quot;</span></span></code></pre></div>
<p>Anyway, it’s sorta crap. I don’t want to maintain 3 or more almost-identical
<code>.cabal</code> files, in 3 or more different Hackage packages, in 3 or more
almost-identical source trees, with the inability to see anything <em>other than</em>
<code>streaming-json</code> in any of the paths at a glance <a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a>.
It’s a bunch of minor irritations, but I just don’t like dealing with a bunch of
minor irritations every time I switch files within a project in my editor.</p>
<p>In Cabal we don’t have feature flags like in Cargo. Feature flags undoubtedly do
have their own potential pitfalls, discussed at some length across a few
different GitHub issues, Reddit posts, and Discourse forums. Proponents such as
myself are likely to think: well, Cargo does have them – and with very little
actual restriction in terms of the foottrebuchets you can erect, and it seems to
work quite nicely. But I could very well be blissfully ignorant of the pain
points feature flags perhaps <strong>are</strong> creating for Rust/Cargo ecosystem maintainers.
And <em>definitely</em> I am blissfully ignorant of the pain it would create for the
Cabal project to actually implement.</p>
<p>Happily, Cabal does have another trick up its sleeve: <a href="https://cabal.readthedocs.io/en/latest/cabal-package-description-file.html#pkg-section-library-library">public sublibraries</a>. This
was written about already by <a href="https://kowainik.github.io/posts/membrain#multiple-public-libraries-vs-orphan-instances">Kowainik</a> back in 2019 – so how are things fairing
almost 6 years later?</p>
<p>Recap: You can write sublibraries as ordinary <code>library</code> stanzas in your cabal
file, just with an additional name, like <code>library streaming-json-servant</code>. Then
set <code>visibility: public</code> within that stanza, and now you have a <em>public sublibrary</em>.
If it’s in your available package set, then you should be able to then use that
library from another package, like <code>build-deps: streaming-json:streaming-json-servant</code>.</p>
<p>This seems like a great way to implement optional features (with their own
transitively-optional dependencies) in a library because unlike Cargo feature
flags, which <a href="https://doc.rust-lang.org/cargo/reference/features.html#feature-unification">merely “should” be additive</a>, Cabal sublibraries <em>are</em> additive
<a href="#fn2" class="footnote-ref" id="fnref2" role="doc-noteref"><sup>2</sup></a>. So that footgun
is avoided completely.</p>
<h1 id="getting-to-a-repl-from-various-toolchain-setup-options">Getting to a REPL from various toolchain setup options</h1>
<p>I’ve now tested out the feature across a few different “typical” Haskell
development setups. The goal is to use a the <code>streaming-json</code> library and its
<code>streaming-json-servant</code> sublibrary from another project, fetching <code>streaming-json</code>
from its Git repo.</p>
<p>I set up a repo on my GitLab called <a href="https://gitlab.com/_mike/sublib-test"><code>sublib-test</code></a>. Each branch has a different
setup from which I try to pull in <code>streaming-json</code> and its sublibrary. There’s not
any “actual” code there. (I feel no offence if readers just flick through the
branches on that repo to see what’s different in each, rather than reading on; a
repo speaks a thousand words.)</p>
<p>A few of the setups use Nix. You could set up a Nix project without using flakes
at all, but use Nix flakes I have. I like the ergonomics of being able to <code>nix flake update</code> my dependencies, although you can definitely accomplish that with
<code>niv</code> and its relatives too. In any case, the “real” difference between usual Nix
setups for Haskell projects is the choice of using the default Haskell infra in
Nixpkgs, or to use IOHK’s haskell.nix – in both style and substance they are
quite different. The third option is using Nix just for getting a development
shell going, which is fine, but nearly equivalent to using <code>ghcup</code>, except with
better isolation of different toolchains across projects.</p>
<h2 id="basic-scaffold"><a href="https://gitlab.com/_mike/sublib-test/-/tree/baseline?ref_type=heads">Basic scaffold</a></h2>
<p>See the link in the heading. The branches on this repo are the different options
below.</p>
<h2 id="option-1-nix-flake"><a href="https://gitlab.com/_mike/sublib-test/-/tree/nix-default">Option 1: Nix flake</a><span id="plain"></span></h2>
<ol type="1">
<li>Use <code>nix flake init -t templates#haskell-hello</code> to get the basic scaffold</li>
<li>Do some very minor finagling to get the <a href="https://gitlab.com/_mike/sublib-test/-/blob/nix-default/flake.nix"><code>flake.nix</code></a> to support “extra deps”
from outside Hackage.</li>
<li>Add the Git repo as a input to the <code>flake.nix</code> or use <code>fetchFromGitLab</code>. I put it
as an input on the <code>flake.nix</code> (with <code>flake = false</code>), because it’s nice to be
able to <code>nix flake update</code> a dependency.</li>
<li><code>nix develop</code> (I would recommend Direnv’s <code>use_flake</code> in an actual project. You
will likely get better editor integration [such as if your dev shell provides
HLS, cabal-gild, fourmolu, etc.] that way.)</li>
<li><code>cabal repl</code></li>
<li>Realise that it doesn’t work and experience slight despair and confusion –
what <strong>is</strong> a package anyway, as opposed to a component, or sublibrary? Are these
distinctions the reason why sublibrary support is kinda broken and complex,
because you need different code to handle all the different sorts of
“components” that Cabal packages contain? Can you convince both Nix and Cabal
of the existence of the sublibrary somehow? The answer to these questions are
I don’t know; nor do I know if they are the right questions in the first
place. <code>ghc-pkg list</code> seems to pick up the sublibrary.</li>
</ol>
<h2 id="option-2-haskell-flake-based-on-nix-flake-init--t-templateshaskell-flake"><a href="https://gitlab.com/_mike/sublib-test/-/blob/nix-haskell-flake/flake.nix?ref_type=heads">Option 2: haskell-flake</a> based on <code>nix flake init -t templates#haskell-flake</code></h2>
<p>Basically the same as <a href="#plain">plain</a> but in a nice, declarative wrapper around plain Nix,
which I’m definitely coming around to. My experience with nice declarative
abstraction in Nix has sometimes involved having to completely break out of the
nice declarative abstraction in order to do something though, so I’m slightly
weary of it. Sublibrary support isn’t any different here from in the <a href="#plain">plain</a>
option.</p>
<h2 id="option-3-haskell-nix-based-on-nix-flake-init--t-templateshaskell-nix"><a href="https://gitlab.com/_mike/sublib-test/-/tree/nix-haskell-nix?ref_type=heads">Option 3: haskell-nix</a> based on <code>nix flake init -t templates#haskell-nix</code></h2>
<p>This uses <a href="https://input-output-hk.github.io/haskell.nix">haskell.nix</a> to parse the <code>cabal.project</code> file (see below). I almost gave
up after 4 hours of being stuck on compiling
<code>generics-sop-lib-generics-sop-x86_64-w64-mingw32</code> until I realised it’s
configured in <code>nix/hix.nix</code> where cross platform compilation support is configured
(which is pretty cool to have! But not very useful for my development
environment).</p>
<p>An initial <code>cabal repl</code> actually works within this environment – but only after
<code>cabal-install</code> itself fetched <code>streaming-json</code>. For actual builds (as opposed to
generating a dev shell) haskell.nix does actually fetch the repository, and it’s
able to build and utilise the sublibrary. Huzzah – there’s at least one way for
a Haskell Nix project to use Cabal sublibraries. I’m not sure how I feel about
it overall, as it’s a quite complex abstraction, with its own <code>nixpkgs</code> even.</p>
<h2 id="option-4-ghcup-cabal-setup"><a href="https://gitlab.com/_mike/sublib-test/-/tree/cabal-project?ref_type=heads">Option 4: ghcup + Cabal setup</a></h2>
<p>This is what newcomers to Haskell would likely be using. The experience here was
actually very straightforward.</p>
<ol type="1">
<li>Use <code>ghcup</code> to set up GHC and <code>cabal-install</code>.</li>
<li>Add a <a href="https://gitlab.com/_mike/sublib-test/-/blob/cabal-project/cabal.project?ref_type=heads"><code>cabal.project</code></a> which has the Git repo for <code>streaming-json</code> at the right
revision.</li>
<li><code>cabal update</code></li>
<li><code>cabal repl</code></li>
<li><code>&gt;&gt;&gt; import Streaming.JSON.Servant</code> - yay!</li>
</ol>
<h2 id="option-5-stack"><a href="https://gitlab.com/_mike/sublib-test/-/tree/stack?ref_type=heads">Option 5: Stack</a></h2>
<p>This was also very straightforward and a likely option that Haskell newcomers
would use. Stack makes a bit more effort to have helpful (and searchable) error
messages which was nice.</p>
<ol type="1">
<li>Use <code>ghcup</code> to acquire Stack.</li>
<li>Add a <code>stack.yaml</code></li>
<li><code>stack repl</code></li>
<li><code>&gt;&gt;&gt; import Streaming.JSON.Servant</code> - yay!</li>
</ol>
<h1 id="documentation">Documentation</h1>
<p>This is where sublibrary support still sucks: Haddock doesn’t generate
documentation for sublibraries by default, but you <strong>can</strong> get some documentation
out of it, by explicitly targetting all the components you want docs for, like
<code>cabal haddock streaming-json streaming-json:streaming-json-servant</code>. The
sublibrary documentation will be placed under an enigmatically-named directory
<code>l/sublibrary-name</code>. I don’t think Hackage will do this for you at the moment
though, so any sublibraries actually on there won’t get rendered docs AFAIK.</p>
<h1 id="conclusion">Conclusion</h1>
<p>So there you have it. Support for public sublibraries is good for <code>cabal-install</code>,
Stack, and Haskell.nix users. It’s sorta shit in “plain” Nix. This post was
generated using the help of a keyboard and my tired fingers. Hopefully it is
helpful to someone out there.</p>
<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>my use of
<code>(projectile-find-file)</code> is utterly enfeebled by this directory structure :D<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn2"><p>to the extent that Haskell themselves modules are additive, anyway. You
could do slightly evil fake conditional compilation with an argumentless open
type family <code>type family Evil :: Symbol</code> with no instance, but lots of definitions
like <code>x :: (Evil ~ "yes") =&gt; (); y :: (Evil ~ "no") =&gt; ()~</code>, effectively locking
the API to only what the ultimate choice of the <code>Evil</code> type is.<a href="#fnref2" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section></section>
  <footer><br />

<div id="disqus_thread"></div>
<script type="text/javascript">
  var disqus_shortname = 'quasimal';
  (function() {
    var dsq = document.createElement('script');
    dsq.type = 'text/javascript';
    dsq.async = true;
    dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
    (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
  })();
</script>
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
<a href="http://disqus.com" class="dsq-brlink">blog comments powered by <span class="logo-disqus">Disqus</span></a>
</footer>
</article>
]]></description>
    <pubDate>Sat, 31 Jan 2026 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/posts/2026-01-31-cabal-sublibraries.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2025-11-18-0151_DSCF0151</title>
    <link>http://quasimal.com/gallery/2025-11-18-0151_DSCF0151.html</link>
    <description><![CDATA[<header>
  <h1>2025-11-18-0151_DSCF0151</h1>
  <time datetime="November 18, 2025">November 18, 2025</time>
</header>
<figure>
  <a href="/gallery/raw/2025-11-18-0151_DSCF0151.webp">
    <img
      src="/gallery/raw/2025-11-18-0151_DSCF0151.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Tue, 18 Nov 2025 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2025-11-18-0151_DSCF0151.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2025-11-18-0032_DSCF0032</title>
    <link>http://quasimal.com/gallery/2025-11-18-0032_DSCF0032.html</link>
    <description><![CDATA[<header>
  <h1>2025-11-18-0032_DSCF0032</h1>
  <time datetime="November 18, 2025">November 18, 2025</time>
</header>
<figure>
  <a href="/gallery/raw/2025-11-18-0032_DSCF0032.webp">
    <img
      src="/gallery/raw/2025-11-18-0032_DSCF0032.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Tue, 18 Nov 2025 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2025-11-18-0032_DSCF0032.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2025-05-26-dazza the defender</title>
    <link>http://quasimal.com/gallery/2025-05-26-dazza%20the%20defender.html</link>
    <description><![CDATA[<header>
  <h1>2025-05-26-dazza the defender</h1>
  <time datetime="May 26, 2025">May 26, 2025</time>
</header>
<figure>
  <a href="/gallery/raw/2025-05-26-dazza the defender.webp">
    <img
      src="/gallery/raw/2025-05-26-dazza the defender.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Mon, 26 May 2025 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2025-05-26-dazza%20the%20defender.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2025-02-06-01_52_57 IMG_1226</title>
    <link>http://quasimal.com/gallery/2025-02-06-01_52_57%20IMG_1226.html</link>
    <description><![CDATA[<header>
  <h1>2025-02-06-01_52_57 IMG_1226</h1>
  <time datetime="February  6, 2025">February  6, 2025</time>
</header>
<figure>
  <a href="/gallery/raw/2025-02-06-01_52_57 IMG_1226.webp">
    <img
      src="/gallery/raw/2025-02-06-01_52_57 IMG_1226.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Thu, 06 Feb 2025 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2025-02-06-01_52_57%20IMG_1226.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2025-02-02-06_37_38 _MG_9707</title>
    <link>http://quasimal.com/gallery/2025-02-02-06_37_38%20_MG_9707.html</link>
    <description><![CDATA[<header>
  <h1>2025-02-02-06_37_38 _MG_9707</h1>
  <time datetime="February  2, 2025">February  2, 2025</time>
</header>
<figure>
  <a href="/gallery/raw/2025-02-02-06_37_38 _MG_9707.webp">
    <img
      src="/gallery/raw/2025-02-02-06_37_38 _MG_9707.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Sun, 02 Feb 2025 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2025-02-02-06_37_38%20_MG_9707.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2025-02-02-04_04_53 _MG_9424</title>
    <link>http://quasimal.com/gallery/2025-02-02-04_04_53%20_MG_9424.html</link>
    <description><![CDATA[<header>
  <h1>2025-02-02-04_04_53 _MG_9424</h1>
  <time datetime="February  2, 2025">February  2, 2025</time>
</header>
<figure>
  <a href="/gallery/raw/2025-02-02-04_04_53 _MG_9424.webp">
    <img
      src="/gallery/raw/2025-02-02-04_04_53 _MG_9424.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Sun, 02 Feb 2025 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2025-02-02-04_04_53%20_MG_9424.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2025-01-25-00_28_28 _MG_7510</title>
    <link>http://quasimal.com/gallery/2025-01-25-00_28_28%20_MG_7510.html</link>
    <description><![CDATA[<header>
  <h1>2025-01-25-00_28_28 _MG_7510</h1>
  <time datetime="January 25, 2025">January 25, 2025</time>
</header>
<figure>
  <a href="/gallery/raw/2025-01-25-00_28_28 _MG_7510.webp">
    <img
      src="/gallery/raw/2025-01-25-00_28_28 _MG_7510.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Sat, 25 Jan 2025 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2025-01-25-00_28_28%20_MG_7510.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2025-01-19-20_19_30 _MG_6502</title>
    <link>http://quasimal.com/gallery/2025-01-19-20_19_30%20_MG_6502.html</link>
    <description><![CDATA[<header>
  <h1>2025-01-19-20_19_30 _MG_6502</h1>
  <time datetime="January 19, 2025">January 19, 2025</time>
</header>
<figure>
  <a href="/gallery/raw/2025-01-19-20_19_30 _MG_6502.webp">
    <img
      src="/gallery/raw/2025-01-19-20_19_30 _MG_6502.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Sun, 19 Jan 2025 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2025-01-19-20_19_30%20_MG_6502.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>hasql-mover</title>
    <link>http://quasimal.com/projects/hasql-mover.html</link>
    <description><![CDATA[<article class="project">
  <header>
    <h1>hasql-mover</h1>
    
    <clearfix></clearfix>
  </header>
  <section></section>
  
  <p>Moved to <a href="https://github.com/mikeplus64/hasql-mover">https://github.com/mikeplus64/hasql-mover</a>.</p>
  <script>
    window.location.href = "https://github.com/mikeplus64/hasql-mover";
  </script>
  
</article>
]]></description>
    <pubDate>2025-01-01</pubDate>
    <guid>http://quasimal.com/projects/hasql-mover.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2024-04-05-IMG_4357</title>
    <link>http://quasimal.com/gallery/2024-04-05-IMG_4357.html</link>
    <description><![CDATA[<header>
  <h1>2024-04-05-IMG_4357</h1>
  <time datetime="April  5, 2024">April  5, 2024</time>
</header>
<figure>
  <a href="/gallery/raw/2024-04-05-IMG_4357.webp">
    <img
      src="/gallery/raw/2024-04-05-IMG_4357.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Fri, 05 Apr 2024 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2024-04-05-IMG_4357.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>Journeyman</title>
    <link>http://quasimal.com/projects/journeyman.html</link>
    <description><![CDATA[<article class="project">
  <header>
    <h1>Journeyman</h1>
    
    <clearfix></clearfix>
  </header>
  <section></section>
  
  <p>Moved to <a href="https://github.com/mikeplus64/journeyman/">https://github.com/mikeplus64/journeyman/</a>.</p>
  <script>
    window.location.href = "https://github.com/mikeplus64/journeyman/";
  </script>
  
</article>
]]></description>
    <pubDate>2023-10-01</pubDate>
    <guid>http://quasimal.com/projects/journeyman.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2023-08-02-kettle-scene</title>
    <link>http://quasimal.com/gallery/2023-08-02-kettle-scene.html</link>
    <description><![CDATA[<header>
  <h1>2023-08-02-kettle-scene</h1>
  <time datetime="August  2, 2023">August  2, 2023</time>
</header>
<figure>
  <a href="/gallery/raw/2023-08-02-kettle-scene.webp">
    <img
      src="/gallery/raw/2023-08-02-kettle-scene.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Wed, 02 Aug 2023 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2023-08-02-kettle-scene.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2023-03-24-refined-clump-friend</title>
    <link>http://quasimal.com/gallery/2023-03-24-refined-clump-friend.html</link>
    <description><![CDATA[<header>
  <h1>2023-03-24-refined-clump-friend</h1>
  <time datetime="March 24, 2023">March 24, 2023</time>
</header>
<figure>
  <a href="/gallery/raw/2023-03-24-refined-clump-friend.webp">
    <img
      src="/gallery/raw/2023-03-24-refined-clump-friend.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Fri, 24 Mar 2023 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2023-03-24-refined-clump-friend.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2023-02-11-T15_51_56_20230212_0167</title>
    <link>http://quasimal.com/gallery/2023-02-11-T15_51_56_20230212_0167.html</link>
    <description><![CDATA[<header>
  <h1>2023-02-11-T15_51_56_20230212_0167</h1>
  <time datetime="February 11, 2023">February 11, 2023</time>
</header>
<figure>
  <a href="/gallery/raw/2023-02-11-T15_51_56_20230212_0167.webp">
    <img
      src="/gallery/raw/2023-02-11-T15_51_56_20230212_0167.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Sat, 11 Feb 2023 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2023-02-11-T15_51_56_20230212_0167.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>kuachi cups</title>
    <link>http://quasimal.com/projects/kuachi%20cups.html</link>
    <description><![CDATA[<article class="project">
  <header>
    <h1>kuachi cups</h1>
    
    <clearfix></clearfix>
  </header>
  <section></section>
  
  <p>Moved to <a href="https://kuachi.gg">https://kuachi.gg</a>.</p>
  <script>
    window.location.href = "https://kuachi.gg";
  </script>
  
</article>
]]></description>
    <pubDate>2023-01-01</pubDate>
    <guid>http://quasimal.com/projects/kuachi%20cups.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2022-07-01-nose</title>
    <link>http://quasimal.com/gallery/2022-07-01-nose.html</link>
    <description><![CDATA[<header>
  <h1>2022-07-01-nose</h1>
  <time datetime="July  1, 2022">July  1, 2022</time>
</header>
<figure>
  <a href="/gallery/raw/2022-07-01-nose.webp">
    <img
      src="/gallery/raw/2022-07-01-nose.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Fri, 01 Jul 2022 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2022-07-01-nose.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>Lenses with OverloadedRecordDot</title>
    <link>http://quasimal.com/posts/2022-02-23-overloadedrecorddot-is-evil.html</link>
    <description><![CDATA[<article class="post">
  <header>
    <h1>Lenses with OverloadedRecordDot</h1>
    <time datetime="2022-02-23">February 23, 2022  by Mike Ledger </time>
    <clearfix></clearfix>
  </header>
  <section><p>Not wanting to let the 6+ gigabytes of storage required for my
<a href="https://input-output-hk.github.io/haskell.nix/"><code>haskell.nix</code></a>-provided GHC 9.2.1 shell to go to waste, I immediately set to work
using one of the new goodies it provides; GHC 9.2.1 introduced 3 new language
extensions, 2 of which (<code>OverloadedRecordDot</code>, and <code>NoFieldSelectors</code>) are of
interest here.</p>
<p><code>NoFieldSelectors</code> is great because we can finally put automatically generated
record field accessors in the bin forever. The upshot is much less clutter with
variable and record field names. Hurray!</p>
<p>In addition we have a very powerful extension in <a href="https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/overloaded_record_dot.html"><code>OverloadedRecordDot</code></a>. With
this you can now access fields on some data like in other – less civilised –
languages, where accessing a field involves merely using the name of the field
after a dot after the so-called object. There’s a huge advantage here, in that
the namespace for what comes after the dot is only as big as the number of
public fields on the object. We Haskellers are currently accustomed to suffering
through qualified imports of record fields, or strangely prefixed record field
names, or awkward label syntax and extra composition operators.</p>
<p>But there is a drawback. What happens to all my code that’s been using <code>lens</code> or
<code>optics</code>? It’s fairly idiomatic in the <code>lens</code> extended universe to write field
compositions as just <code>field1.field2.field3</code>, but now with <code>OverloadedRecordDot</code>
enabled, <code>.</code> used without spaces around it is no longer the <code>(.)</code> operator from
<code>Prelude</code>, but a special field accessing operator introduced by
<code>OverloadedRecordDot</code>. <code>obj.field</code> is equivalent now to <code>getField @"field" obj</code>;
<code>obj.a.b</code> would be <code>getField @"b" (getField @"a" obj)</code> – so we can see how this
doesn’t really make sense with optics, as the fields are no longer really
“first-class” objects but depend on whatever we are accessing.</p>
<h1 id="you-can-get-lenses-out-of-a-stone">You can get lenses out of a stone</h1>
<aside class="post-image inline-on-right">

<figure>
<img src="../images/average haskell progarmmer.png" />
<figcaption>The average Haskell programmer is an incredible polyglot, knowing upwards of a hundred LANGUAGEs</figcaption>
</figure>
</aside>

<p>We can still make this work. Let’s see what the definition for <code>HasField</code> is:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="op">&gt;&gt;&gt;</span> <span class="op">:</span>i getField</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> <span class="dt">HasField</span><span class="ot"> ::</span> <span class="kw">forall</span> {k}<span class="op">.</span> k <span class="ot">-&gt;</span> <span class="op">*</span> <span class="ot">-&gt;</span> <span class="op">*</span> <span class="ot">-&gt;</span> <span class="dt">Constraint</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> <span class="dt">HasField</span> x r a <span class="op">|</span> x r <span class="ot">-&gt;</span> a <span class="kw">where</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="ot">  getField ::</span> r <span class="ot">-&gt;</span> a</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>    <span class="co">-- Defined in `GHC.Records&#39;</span></span></code></pre></div>
<p>We can define our own instances for this class that GHC will happily accept –
it isn’t a class where only it is permitted to provide the instances
automatically (a la <code>Coercible</code>).</p>
<p>The same idea has been implemented before in JavaScript/TypeScript land, where
there is a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy"><code>Proxy</code></a> object that lets you override the absolute anything out of
almost everything. <code>OverloadedRecordDot</code> enables many of the same (pretty wild)
possibilities.</p>
<p>Some prior art using <code>Proxy</code> for optics:</p>
<ul>
<li><a href="https://github.com/aynik/proxy-lens">https://github.com/aynik/proxy-lens</a></li>
<li><a href="https://github.com/hatashiro/lens.ts">https://github.com/hatashiro/lens.ts</a></li>
<li><a href="https://github.com/yelouafi/focused">https://github.com/yelouafi/focused</a></li>
</ul>
<p>So what follows is pretty much that, but for Haskell, using <code>lens</code> or <code>optics</code>, and
<code>OverloadedRecordDot</code>.</p>
<p>First, we can define the type that will encapsulate our new “first class field”
type. It’ll just be a unit type carrying around a type-level list of symbols,
morally representing a bunch of fields composed with each other.</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- I recommend to put GHC extensions all on one line so that it&#39;s harder to</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="co">-- count how many extensions your file requires</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE AllowAmbiguousTypes, DataKinds, OverloadedRecordDot, ScopedTypeVariables, TypeApplications, UndecidableInstances, DeriveGeneric, TypeFamilies #-}</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">L</span> (<span class="ot">fields ::</span> [<span class="dt">Symbol</span>]) <span class="ot">=</span> <span class="dt">L</span> <span class="kw">deriving</span> (<span class="dt">Show</span>)</span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a><span class="ot">ltail ::</span> <span class="dt">L</span> (x &#39;<span class="op">:</span> xs) <span class="ot">-&gt;</span> <span class="dt">L</span> xs</span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a>ltail _ <span class="ot">=</span> <span class="dt">L</span></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a></span></code></pre></div>
<p>Now, note that <code>OverloadedRecordDot</code> won’t work on data constructors as GHC will
see them as modules first. So, define a lowercase synonym for <code>L</code> as well. A nice
bonus is we can fix it to be of the empty list type instead of any old type
unifying with <code>L xs</code>.</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="ot">l ::</span> <span class="dt">L</span> &#39;[]</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>l <span class="ot">=</span> <span class="dt">L</span></span></code></pre></div>
<p>Defining <code>HasField</code> is easy:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">HasField</span> x (<span class="dt">L</span> xs) (<span class="dt">L</span> (x &#39;<span class="op">:</span> xs)) <span class="kw">where</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>  getField _ <span class="ot">=</span> <span class="dt">L</span></span></code></pre></div>
<p>Which gives us some pretty nifty capabilities:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="op">&gt;&gt;&gt;</span> <span class="op">:</span>t l<span class="op">.</span>asdf</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a>l<span class="op">.</span><span class="ot">asdf ::</span> <span class="dt">L</span> &#39;[<span class="st">&quot;asdf&quot;</span>]</span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="op">&gt;&gt;&gt;</span> <span class="op">:</span>t l<span class="op">.</span>foo<span class="op">.</span>bar</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a>l<span class="op">.</span>foo<span class="op">.</span><span class="ot">bar ::</span> <span class="dt">L</span> &#39;[<span class="st">&quot;bar&quot;</span>, <span class="st">&quot;foo&quot;</span>]</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="op">&gt;&gt;&gt;</span> <span class="op">:</span>t l<span class="op">.</span>foo<span class="op">.</span>bar<span class="op">.</span>baz</span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a>l<span class="op">.</span>foo<span class="op">.</span>bar<span class="op">.</span><span class="ot">baz ::</span> <span class="dt">L</span> &#39;[<span class="st">&quot;baz&quot;</span>, <span class="st">&quot;bar&quot;</span>, <span class="st">&quot;foo&quot;</span>]</span></code></pre></div>
<p>Now, basically, all that’s left is a way to turn a <code>L</code> into an optic. This will
work with both <code>lens</code> and <code>optics</code> style optics. The trick is to utilise some code
previously written that was intended for use with the <code>OverloadedLabels</code> but works
perfectly here as well. For <code>lens</code>, <code>generic-lens</code> gives us that ability to turn a
type-level <code>Symbol</code> into a lens, and <code>optics</code> has the same functionality built-in
through its <code>LabelOptic</code> typeclass.</p>
<h2 id="lens-version"><code>lens</code> version</h2>
<div class="sourceCode" id="cb6"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> <span class="dt">LToLens</span> xs s t a b <span class="kw">where</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a><span class="ot">  asLens ::</span> <span class="dt">L</span> xs <span class="ot">-&gt;</span> <span class="dt">Lens</span> s t a b</span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> (a <span class="op">~</span> s, b <span class="op">~</span> t) <span class="ot">=&gt;</span> <span class="dt">LToLens</span> &#39;[] s t a b <span class="kw">where</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a>  asLens _ <span class="ot">=</span> <span class="fu">id</span></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span></span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a>  (</span>
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a>    <span class="dt">Field</span> x u v a b,</span>
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true" tabindex="-1"></a>    <span class="dt">LToLens</span> xs s t u v</span>
<span id="cb6-11"><a href="#cb6-11" aria-hidden="true" tabindex="-1"></a>  ) <span class="ot">=&gt;</span> <span class="dt">LToLens</span> (x &#39;<span class="op">:</span> xs) s t a b <span class="kw">where</span></span>
<span id="cb6-12"><a href="#cb6-12" aria-hidden="true" tabindex="-1"></a>  asLens l <span class="ot">=</span></span>
<span id="cb6-13"><a href="#cb6-13" aria-hidden="true" tabindex="-1"></a>    asLens (ltail l) <span class="op">.</span></span>
<span id="cb6-14"><a href="#cb6-14" aria-hidden="true" tabindex="-1"></a>    fieldLens <span class="op">@</span>x <span class="op">@</span>u <span class="op">@</span>v <span class="op">@</span>a <span class="op">@</span>b</span></code></pre></div>
<h2 id="optics-version"><code>optics</code> version</h2>
<div class="sourceCode" id="cb7"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> <span class="dt">LToOptic</span> xs k s t a b <span class="kw">where</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a><span class="ot">  asOptic ::</span> <span class="dt">L</span> xs <span class="ot">-&gt;</span> <span class="dt">Optic</span> k <span class="dt">NoIx</span> s t a b</span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a><span class="co">-- Unsure how needed the A_Lens constraint here is or if there&#39;s a better alternative</span></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> (a <span class="op">~</span> s, b <span class="op">~</span> t, k <span class="op">~</span> <span class="dt">A_Lens</span>) <span class="ot">=&gt;</span> <span class="dt">LToOptic</span> &#39;[] k s t a b <span class="kw">where</span></span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a>  asOptic _ <span class="ot">=</span> <span class="dt">Optic</span> <span class="fu">id</span></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span></span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a>  (</span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a>    <span class="dt">LabelOptic</span> x k u v a b,</span>
<span id="cb7-11"><a href="#cb7-11" aria-hidden="true" tabindex="-1"></a>    <span class="dt">LToOptic</span> xs l s t u v,</span>
<span id="cb7-12"><a href="#cb7-12" aria-hidden="true" tabindex="-1"></a>    <span class="dt">JoinKinds</span> l k m</span>
<span id="cb7-13"><a href="#cb7-13" aria-hidden="true" tabindex="-1"></a>  ) <span class="ot">=&gt;</span> <span class="dt">LToOptic</span> (x &#39;<span class="op">:</span> xs) m s t a b <span class="kw">where</span></span>
<span id="cb7-14"><a href="#cb7-14" aria-hidden="true" tabindex="-1"></a>  asOptic l <span class="ot">=</span></span>
<span id="cb7-15"><a href="#cb7-15" aria-hidden="true" tabindex="-1"></a>    asOptic <span class="op">@</span>xs <span class="op">@</span>l <span class="op">@</span>s <span class="op">@</span>t <span class="op">@</span>u <span class="op">@</span>v (ltail l) <span class="op">%</span></span>
<span id="cb7-16"><a href="#cb7-16" aria-hidden="true" tabindex="-1"></a>    labelOptic <span class="op">@</span>x <span class="op">@</span>k <span class="op">@</span>u <span class="op">@</span>v <span class="op">@</span>a <span class="op">@</span>b</span></code></pre></div>
<h2 id="putting-it-to-use">Putting it to use</h2>
<div class="sourceCode" id="cb8"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">A</span> <span class="ot">=</span> <span class="dt">A</span> {<span class="ot"> a ::</span> <span class="dt">B</span> } <span class="kw">deriving</span> (<span class="dt">Show</span>, <span class="dt">Generic</span>)</span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">B</span> <span class="ot">=</span> <span class="dt">B</span> {<span class="ot"> b ::</span> <span class="dt">C</span> } <span class="kw">deriving</span> (<span class="dt">Show</span>, <span class="dt">Generic</span>)</span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">C</span> <span class="ot">=</span> <span class="dt">C</span> {<span class="ot"> c ::</span> <span class="dt">Int</span> } <span class="kw">deriving</span> (<span class="dt">Show</span>, <span class="dt">Generic</span>)</span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a><span class="ot">test ::</span> <span class="dt">A</span></span>
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true" tabindex="-1"></a>test <span class="ot">=</span> <span class="dt">A</span> (<span class="dt">B</span> (<span class="dt">C</span> <span class="dv">42</span>))</span>
<span id="cb8-7"><a href="#cb8-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-8"><a href="#cb8-8" aria-hidden="true" tabindex="-1"></a>test1 <span class="ot">=</span> test <span class="op">^.</span> asLens l<span class="op">.</span>a<span class="op">.</span>b<span class="op">.</span>c</span>
<span id="cb8-9"><a href="#cb8-9" aria-hidden="true" tabindex="-1"></a><span class="co">-- test1 = 42</span></span>
<span id="cb8-10"><a href="#cb8-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-11"><a href="#cb8-11" aria-hidden="true" tabindex="-1"></a>test43 <span class="ot">=</span> (test <span class="op">&amp;</span> asLens l<span class="op">.</span>a<span class="op">.</span>b<span class="op">.</span>c <span class="op">.~</span> <span class="dv">43</span>) <span class="op">^.</span> asLens l<span class="op">.</span>a<span class="op">.</span>b<span class="op">.</span>c</span>
<span id="cb8-12"><a href="#cb8-12" aria-hidden="true" tabindex="-1"></a><span class="co">-- test43 = 43</span></span>
<span id="cb8-13"><a href="#cb8-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-14"><a href="#cb8-14" aria-hidden="true" tabindex="-1"></a><span class="co">-- basic type-changing update</span></span>
<span id="cb8-15"><a href="#cb8-15" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Test</span> a <span class="ot">=</span> <span class="dt">Test</span> {<span class="ot"> test ::</span> a }</span>
<span id="cb8-16"><a href="#cb8-16" aria-hidden="true" tabindex="-1"></a>  <span class="kw">deriving</span> (<span class="dt">Generic</span>, <span class="dt">Show</span>)</span>
<span id="cb8-17"><a href="#cb8-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-18"><a href="#cb8-18" aria-hidden="true" tabindex="-1"></a><span class="ot">typechanging0 ::</span> <span class="dt">Test</span> <span class="dt">Int</span></span>
<span id="cb8-19"><a href="#cb8-19" aria-hidden="true" tabindex="-1"></a>typechanging0 <span class="ot">=</span> <span class="dt">Test</span> <span class="dv">23</span></span>
<span id="cb8-20"><a href="#cb8-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-21"><a href="#cb8-21" aria-hidden="true" tabindex="-1"></a><span class="ot">typechanged ::</span> <span class="dt">Test</span> ()</span>
<span id="cb8-22"><a href="#cb8-22" aria-hidden="true" tabindex="-1"></a>typechanged <span class="ot">=</span> typechanging0 <span class="op">&amp;</span> asLens l<span class="op">.</span>test <span class="op">.~</span> ()</span>
<span id="cb8-23"><a href="#cb8-23" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-24"><a href="#cb8-24" aria-hidden="true" tabindex="-1"></a><span class="ot">nested ::</span> <span class="dt">Test</span> (<span class="dt">Test</span> (<span class="dt">Test</span> <span class="dt">Char</span>))</span>
<span id="cb8-25"><a href="#cb8-25" aria-hidden="true" tabindex="-1"></a>nested <span class="ot">=</span> <span class="dt">Test</span> (<span class="dt">Test</span> (<span class="dt">Test</span> ())) <span class="op">&amp;</span> asLens l<span class="op">.</span>test<span class="op">.</span>test<span class="op">.</span>test <span class="op">.~</span> <span class="ch">&#39;a&#39;</span></span></code></pre></div>
<h1 id="going-further">Going further</h1>
<p>Do some type-level parsing in the <code>GHC.Records.HasField</code> instance to enable:</p>
<ul>
<li>Prisms, i.e. fields beginning with <code>/_[A-Z]/</code>. (Note actual fields can do that
as well, but realistically, who cares?)</li>
<li>An escape hatch out of the <code>L</code> type. That is, accessing, for instance, <code>l.lens</code>,
could have the same effect as calling <code>asLens</code>. Alternatively, have an escape
hatch straight into getters and setters, or even a <code>Lens</code> “object” that supports
many different operations on it. You could really go wild with syntactically
approximating OOP with this extension.</li>
</ul>
<p>Should anyone use this? Honestly, I dunno. There’s already so many different
options for records in Haskell that yet another is maybe … perfectly ok. I
haven’t packaged this up because I can’t use GHC 9.2.1 in production anyway due
to previous changes in the 9.x series holding back many packages that now need
various changes. GHC 8.10.7 is, basically, good enough for any production use,
and this sort of stuff sadly falls in the “nice to have” basket.</p></section>
  <footer><br />

<div id="disqus_thread"></div>
<script type="text/javascript">
  var disqus_shortname = 'quasimal';
  (function() {
    var dsq = document.createElement('script');
    dsq.type = 'text/javascript';
    dsq.async = true;
    dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
    (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
  })();
</script>
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
<a href="http://disqus.com" class="dsq-brlink">blog comments powered by <span class="logo-disqus">Disqus</span></a>
</footer>
</article>
]]></description>
    <pubDate>Wed, 23 Feb 2022 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/posts/2022-02-23-overloadedrecorddot-is-evil.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2022-02-08-combo6</title>
    <link>http://quasimal.com/gallery/2022-02-08-combo6.html</link>
    <description><![CDATA[<header>
  <h1>2022-02-08-combo6</h1>
  <time datetime="February  8, 2022">February  8, 2022</time>
</header>
<figure>
  <a href="/gallery/raw/2022-02-08-combo6.webp">
    <img
      src="/gallery/raw/2022-02-08-combo6.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Tue, 08 Feb 2022 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2022-02-08-combo6.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>solid-typefu-router5</title>
    <link>http://quasimal.com/projects/solid-typefu-router5.html</link>
    <description><![CDATA[<article class="project">
  <header>
    <h1>solid-typefu-router5</h1>
    
    <clearfix></clearfix>
  </header>
  <section><p>This is a TypeScript library making extreme use of type-level computation to
create a really nice routing library (a la <code>react-router</code>), for SolidJS, and
backed by a <code>router5</code> router internally. See documentation <a href="https://mikeplus64.github.io/solid-typefu-router5/">on
GitHub</a>.</p></section>
  
  <p>Moved to <a href="https://mikeplus64.github.io/solid-typefu-router5">https://mikeplus64.github.io/solid-typefu-router5</a>.</p>
  <script>
    window.location.href = "https://mikeplus64.github.io/solid-typefu-router5";
  </script>
  
</article>
]]></description>
    <pubDate>2022-01-14</pubDate>
    <guid>http://quasimal.com/projects/solid-typefu-router5.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2022-01-05-eye</title>
    <link>http://quasimal.com/gallery/2022-01-05-eye.html</link>
    <description><![CDATA[<header>
  <h1>2022-01-05-eye</h1>
  <time datetime="January  5, 2022">January  5, 2022</time>
</header>
<figure>
  <a href="/gallery/raw/2022-01-05-eye.webp">
    <img
      src="/gallery/raw/2022-01-05-eye.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Wed, 05 Jan 2022 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2022-01-05-eye.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>melee-vpad</title>
    <link>http://quasimal.com/projects/melee-vpad.html</link>
    <description><![CDATA[<article class="project">
  <header>
    <h1>melee-vpad</h1>
    
    <clearfix></clearfix>
  </header>
  <section><p><a href="https://github.com/mikeplus64/melee-vpad">Available on GitHub</a></p>
<video autoplay controls src="../extra/melee-vpad.mp4" />
<p>Simple evdev/uinput virtual gamepad for keyboard controls in emulated Smash
Bros Melee. Written in Rust.</p>
<p>Sale-speak: Level up your Slippi gameplay with this one neat trick. Is it
competition-worthy? Probably not. Will you get banned for trying to play with it
in a tournament? Maybe! Will /I/ get banned for playing mediocrely anyway while
still having cheater-level-easy shield drops? Probably, no.</p></section>
  
</article>
]]></description>
    <pubDate>2022-01-01</pubDate>
    <guid>http://quasimal.com/projects/melee-vpad.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2021-08-06-surfshamb</title>
    <link>http://quasimal.com/gallery/2021-08-06-surfshamb.html</link>
    <description><![CDATA[<header>
  <h1>2021-08-06-surfshamb</h1>
  <time datetime="August  6, 2021">August  6, 2021</time>
</header>
<figure>
  <a href="/gallery/raw/2021-08-06-surfshamb.webp">
    <img
      src="/gallery/raw/2021-08-06-surfshamb.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Fri, 06 Aug 2021 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2021-08-06-surfshamb.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2021-03-05-back-off-mart-small</title>
    <link>http://quasimal.com/gallery/2021-03-05-back-off-mart-small.html</link>
    <description><![CDATA[<header>
  <h1>2021-03-05-back-off-mart-small</h1>
  <time datetime="March  5, 2021">March  5, 2021</time>
</header>
<figure>
  <a href="/gallery/raw/2021-03-05-back-off-mart-small.webp">
    <img
      src="/gallery/raw/2021-03-05-back-off-mart-small.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Fri, 05 Mar 2021 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2021-03-05-back-off-mart-small.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2021-01-11-clump-zenaku</title>
    <link>http://quasimal.com/gallery/2021-01-11-clump-zenaku.html</link>
    <description><![CDATA[<header>
  <h1>2021-01-11-clump-zenaku</h1>
  <time datetime="January 11, 2021">January 11, 2021</time>
</header>
<figure>
  <a href="/gallery/raw/2021-01-11-clump-zenaku.webp">
    <img
      src="/gallery/raw/2021-01-11-clump-zenaku.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Mon, 11 Jan 2021 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2021-01-11-clump-zenaku.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>Respecify</title>
    <link>http://quasimal.com/projects/respecify.html</link>
    <description><![CDATA[<article class="project">
  <header>
    <h1>Respecify</h1>
    
    <clearfix></clearfix>
  </header>
  <section><iframe width="420" height="315" src="https://www.youtube.com/watch?v=HA_ywaFb3Y8" frameborder="0" allowfullscreen></iframe>

<p>Respecify was a novel web-based requirements authoring tool developed by me for
Ricardo Rail. It aids requirements engineers by applying a simple English
grammar that ensures.</p>
<ol type="1">
<li>Basic syntactic correctness.</li>
<li>All terms within a specification are defined and have at most 1 meaning.</li>
<li>The relationships specified do not contradict eachother.</li>
<li>Requirements meet some basic quality checks.</li>
</ol>
<p> </p>
<p>A <a href="https://ieeexplore.ieee.org/document/8049164/">tool demonstration</a> was published for <a href="https://web.archive.org/web/20171119051337/http://re2017.org/">RE’17</a>.</p></section>
  
</article>
]]></description>
    <pubDate>2020-01-08</pubDate>
    <guid>http://quasimal.com/projects/respecify.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2020-01-02-card-wandg-robot</title>
    <link>http://quasimal.com/gallery/2020-01-02-card-wandg-robot.html</link>
    <description><![CDATA[<header>
  <h1>2020-01-02-card-wandg-robot</h1>
  <time datetime="January  2, 2020">January  2, 2020</time>
</header>
<figure>
  <a href="/gallery/raw/2020-01-02-card-wandg-robot.webp">
    <img
      src="/gallery/raw/2020-01-02-card-wandg-robot.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Thu, 02 Jan 2020 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2020-01-02-card-wandg-robot.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2020-01-02-card-moondog</title>
    <link>http://quasimal.com/gallery/2020-01-02-card-moondog.html</link>
    <description><![CDATA[<header>
  <h1>2020-01-02-card-moondog</h1>
  <time datetime="January  2, 2020">January  2, 2020</time>
</header>
<figure>
  <a href="/gallery/raw/2020-01-02-card-moondog.webp">
    <img
      src="/gallery/raw/2020-01-02-card-moondog.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Thu, 02 Jan 2020 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2020-01-02-card-moondog.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2020-01-02-card-calvary</title>
    <link>http://quasimal.com/gallery/2020-01-02-card-calvary.html</link>
    <description><![CDATA[<header>
  <h1>2020-01-02-card-calvary</h1>
  <time datetime="January  2, 2020">January  2, 2020</time>
</header>
<figure>
  <a href="/gallery/raw/2020-01-02-card-calvary.webp">
    <img
      src="/gallery/raw/2020-01-02-card-calvary.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Thu, 02 Jan 2020 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2020-01-02-card-calvary.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2019-11-03-wombat</title>
    <link>http://quasimal.com/gallery/2019-11-03-wombat.html</link>
    <description><![CDATA[<header>
  <h1>2019-11-03-wombat</h1>
  <time datetime="November  3, 2019">November  3, 2019</time>
</header>
<figure>
  <a href="/gallery/raw/2019-11-03-wombat.webp">
    <img
      src="/gallery/raw/2019-11-03-wombat.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Sun, 03 Nov 2019 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2019-11-03-wombat.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2019-11-03-quokka-em</title>
    <link>http://quasimal.com/gallery/2019-11-03-quokka-em.html</link>
    <description><![CDATA[<header>
  <h1>2019-11-03-quokka-em</h1>
  <time datetime="November  3, 2019">November  3, 2019</time>
</header>
<figure>
  <a href="/gallery/raw/2019-11-03-quokka-em.webp">
    <img
      src="/gallery/raw/2019-11-03-quokka-em.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Sun, 03 Nov 2019 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2019-11-03-quokka-em.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2019-10-14-sunburnt-country</title>
    <link>http://quasimal.com/gallery/2019-10-14-sunburnt-country.html</link>
    <description><![CDATA[<header>
  <h1>2019-10-14-sunburnt-country</h1>
  <time datetime="October 14, 2019">October 14, 2019</time>
</header>
<figure>
  <a href="/gallery/raw/2019-10-14-sunburnt-country.webp">
    <img
      src="/gallery/raw/2019-10-14-sunburnt-country.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Mon, 14 Oct 2019 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2019-10-14-sunburnt-country.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2019-10-12-wombat</title>
    <link>http://quasimal.com/gallery/2019-10-12-wombat.html</link>
    <description><![CDATA[<header>
  <h1>2019-10-12-wombat</h1>
  <time datetime="October 12, 2019">October 12, 2019</time>
</header>
<figure>
  <a href="/gallery/raw/2019-10-12-wombat.webp">
    <img
      src="/gallery/raw/2019-10-12-wombat.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Sat, 12 Oct 2019 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2019-10-12-wombat.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2019-10-03-wombat</title>
    <link>http://quasimal.com/gallery/2019-10-03-wombat.html</link>
    <description><![CDATA[<header>
  <h1>2019-10-03-wombat</h1>
  <time datetime="October  3, 2019">October  3, 2019</time>
</header>
<figure>
  <a href="/gallery/raw/2019-10-03-wombat.webp">
    <img
      src="/gallery/raw/2019-10-03-wombat.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Thu, 03 Oct 2019 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2019-10-03-wombat.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2019-10-03-bird</title>
    <link>http://quasimal.com/gallery/2019-10-03-bird.html</link>
    <description><![CDATA[<header>
  <h1>2019-10-03-bird</h1>
  <time datetime="October  3, 2019">October  3, 2019</time>
</header>
<figure>
  <a href="/gallery/raw/2019-10-03-bird.webp">
    <img
      src="/gallery/raw/2019-10-03-bird.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Thu, 03 Oct 2019 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2019-10-03-bird.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2019-07-07-clump-warrior</title>
    <link>http://quasimal.com/gallery/2019-07-07-clump-warrior.html</link>
    <description><![CDATA[<header>
  <h1>2019-07-07-clump-warrior</h1>
  <time datetime="July  7, 2019">July  7, 2019</time>
</header>
<figure>
  <a href="/gallery/raw/2019-07-07-clump-warrior.webp">
    <img
      src="/gallery/raw/2019-07-07-clump-warrior.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Sun, 07 Jul 2019 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2019-07-07-clump-warrior.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>csvmaps</title>
    <link>http://quasimal.com/projects/csvmaps.html</link>
    <description><![CDATA[<article class="project">
  <header>
    <h1>csvmaps</h1>
    
    <clearfix></clearfix>
  </header>
  <section><p>This is a command-line tool for manipulating map-like CSV data, where the first
column is treated as a “key” for that row. Please see the README for the most
up-to-date documentation <a href="https://gitlab.com/transportengineering/csvmaps">here</a>.</p>
<h2 id="installation">Installation</h2>
<p>Currently requires stack because of a dependency on an unreleased (or rather,
also not on hackage) package <code>cassava-streaming</code>.</p>
<pre class="shell"><code>$ git clone git@gitlab.com:transportengineering/csvmaps.git
$ cd csvmaps
$ stack install</code></pre>
<h2 id="usage">Usage</h2>
<pre class="shell"><code>$ csvmaps --help
Usage: csvmaps [-i|--infiles STRING]... [-o|--outfile STRING]
               [--has-header BOOL] [--op MAPOP]
               [--expr MAPEXPR] [--save-labels BOOL]
               [--verbose BOOL]

Available options:
  -h,--help                Show this help text
  -i,--infiles STRING...   Files to combine. The first column of each csv will
                           be used as a key. For non-csv files ( iles that don&#39;t
                           end in .csv), each line will be treated as a key.
  -o,--outfile STRING      .csv file to write output to. If omitted, use stdout.
  --has-header BOOL        whether to ignore header in csv files
  --op MAPOP               The operation to use to combine csv files (default:
                           union)
  --expr MAPEXPR
                           The expression to use to combine csv files. Reference
                           the inputs with $N for the Nth document in the
                           &quot;infiles&quot;. The operations available are: 
                           1. Union with the + operator; 
                           2. Difference with the - operator; 
                           3. Intersection with the * operator; 
                           4. Union combining all columns with the +. operator; 
                           5. Intersection combining all columns with the *. 
                              operator 
                           6. Labels for expressions with the syntax
                              &#39;&quot;LABEL&quot;: EXPR&#39;.</code></pre>
<h2 id="examples">Examples</h2>
<p>Take the rows that exist in both <code>v1</code>, in addition (preferring <code>v1</code>) to the rows
that exist in both <code>v1</code> and <code>v2</code> (preferring <code>v2</code>).</p>
<pre class="shell"><code>$ csvmaps -i v1.csv -i v2.csv --expr &#39;(($1 *| $2) +| ($1 - $2))&#39;</code></pre>
<h3 id="mapexpr-syntax">MAPEXPR syntax</h3>
<dl>
<dt><code>$N</code></dt>
<dd>
References the Nth document specified, starting at 1.
</dd>
<dt><code>keys A</code></dt>
<dd>
Discards the values of all rows.
</dd>
<dt><code>nulls A</code></dt>
<dd>
Discards the rows that have non-empty values.
</dd>
<dt><code>non-nulls A</code></dt>
<dd>
Discards the rows that have empty values.
</dd>
<dt><code>A : "label"</code></dt>
<dd>
Assigns a label to the expression <code>A</code>. When used with the <code>--save-labels</code>
option, this will also result in a file called <code>label.csv</code> being created using
<code>A</code>.
</dd>
<dt><code>const ["Col_1","Col_2",...] A</code></dt>
<dd>
Replaces all values of <code>A</code> with the columns given in the first argument.
</dd>
<dt><code>pad N A</code></dt>
<dd>
Ensures the number of columns after each key is at least N; fills ones that
don’t exist with empty strings.
</dd>
<dt><code>col N A</code></dt>
<dd>
Takes the Nth column.
</dd>
<dt><code>col [N1,N2,...] A</code></dt>
<dd>
Uses the columns given.
</dd>
</dl>
<h4 id="unions">Unions</h4>
<dl>
<dt><code>A + B</code></dt>
<dd>
Left-biased union of A and B. Same as <code>A + B</code>. When keys exist in both maps,
the values at those keys are taken from the left operand.
</dd>
<dt><code>A |+ B</code></dt>
<dd>
Left-biased union of A and B. Same as <code>A + B</code>. When keys exist in both maps,
the left operand’s values at those keys are used, unless they are empty.
</dd>
<dt><code>A +| B</code></dt>
<dd>
Right-biased union of A and B. Same as <code>A +| B</code>. When keys exist in both maps,
the right operand’s values at those keys are used, unless they are empty.
</dd>
<dt><code>A |+| B</code></dt>
<dd>
Union of A and B that concatenates the values of all keys that both operands
have in common.
</dd>
</dl>
<h4 id="intersections">Intersections</h4>
<dl>
<dt><code>A * B</code></dt>
<dd>
Left-biased intersection of A and B. Same as <code>A * B</code>. When keys exist in both
maps, the values at those keys are taken from the left operand.
</dd>
<dt><code>A |* B</code></dt>
<dd>
Left-biased intersection of A and B. Same as <code>A * B</code>. When keys exist in both
maps, the left operand’s values at those keys are used, unless they are empty.
</dd>
<dt><code>A *| B</code></dt>
<dd>
Right-biased intersection of A and B. Same as <code>A *| B</code>. When keys exist in both
maps, the right operand’s values at those keys are used, unless they are empty.
</dd>
<dt><code>A |*| B</code></dt>
<dd>
Intersection of A and B that concatenates the values of all keys that both
operands have in common.
</dd>
</dl>
<h4 id="difference">Difference</h4>
<dl>
<dt><code>A - B</code></dt>
<dd>
Returns the rows of A whose keys are not in B.
</dd>
</dl></section>
  
</article>
]]></description>
    <pubDate>January 9, 2019</pubDate>
    <guid>http://quasimal.com/projects/csvmaps.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>shrinkmusic</title>
    <link>http://quasimal.com/projects/shrinkmusic.html</link>
    <description><![CDATA[<article class="project">
  <header>
    <h1>shrinkmusic</h1>
    
    <time>September 12, 2018</time>
    
    <clearfix></clearfix>
  </header>
  <section><p>I love music. I have a rather large digital collection of it, hoarded over many
years, and I intend to grow it indefinitely. There are a wide variety of
formats, from FLAC, to Ogg Theora, to MP3, to (very poor) WMA.</p>
<p>Converting all the music at once, preserving the exact directory structure, is
more of a challenge than it ought to be – ideally, a command should exist, like
<code>rsync</code>, that intelligently decides what to convert and what to just copy
(converting 96kbps mp3 files to a different lossy format will not do it many
favours).</p>
<p>That’s what <a href="https://github.com/mikeplus64/shrinkmusic">shrinkmusic</a> is. It takes an input directory, an output directory,
computes a plan – what to convert, what to keep, what to skip due to already
existing in the output directory, and what to ignore due to a user flag – and
then executes it, in parallel, using as many jobs as you’d like.</p>
<h1 id="features">Features</h1>
<ul>
<li>Add new music without re-converting an entire library</li>
<li>Decides (albeit by pretty dumb heuristics) what is worth converting, and what
is OK to just copy</li>
<li>Splits <code>FILE.flac</code> and <code>FILE.cue</code> pairs automatically</li>
<li>Parallel execution</li>
<li>Dry run mode</li>
<li>Can ignore specific files/directories (e.g., I don’t care to copy hoarded
podcasts – readily available on the internet anyway – over)</li>
</ul>
<h1 id="install">Install</h1>
<p>Probably easiest to install using <a href="https://www.stackage.org/">Stack</a> or <a href="https://nixos.org/">Nix</a>.</p>
<h2 id="stack">Stack</h2>
<div class="sourceCode" id="cb1" data-org-language="sh"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> git pull https://github.com/mikeplus64/shrinkmusic.git</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> cd shrinkmusic</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> stack install</span></code></pre></div>
<h2 id="nix">Nix</h2>
<pre><code>$ nix-env -f ./default.nix -i
</code></pre>
<h1 id="usage">Usage</h1>
<pre><code>Usage: shrinkmusic (-i|--input INPUT) (-o|--output OUTPUT)
                   (-b|--bitrate BITRATE) (-j|--jobs JOBS) [-z|--ignore IGNORE]
                   [-d|--dry-run]

Available options:
  -h,--help                Show this help text
  -i,--input INPUT         Input directory
  -o,--output OUTPUT       Output directory
  -b,--bitrate BITRATE     bitrate for audio
  -j,--jobs JOBS           Number of jobs to use
  -z,--ignore IGNORE       Ignore this file
  -d,--dry-run             Do nothing; just output the plan
</code></pre></section>
  
  <p>Moved to <a href="../posts/2018-08-12-shrinkmusic.html">../posts/2018-08-12-shrinkmusic.html</a>.</p>
  <script>
    window.location.href = "../posts/2018-08-12-shrinkmusic.html";
  </script>
  
</article>
]]></description>
    <pubDate>Wed, 12 Sep 2018 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/projects/shrinkmusic.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2018-04-18-palmer</title>
    <link>http://quasimal.com/gallery/2018-04-18-palmer.html</link>
    <description><![CDATA[<header>
  <h1>2018-04-18-palmer</h1>
  <time datetime="April 18, 2018">April 18, 2018</time>
</header>
<figure>
  <a href="/gallery/raw/2018-04-18-palmer.webp">
    <img
      src="/gallery/raw/2018-04-18-palmer.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Wed, 18 Apr 2018 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2018-04-18-palmer.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2018-03-05-man</title>
    <link>http://quasimal.com/gallery/2018-03-05-man.html</link>
    <description><![CDATA[<header>
  <h1>2018-03-05-man</h1>
  <time datetime="March  5, 2018">March  5, 2018</time>
</header>
<figure>
  <a href="/gallery/raw/2018-03-05-man.webp">
    <img
      src="/gallery/raw/2018-03-05-man.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Mon, 05 Mar 2018 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2018-03-05-man.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2018-02-04-leo</title>
    <link>http://quasimal.com/gallery/2018-02-04-leo.html</link>
    <description><![CDATA[<header>
  <h1>2018-02-04-leo</h1>
  <time datetime="February  4, 2018">February  4, 2018</time>
</header>
<figure>
  <a href="/gallery/raw/2018-02-04-leo.webp">
    <img
      src="/gallery/raw/2018-02-04-leo.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Sun, 04 Feb 2018 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2018-02-04-leo.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2018-02-02-will-durant</title>
    <link>http://quasimal.com/gallery/2018-02-02-will-durant.html</link>
    <description><![CDATA[<header>
  <h1>2018-02-02-will-durant</h1>
  <time datetime="February  2, 2018">February  2, 2018</time>
</header>
<figure>
  <a href="/gallery/raw/2018-02-02-will-durant.webp">
    <img
      src="/gallery/raw/2018-02-02-will-durant.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Fri, 02 Feb 2018 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2018-02-02-will-durant.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2018-02-02-age-does-wither-me</title>
    <link>http://quasimal.com/gallery/2018-02-02-age-does-wither-me.html</link>
    <description><![CDATA[<header>
  <h1>2018-02-02-age-does-wither-me</h1>
  <time datetime="February  2, 2018">February  2, 2018</time>
</header>
<figure>
  <a href="/gallery/raw/2018-02-02-age-does-wither-me.webp">
    <img
      src="/gallery/raw/2018-02-02-age-does-wither-me.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Fri, 02 Feb 2018 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2018-02-02-age-does-wither-me.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2018-01-12-ravel</title>
    <link>http://quasimal.com/gallery/2018-01-12-ravel.html</link>
    <description><![CDATA[<header>
  <h1>2018-01-12-ravel</h1>
  <time datetime="January 12, 2018">January 12, 2018</time>
</header>
<figure>
  <a href="/gallery/raw/2018-01-12-ravel.webp">
    <img
      src="/gallery/raw/2018-01-12-ravel.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Fri, 12 Jan 2018 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2018-01-12-ravel.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>nice-html: a fast and fancy HTML generation library</title>
    <link>http://quasimal.com/posts/2018-01-11-nice-html.html</link>
    <description><![CDATA[<article class="post">
  <header>
    <h1>nice-html: a fast and fancy HTML generation library</h1>
    <time datetime="2018-01-11">January 11, 2018  by Mike Ledger </time>
    <clearfix></clearfix>
  </header>
  <section><p>I’ve been working on and off on a HTML templating library for a few months. It
was originally intended for use in <a href="https://quasimal.com/projects/respecify.html">Respecify</a>, but that evolved into a
single-page application. Thus a library for fast server-side HTML generation
wasn’t necessary any more. (Though you could just as well use <code>nice-html</code> on a
frontend through GHCJS.)</p>
<p>The biggest difference to <a href="http://jaspervdj.be/blaze">BlazeHtml</a> and <a href="https://chrisdone.github.io/lucid/">Lucid</a> and is that templates are
compiled, so as much HTML is “rendered” (i.e., as many strings are concatenated
as possible ☺) ahead-of-time as possible. This isn’t an extremely clever
optimisation, but it can make a pretty big difference in render times.</p>
<p>To make it possible to still “inject” data into a template, a type parameter is
given to the markup (compiled <code>FastMarkup</code> and non-compiled <code>Markup</code>) that gives
templates “holes”. e.g., a template that requires the description of a person to
render could be typed <code>tpl :: FastMarkup (Person -&gt; Text)</code>. On the other hand, a
template with absolutely no dynamic data can be typed <code>tpl :: forall a. FastMarkup a</code> — or just <code>tpl :: FastMarkup Void</code>.</p>
<p>There are a few functions for rendering templates; take your pick. The
easiest-to-use, and most magical, is <code>r :: Render a m =&gt; a -&gt; m Builder</code>. This
allows you to render any <code>FastMarkup</code>, constraining <code>m</code> to be a <code>ReaderT</code> monad
when the parameter to <code>FastMarkup</code> has an arrow. A simpler and less magical
alternative <code>renderM :: Monad m =&gt; (a -&gt; m Builder) -&gt; FastMarkup a -&gt; m Builder</code>
is also provided.</p>
<p>That is, given a simple template like:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Params</span> <span class="ot">=</span> <span class="dt">Params</span> {<span class="ot"> param1 ::</span> <span class="dt">Text</span> }</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="ot">tpl ::</span> <span class="dt">FastMarkup</span> (<span class="dt">Params</span> <span class="ot">-&gt;</span> <span class="dt">Text</span>)</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>tpl <span class="ot">=</span> compile <span class="op">$</span> <span class="kw">do</span> <span class="co">-- note compile :: Markup t a -&gt; FastMarkup t</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>  p_ <span class="op">$</span> <span class="kw">do</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>    <span class="st">&quot;param1 is:&quot;</span> </span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>    dynamic param1</span></code></pre></div>
<p>Rendering can be achieved with:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- using the Reader interface:</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>r tpl <span class="ot">`runReaderT`</span> <span class="dt">Params</span>{param1 <span class="ot">=</span> <span class="st">&quot;i am param1!!!&quot;</span>}<span class="ot"> ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> m <span class="dt">Builder</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="co">-- alternatively using (:$) :: FastMarkup (a -&gt; b) -&gt; a -&gt; a :$ b</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>r (tpl <span class="op">:$</span> <span class="dt">Param</span>{param1 <span class="ot">=</span> <span class="st">&quot;i am param1!!!&quot;</span>})<span class="ot"> ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> m <span class="dt">Builder</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a><span class="co">-- alternatively using </span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a><span class="co">-- renderM :: Monad m =&gt; (a -&gt; m Builder) -&gt; FastMarkup a -&gt; m Builder</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a><span class="co">-- for shunners of magic</span></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a>renderM (\f <span class="ot">-&gt;</span> <span class="fu">return</span> (fromText (f <span class="dt">Param1</span>{param1 <span class="ot">=</span> <span class="st">&quot;i am param1!!!&quot;</span>}))) tpl </span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a><span class="ot">  ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> m <span class="dt">Builder</span></span></code></pre></div>
<p>The biggest drawback of this approach is that it’s more difficult to write
templates in this style, despite whatever benefits it confers. The primitives
that you need to be aware of for inserting “dynamic” data are:</p>
<ol type="1">
<li><p><code>dynamic :: p -&gt; Markup p ()</code> inserts a hole that will be escaped.</p></li>
<li><p><code>dynamicRaw :: p -&gt; Markup p ()</code> inserts a hole that won’t be escaped, e.g.
for a chunk of HTML.</p></li>
<li><p><code>stream :: Foldable f =&gt; Markup (a -&gt; n) r -&gt; Markup (f a -&gt; FastMarkup n) r</code>
inserts a hole for any old <code>Foldable</code> – e.g. <code>[Text]</code>, <code>[Article]</code>,
<code>Vector TodoItem</code> etc.
This is admittedly a bit of a misnomer, since most sane <code>Foldable</code> s won’t ever actually stream.
Except for <code>String</code> s produced by <code>Prelude.readFile</code>, <code>Prelude.getContents</code>,
etc., but these functions are increasingly taboo.</p></li>
<li><p><code>sub :: Markup n a -&gt; Markup (FastMarkup n) a</code> puts templates in your
templates.</p></li>
</ol>
<h2 id="a-complete-example">A complete example</h2>
<div class="sourceCode" id="cb3"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE OverloadedStrings #-}</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="kw">module</span> <span class="dt">TodoList</span> <span class="kw">where</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span>           <span class="dt">Data.Text</span>                   (<span class="dt">Text</span>)</span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span>           <span class="dt">Text.Html.Nice</span>              ((:$) (..), <span class="dt">Attr</span> (..), <span class="dt">Builder</span>,</span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a>                                              <span class="dt">FastMarkup</span>, <span class="dt">Render</span> (<span class="op">..</span>))</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span>           <span class="dt">Text.Html.Nice.Writer</span></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span>           <span class="dt">Text.Html.Nice.Writer.Html5</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Todo</span> <span class="ot">=</span> <span class="dt">Todo</span></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a>  {<span class="ot"> todoDate ::</span> <span class="dt">Text</span></span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a>  ,<span class="ot"> todoText ::</span> <span class="dt">Text</span></span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a>  }</span>
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a><span class="ot">todos ::</span> [<span class="dt">Todo</span>]</span>
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a>todos <span class="ot">=</span></span>
<span id="cb3-16"><a href="#cb3-16" aria-hidden="true" tabindex="-1"></a>  [ <span class="dt">Todo</span> <span class="st">&quot;october 25 2017&quot;</span> <span class="st">&quot;write todo list &lt;html&gt;asdf&lt;/html&gt;&quot;</span> <span class="co">-- escaped</span></span>
<span id="cb3-17"><a href="#cb3-17" aria-hidden="true" tabindex="-1"></a>  , <span class="dt">Todo</span> <span class="st">&quot;october 26 2017&quot;</span> <span class="st">&quot;write another todo list&quot;</span></span>
<span id="cb3-18"><a href="#cb3-18" aria-hidden="true" tabindex="-1"></a>  ]</span>
<span id="cb3-19"><a href="#cb3-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-20"><a href="#cb3-20" aria-hidden="true" tabindex="-1"></a><span class="ot">template ::</span> <span class="dt">FastMarkup</span> ([<span class="dt">Todo</span>] <span class="ot">-&gt;</span> <span class="dt">FastMarkup</span> <span class="dt">Text</span>)</span>
<span id="cb3-21"><a href="#cb3-21" aria-hidden="true" tabindex="-1"></a>template <span class="ot">=</span> compile <span class="op">$</span> <span class="kw">do</span></span>
<span id="cb3-22"><a href="#cb3-22" aria-hidden="true" tabindex="-1"></a>  doctype_</span>
<span id="cb3-23"><a href="#cb3-23" aria-hidden="true" tabindex="-1"></a>  html_ <span class="op">$</span> <span class="kw">do</span></span>
<span id="cb3-24"><a href="#cb3-24" aria-hidden="true" tabindex="-1"></a>    head_ <span class="op">$</span> title_ <span class="st">&quot;Todo list&quot;</span></span>
<span id="cb3-25"><a href="#cb3-25" aria-hidden="true" tabindex="-1"></a>    body_ <span class="op">$</span> <span class="kw">do</span></span>
<span id="cb3-26"><a href="#cb3-26" aria-hidden="true" tabindex="-1"></a>      h1_ <span class="st">&quot;Todo list&quot;</span></span>
<span id="cb3-27"><a href="#cb3-27" aria-hidden="true" tabindex="-1"></a>      stream <span class="op">$</span> div_ <span class="op">!</span> <span class="st">&quot;class&quot;</span> <span class="op">:=</span> <span class="st">&quot;todo-item&quot;</span> <span class="op">$</span> <span class="kw">do</span></span>
<span id="cb3-28"><a href="#cb3-28" aria-hidden="true" tabindex="-1"></a>        text <span class="st">&quot;\n&lt;script&gt;&lt;/script&gt;\n&quot;</span> <span class="co">-- this gets escaped</span></span>
<span id="cb3-29"><a href="#cb3-29" aria-hidden="true" tabindex="-1"></a>        b_ (dynamic todoText)</span>
<span id="cb3-30"><a href="#cb3-30" aria-hidden="true" tabindex="-1"></a>        <span class="st">&quot; (&quot;</span></span>
<span id="cb3-31"><a href="#cb3-31" aria-hidden="true" tabindex="-1"></a>        dynamic todoDate</span>
<span id="cb3-32"><a href="#cb3-32" aria-hidden="true" tabindex="-1"></a>        <span class="st">&quot;)&quot;</span></span>
<span id="cb3-33"><a href="#cb3-33" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-34"><a href="#cb3-34" aria-hidden="true" tabindex="-1"></a><span class="ot">test ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> m <span class="dt">Builder</span></span>
<span id="cb3-35"><a href="#cb3-35" aria-hidden="true" tabindex="-1"></a>test <span class="ot">=</span> r (template <span class="op">:$</span> todos)</span></code></pre></div>
<h1 id="performance">Performance</h1>
<p>These benchmarks derive from <code>blaze-markup</code>’s “bigtable” benchmark; but aren’t
exactly the same. They’ve been shamelessly altered – I added some static markup
to the front and end of the templates, since that’s more realistic than a table
on its own – to highlight the strengths of <code>nice-html</code>.</p>
<p>The benchmark itself generates a table of N rows, and measures the time taken to
render, as well as the amount of memory used, using the venerable <code>criterion</code>
and the underrated <code>weigh</code>. The <code>nice-html</code> version looks (and by looks, I mean
“is”) like:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE OverloadedStrings #-}</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="co">-- derived from https://github.com/jaspervdj/blaze-markup/blob/master/benchmarks/bigtable/html.hs</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="kw">module</span> <span class="dt">BigTable.Nice</span> <span class="kw">where</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span>           <span class="dt">Control.Monad.Trans.Reader</span> (runReader)</span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span>           <span class="dt">Criterion.Main</span>             (<span class="dt">Benchmark</span>, bench, nf)</span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span>           <span class="dt">Data.Text.Lazy</span>             (<span class="dt">Text</span>)</span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span>           <span class="dt">Data.Text.Lazy.Builder</span>     (<span class="dt">Builder</span>, toLazyText)</span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span>           <span class="dt">Data.Text.Lazy.Builder.Int</span> (decimal)</span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span>           <span class="dt">Weigh</span>                      (<span class="dt">Weigh</span>, func)</span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span>           <span class="dt">Text.Html.Nice</span></span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a><span class="ot">rows ::</span> <span class="dt">FastMarkup</span> ([[<span class="dt">Int</span>]] <span class="ot">-&gt;</span> <span class="dt">FastMarkup</span> (<span class="dt">FastMarkup</span> <span class="dt">Builder</span>))</span>
<span id="cb4-14"><a href="#cb4-14" aria-hidden="true" tabindex="-1"></a>rows <span class="ot">=</span> compile <span class="op">$</span> <span class="kw">do</span></span>
<span id="cb4-15"><a href="#cb4-15" aria-hidden="true" tabindex="-1"></a>  h1_ <span class="st">&quot;i am a real big old table\n&quot;</span></span>
<span id="cb4-16"><a href="#cb4-16" aria-hidden="true" tabindex="-1"></a>  p_ <span class="st">&quot;i am good at lots of static data\n&quot;</span></span>
<span id="cb4-17"><a href="#cb4-17" aria-hidden="true" tabindex="-1"></a>  p_ <span class="st">&quot;i am glab at lots of static data\n&quot;</span></span>
<span id="cb4-18"><a href="#cb4-18" aria-hidden="true" tabindex="-1"></a>  p_ <span class="st">&quot;i am glob at lots of static data\n&quot;</span></span>
<span id="cb4-19"><a href="#cb4-19" aria-hidden="true" tabindex="-1"></a>  p_ <span class="st">&quot;i am glib at lots of static data\n&quot;</span></span>
<span id="cb4-20"><a href="#cb4-20" aria-hidden="true" tabindex="-1"></a>  p_ <span class="st">&quot;i am glub at lots of static data\n&quot;</span></span>
<span id="cb4-21"><a href="#cb4-21" aria-hidden="true" tabindex="-1"></a>  p_ <span class="st">&quot;i am glom at lots of static data\n&quot;</span></span>
<span id="cb4-22"><a href="#cb4-22" aria-hidden="true" tabindex="-1"></a>  p_ <span class="st">&quot;i am glof at lots of static data\n&quot;</span></span>
<span id="cb4-23"><a href="#cb4-23" aria-hidden="true" tabindex="-1"></a>  p_ <span class="st">&quot;i am gref at lots of static data\n&quot;</span></span>
<span id="cb4-24"><a href="#cb4-24" aria-hidden="true" tabindex="-1"></a>  p_ <span class="st">&quot;i am greg at lots of static data\n&quot;</span></span>
<span id="cb4-25"><a href="#cb4-25" aria-hidden="true" tabindex="-1"></a>  table_ <span class="op">$</span> <span class="kw">do</span></span>
<span id="cb4-26"><a href="#cb4-26" aria-hidden="true" tabindex="-1"></a>    thead_ <span class="op">.</span> tr_ <span class="op">.</span> <span class="fu">mapM_</span> (th_ <span class="op">.</span> builder <span class="op">.</span> decimal) <span class="op">$</span> [<span class="dv">1</span><span class="op">..</span><span class="dv">10</span><span class="ot"> ::</span> <span class="dt">Int</span>]</span>
<span id="cb4-27"><a href="#cb4-27" aria-hidden="true" tabindex="-1"></a>    tbody_ <span class="op">.</span> stream <span class="op">.</span> tr_ <span class="op">.</span> stream <span class="op">.</span> td_ <span class="op">$</span> <span class="kw">do</span></span>
<span id="cb4-28"><a href="#cb4-28" aria-hidden="true" tabindex="-1"></a>      p_ <span class="st">&quot;hi!\n&quot;</span></span>
<span id="cb4-29"><a href="#cb4-29" aria-hidden="true" tabindex="-1"></a>      dynamic decimal</span>
<span id="cb4-30"><a href="#cb4-30" aria-hidden="true" tabindex="-1"></a>      p_ <span class="st">&quot;hello!\n&quot;</span></span>
<span id="cb4-31"><a href="#cb4-31" aria-hidden="true" tabindex="-1"></a>  p_ <span class="st">&quot;i am good at lots of static data\n&quot;</span></span>
<span id="cb4-32"><a href="#cb4-32" aria-hidden="true" tabindex="-1"></a>  p_ <span class="st">&quot;i am glab at lots of static data\n&quot;</span></span>
<span id="cb4-33"><a href="#cb4-33" aria-hidden="true" tabindex="-1"></a>  p_ <span class="st">&quot;i am glob at lots of static data\n&quot;</span></span>
<span id="cb4-34"><a href="#cb4-34" aria-hidden="true" tabindex="-1"></a>  p_ <span class="st">&quot;i am glib at lots of static data\n&quot;</span></span>
<span id="cb4-35"><a href="#cb4-35" aria-hidden="true" tabindex="-1"></a>  p_ <span class="st">&quot;i am glub at lots of static data\n&quot;</span></span>
<span id="cb4-36"><a href="#cb4-36" aria-hidden="true" tabindex="-1"></a>  p_ <span class="st">&quot;i am glom at lots of static data\n&quot;</span></span>
<span id="cb4-37"><a href="#cb4-37" aria-hidden="true" tabindex="-1"></a>  p_ <span class="st">&quot;i am glof at lots of static data\n&quot;</span></span>
<span id="cb4-38"><a href="#cb4-38" aria-hidden="true" tabindex="-1"></a>  p_ <span class="st">&quot;i am gref at lots of static data\n&quot;</span></span>
<span id="cb4-39"><a href="#cb4-39" aria-hidden="true" tabindex="-1"></a>  p_ <span class="st">&quot;i am greg at lots of static data\n&quot;</span></span>
<span id="cb4-40"><a href="#cb4-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-41"><a href="#cb4-41" aria-hidden="true" tabindex="-1"></a><span class="ot">bigTable ::</span> [[<span class="dt">Int</span>]] <span class="ot">-&gt;</span> <span class="dt">Text</span></span>
<span id="cb4-42"><a href="#cb4-42" aria-hidden="true" tabindex="-1"></a>bigTable table <span class="ot">=</span> toLazyText (r rows <span class="ot">`runReader`</span> table)</span>
<span id="cb4-43"><a href="#cb4-43" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-44"><a href="#cb4-44" aria-hidden="true" tabindex="-1"></a><span class="ot">benchmark ::</span> [[<span class="dt">Int</span>]] <span class="ot">-&gt;</span> <span class="dt">Benchmark</span></span>
<span id="cb4-45"><a href="#cb4-45" aria-hidden="true" tabindex="-1"></a>benchmark t <span class="ot">=</span> bench <span class="st">&quot;nice&quot;</span> (bigTable <span class="ot">`nf`</span> t)</span>
<span id="cb4-46"><a href="#cb4-46" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-47"><a href="#cb4-47" aria-hidden="true" tabindex="-1"></a><span class="ot">weight ::</span> [[<span class="dt">Int</span>]] <span class="ot">-&gt;</span> <span class="dt">Weigh</span> ()</span>
<span id="cb4-48"><a href="#cb4-48" aria-hidden="true" tabindex="-1"></a>weight i <span class="ot">=</span> func (<span class="fu">show</span> (<span class="fu">length</span> i) <span class="op">++</span> <span class="st">&quot;/nice&quot;</span>) bigTable i</span></code></pre></div>
<h2 id="runtime">Runtime</h2>
<p>Abridged: <code>blaze</code> is fast; <code>lucid</code> is faster; <code>nice-html</code> is <strong>fasterer</strong>.</p>
<pre><code>Benchmark perf: RUNNING...
benchmarking 10/blaze
time                 91.73 μs   (91.10 μs .. 92.33 μs)
                     1.000 R²   (0.999 R² .. 1.000 R²)
mean                 92.64 μs   (92.25 μs .. 93.03 μs)
std dev              1.358 μs   (1.073 μs .. 1.807 μs)

benchmarking 10/nice
time                 35.76 μs   (35.52 μs .. 36.00 μs)
                     1.000 R²   (0.999 R² .. 1.000 R²)
mean                 35.50 μs   (35.28 μs .. 35.67 μs)
std dev              626.9 ns   (467.4 ns .. 811.9 ns)
variance introduced by outliers: 14% (moderately inflated)

benchmarking 10/lucid
time                 57.08 μs   (56.91 μs .. 57.27 μs)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 57.20 μs   (56.94 μs .. 57.36 μs)
std dev              711.5 ns   (531.2 ns .. 1.126 μs)

benchmarking 100/blaze
time                 762.7 μs   (760.5 μs .. 764.2 μs)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 762.0 μs   (759.5 μs .. 763.9 μs)
std dev              7.546 μs   (5.949 μs .. 9.589 μs)

benchmarking 100/nice
time                 344.2 μs   (342.9 μs .. 345.4 μs)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 343.5 μs   (342.4 μs .. 344.5 μs)
std dev              3.498 μs   (2.939 μs .. 4.304 μs)

benchmarking 100/lucid
time                 486.5 μs   (485.2 μs .. 487.8 μs)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 485.5 μs   (483.9 μs .. 486.6 μs)
std dev              4.137 μs   (2.838 μs .. 7.064 μs)

benchmarking 1000/blaze
time                 7.243 ms   (7.183 ms .. 7.310 ms)
                     0.999 R²   (0.998 R² .. 1.000 R²)
mean                 7.298 ms   (7.246 ms .. 7.347 ms)
std dev              147.5 μs   (125.5 μs .. 178.1 μs)

benchmarking 1000/nice
time                 3.422 ms   (3.387 ms .. 3.465 ms)
                     0.999 R²   (0.999 R² .. 1.000 R²)
mean                 3.420 ms   (3.402 ms .. 3.436 ms)
std dev              56.16 μs   (46.34 μs .. 69.55 μs)

benchmarking 1000/lucid
time                 4.689 ms   (4.661 ms .. 4.714 ms)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 4.685 ms   (4.667 ms .. 4.698 ms)
std dev              48.05 μs   (38.33 μs .. 62.37 μs)

Benchmark perf: FINISH
</code></pre>
<h2 id="memory-use-including-compilation-overhead">Memory use, including compilation overhead</h2>
<pre><code>Benchmark mem: RUNNING...

Case         Allocated  GCs
10/blaze       597,808    1
10/nice      3,062,248    5
10/lucid       247,008    0
100/blaze    4,556,200    8
100/nice     5,716,888   11
100/lucid    1,735,160    3
1000/blaze  44,138,200   85
1000/nice   32,264,800   62
1000/lucid  16,582,944   29
Benchmark mem: FINISH
</code></pre>
<h2 id="environment-info">Environment info</h2>
<ul>
<li>packages pulled from Stackage’s <code>lts-8.13</code> resolver.</li>
<li><code>nice-html-0.3.0</code></li>
<li><code>lucid-2.9.8.1</code></li>
<li><code>blaze-html-0.8.1.3</code> and <code>blaze-markup-0.7.1.1</code></li>
</ul>
<h1 id="roadmap">Roadmap</h1>
<ol type="1">
<li><p>A more honestly streaming <code>stream</code> using e.g. <code>streaming</code> or <code>pipes</code> or
<code>conduit</code> – or maybe all 3 at once, just to stick it to the zealots of each
☺ – shouldn’t be <strong>too</strong> hard to implement.</p></li>
<li><p>Rewrite <code>Text.Html.Nice.Writer</code> to just use a plain-old <code>State</code> or <code>Writer</code>
monad internally.</p></li>
<li><p>Have a virtual-DOM-esque <code>rerender</code> function that (somehow) only re-renders
what is likely (i.e, if a parameter has changed) to change, and some
JavaScript glue code to enable a client to replace “old” HTML. I’ve scratched
at the surface of this with <code>note</code>, which just gives nodes a unique <code>id</code>
attribute, but it would be <strong>really</strong> neat to achieve this – my eventual dream
is to be able to write fast server-side single-page-applications (especially
if updates are facilitated over e.g. a WebSocket) entirely in Haskell without
needing GHCJS. <a href="https://github.com/ghcjs/jsaddle"><code>jsaddle</code></a> might already do this but I haven’t seriously looked
into it.</p></li>
</ol>
<h1 id="quick-links">Quick links</h1>
<ol type="1">
<li><a href="https://hackage.haskell.org/package/nice-html">Hackage</a></li>
<li><a href="https://github.com/TransportEngineering/nice-html">GitHub</a></li>
<li><a href="https://hackage.haskell.org/package/type-of-html">type-of-html</a> is another fairly recent library that “compiles” HTML (and is a
Haskell EDSL), but it does so at the type-level.</li>
</ol>
<p>Enjoy!</p></section>
  <footer><br />

<div id="disqus_thread"></div>
<script type="text/javascript">
  var disqus_shortname = 'quasimal';
  (function() {
    var dsq = document.createElement('script');
    dsq.type = 'text/javascript';
    dsq.async = true;
    dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
    (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
  })();
</script>
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
<a href="http://disqus.com" class="dsq-brlink">blog comments powered by <span class="logo-disqus">Disqus</span></a>
</footer>
</article>
]]></description>
    <pubDate>Thu, 11 Jan 2018 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/posts/2018-01-11-nice-html.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2018-01-10-lazy</title>
    <link>http://quasimal.com/gallery/2018-01-10-lazy.html</link>
    <description><![CDATA[<header>
  <h1>2018-01-10-lazy</h1>
  <time datetime="January 10, 2018">January 10, 2018</time>
</header>
<figure>
  <a href="/gallery/raw/2018-01-10-lazy.webp">
    <img
      src="/gallery/raw/2018-01-10-lazy.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Wed, 10 Jan 2018 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2018-01-10-lazy.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>Manipulating key-value csv files</title>
    <link>http://quasimal.com/posts/2018-01-09-csvmaps.html</link>
    <description><![CDATA[<article class="post">
  <header>
    <h1>Manipulating key-value csv files</h1>
    <time datetime="2018-01-09">January  9, 2018  by Mike Ledger </time>
    <clearfix></clearfix>
  </header>
  <section><p>This post is about a command-line tool for manipulating map-like CSV data, where
the first column is treated as a “key” for that row. This post is largely the
same as the README available at
https://gitlab.com/transportengineering/csvmaps#unions; except that it’ll
(extremely slowly) get buried under other posts I make. I also self-plagiarized
the same README again on the “project” page for csvmaps, also on this website.</p>
<h2 id="installation">Installation</h2>
<p>Currently requires stack because of a dependency on an unreleased (or rather,
also not on hackage) package <code>cassava-streaming</code>.</p>
<pre class="shell"><code>$ git clone git@gitlab.com:transportengineering/csvmaps.git
$ cd csvmaps
$ stack install</code></pre>
<h2 id="motivation">Motivation</h2>
<p>The use case that motivated it was to manipulate a dictionary of terms from a
project that we, <a href="http://transportengineering.com.au">Transport Engineering</a>
were tasked to clean up, with very little time to actually do it.</p>
<p>The dictionary was simply all the title-case(*) terms that we could
automatically pull out of a particular version of the project. This lead to
there being thousands of spurious terms (e.g., from words at the beginning of a
sentence), and terms that were only partially detected, and a proportional
amount of manual work to cull or edit them.</p>
<p>The tool allowed me to:</p>
<ol type="1">
<li><p>Delete the same terms deleted in version A in version A+1</p></li>
<li><p>For terms that were edited rather than deleted in version A, replace the
non-edited version from A+1 (if it existed) with the version from A</p></li>
<li><p>Detect entirely new terms</p></li>
<li><p>Combine the official project dictionaries with the title-case one I’d
produced</p></li>
</ol>
<p>(*): It also included some joining words like “and”, “or”, “for”, “for” etc.</p>
<h2 id="usage">Usage</h2>
<pre class="shell"><code>$ csvmaps --help
Usage: csvmaps [-i|--infiles STRING]... [-o|--outfile STRING]
               [--has-header BOOL] [--op MAPOP]
               [--expr MAPEXPR] [--save-labels BOOL]
               [--verbose BOOL]

Available options:
  -h,--help                Show this help text
  -i,--infiles STRING...   Files to combine. The first column of each csv will
                           be used as a key. For non-csv files ( iles that don&#39;t
                           end in .csv), each line will be treated as a key.
  -o,--outfile STRING      .csv file to write output to. If omitted, use stdout.
  --has-header BOOL        whether to ignore header in csv files
  --op MAPOP               The operation to use to combine csv files (default:
                           union)
  --expr MAPEXPR
                           The expression to use to combine csv files. Reference
                           the inputs with $N for the Nth document in the
                           &quot;infiles&quot;. The operations available are: 
                           1. Union with the + operator; 
                           2. Difference with the - operator; 
                           3. Intersection with the * operator; 
                           4. Union combining all columns with the +. operator; 
                           5. Intersection combining all columns with the *. 
                              operator 
                           6. Labels for expressions with the syntax
                              &#39;&quot;LABEL&quot;: EXPR&#39;.</code></pre>
<h2 id="examples">Examples</h2>
<p>Take the rows that exist in both <code>v1</code>, in addition (preferring <code>v1</code>) to the rows
that exist in both <code>v1</code> and <code>v2</code> (preferring <code>v2</code>).</p>
<pre class="shell"><code>$ csvmaps -i v1.csv -i v2.csv --expr &#39;(($1 *| $2) +| ($1 - $2))&#39;</code></pre>
<h3 id="mapexpr-syntax">MAPEXPR syntax</h3>
<dl>
<dt><code>$N</code></dt>
<dd>
References the Nth document specified, starting at 1.
</dd>
<dt><code>keys A</code></dt>
<dd>
Discards the values of all rows.
</dd>
<dt><code>nulls A</code></dt>
<dd>
Discards the rows that have non-empty values.
</dd>
<dt><code>non-nulls A</code></dt>
<dd>
Discards the rows that have empty values.
</dd>
<dt><code>A : "label"</code></dt>
<dd>
Assigns a label to the expression <code>A</code>. When used with the <code>--save-labels</code>
option, this will also result in a file called <code>label.csv</code> being created using
<code>A</code>.
</dd>
<dt><code>const ["Col_1","Col_2",...] A</code></dt>
<dd>
Replaces all values of <code>A</code> with the columns given in the first argument.
</dd>
<dt><code>pad N A</code></dt>
<dd>
Ensures the number of columns after each key is at least N; fills ones that
don’t exist with empty strings.
</dd>
<dt><code>col N A</code></dt>
<dd>
Takes the Nth column.
</dd>
<dt><code>col [N1,N2,...] A</code></dt>
<dd>
Uses the columns given.
</dd>
</dl>
<h4 id="unions">Unions</h4>
<dl>
<dt><code>A + B</code></dt>
<dd>
Left-biased union of A and B. Same as <code>A + B</code>. When keys exist in both maps,
the values at those keys are taken from the left operand.
</dd>
<dt><code>A |+ B</code></dt>
<dd>
Left-biased union of A and B. Same as <code>A + B</code>. When keys exist in both maps,
the left operand’s values at those keys are used, unless they are empty.
</dd>
<dt><code>A +| B</code></dt>
<dd>
Right-biased union of A and B. Same as <code>A +| B</code>. When keys exist in both maps,
the right operand’s values at those keys are used, unless they are empty.
</dd>
<dt><code>A |+| B</code></dt>
<dd>
Union of A and B that concatenates the values of all keys that both operands
have in common.
</dd>
</dl>
<h4 id="intersections">Intersections</h4>
<dl>
<dt><code>A * B</code></dt>
<dd>
Left-biased intersection of A and B. Same as <code>A * B</code>. When keys exist in both
maps, the values at those keys are taken from the left operand.
</dd>
<dt><code>A |* B</code></dt>
<dd>
Left-biased intersection of A and B. Same as <code>A * B</code>. When keys exist in both
maps, the left operand’s values at those keys are used, unless they are empty.
</dd>
<dt><code>A *| B</code></dt>
<dd>
Right-biased intersection of A and B. Same as <code>A *| B</code>. When keys exist in both
maps, the right operand’s values at those keys are used, unless they are empty.
</dd>
<dt><code>A |*| B</code></dt>
<dd>
Intersection of A and B that concatenates the values of all keys that both
operands have in common.
</dd>
</dl>
<h4 id="difference">Difference</h4>
<dl>
<dt><code>A - B</code></dt>
<dd>
Returns the rows of A whose keys are not in B.
</dd>
</dl></section>
  <footer><br />

<div id="disqus_thread"></div>
<script type="text/javascript">
  var disqus_shortname = 'quasimal';
  (function() {
    var dsq = document.createElement('script');
    dsq.type = 'text/javascript';
    dsq.async = true;
    dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
    (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
  })();
</script>
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
<a href="http://disqus.com" class="dsq-brlink">blog comments powered by <span class="logo-disqus">Disqus</span></a>
</footer>
</article>
]]></description>
    <pubDate>January 9, 2018</pubDate>
    <guid>http://quasimal.com/posts/2018-01-09-csvmaps.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>radixtree: a prefix-tree parsing library for Haskell</title>
    <link>http://quasimal.com/posts/2018-01-08-prefix-tree-parsing.html</link>
    <description><![CDATA[<article class="post">
  <header>
    <h1>radixtree: a prefix-tree parsing library for Haskell</h1>
    <time datetime="2018-01-08">January  8, 2018  by Mike Ledger </time>
    <clearfix></clearfix>
  </header>
  <section><p>This is just a brief post about <a href="https://hackage.haskell.org/package/radixtree">radixtree</a>, which is a library that:</p>
<ol type="1">
<li>Produces radix (alternatively, prefix?) trees from <code>Text</code> values</li>
<li>Provides a generic parser suitable for use with <code>attoparsec</code>, <code>trifecta</code>,
<code>parsec</code>, or anything with a <code>CharParsing</code> (from <a href="https://hackage.haskell.org/package/parsers">parsers</a>)</li>
</ol>
<h1 id="background">Background</h1>
<p>I’m developing a requirements authoring tool called Respecify, at <a href="http://transportengineering.com.au">Transport Engineering</a>. One of its core features is a nifty (if I do say so myself) parser
generator that I’ve used to create an extremely constrained English grammar for,
which then parses requirements, and enables some other core features of
Respecify.</p>
<p>The parser has to be able to quickly parse hundreds of requirements at once,
each containing terms from a fixed (-ish) dictionary of user-specified nouns and
verbs. The number of terms in these dictionaries can easily number in the
thousands, so it’s critical that this is fast. <a href="http://quasimal.com/projects/respecify.html">See this short project page for more information about Respecify.</a></p>
<h1 id="foreground">Foreground</h1>
<p>Radix trees are a much-beloved data structure useful for parsing terms from
large dictionaries with lots of similar prefixes (e.g., English). In this
context, each edge from a specific node is labelled with the text needed to
advance a parser to the next node, and each node is marked with whether or not
the parser can terminate successfully there, returning whatever datum is at that
node.</p>
<p><img src="../images/prefix-tree.svg" /></p>
<h2 id="performance">Performance</h2>
<p>The motivation is simply speed. Parsing large corpuses can be <strong>much</strong> faster with
this approach. Benchmark results on a corpus of around 800 English terms
demonstrate this:</p>
<pre><code>Benchmark radixtree-parsing: RUNNING...
benchmarking attoparsec/radix
time                 5.032 μs   (5.025 μs .. 5.041 μs)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 5.035 μs   (5.027 μs .. 5.047 μs)
std dev              31.24 ns   (23.48 ns .. 45.45 ns)

benchmarking attoparsec/radix compressed
time                 5.041 μs   (5.031 μs .. 5.053 μs)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 5.060 μs   (5.045 μs .. 5.084 μs)
std dev              59.94 ns   (42.56 ns .. 82.04 ns)

benchmarking attoparsec/naiive
time                 69.32 μs   (69.16 μs .. 69.47 μs)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 69.54 μs   (69.28 μs .. 69.89 μs)
std dev              1.028 μs   (778.0 ns .. 1.251 μs)

Benchmark radixtree-parsing: FINISH
</code></pre>
<p>(Here, “naiive” refers to, basically, <code>choice . map text . sortOn (negate . length)</code>.)</p>
<p>I don’t know and haven’t measured if, for Respecify’s use-case, <code>radixtree</code> is
actually faster than e.g., tokenising and using a hashtable / <code>HashMap</code>, though
that approach also has its own unique downsides. Namely, that you actually have
to define what a token is: even though a user might want the term “IEEE 754” in
their references dictionary, the parser would have to lookup “IEEE”, <strong>then</strong>
“IEEE 754”. But if the user just wants to ignore the parse error because “IEEE
754” isn’t in their reference list yet, the parser will have to just keep trying
with each new token, e.g. “IEEE 754 is”, “IEEE 754 is great”, etc.</p>
<h2 id="memory-use">Memory use</h2>
<p>The trade-off is that a <code>RadixTree</code> uses much more memory than a simple list of
terms, though this is slightly mitigated with a clever (i.e., probably horribly
dangerous – though it hasn’t done anything evil to me yet, and the test suite
seems to validate it) variant named <code>CompressedRadixTree</code>, which just relies on
a single <code>Text</code> array for <strong>all</strong> of its edges and leafs.</p>
<table>
<thead>
<tr>
<th>Variant</th>
<th>Size in bytes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>[Text]</code></td>
<td>69840</td>
</tr>
<tr>
<td><code>Vector Text</code></td>
<td>56952</td>
</tr>
<tr>
<td><code>CompressedRadixTree</code></td>
<td>254032</td>
</tr>
<tr>
<td><code>RadixTree</code></td>
<td>709904</td>
</tr>
</tbody>
</table>
<h1 id="example-code">Example code</h1>
<h1 id="todos">TODOs</h1>
<ol type="1">
<li>Become less afraid of <code>CompressedRadixTree</code></li>
<li>Allow arbitrary data to be accepted by <code>RadixTree</code>, so they can be used as
an alternative to <code>HashMap Text</code>.</li>
</ol>
<h1 id="links">Links</h1>
<ol type="1">
<li><a href="https://hackage.haskell.org/package/radixtree">Hackage</a></li>
<li><a href="https://gitlab.com/transportengineering/radixtree">GitLab</a></li>
</ol></section>
  <footer><br />

<div id="disqus_thread"></div>
<script type="text/javascript">
  var disqus_shortname = 'quasimal';
  (function() {
    var dsq = document.createElement('script');
    dsq.type = 'text/javascript';
    dsq.async = true;
    dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
    (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
  })();
</script>
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
<a href="http://disqus.com" class="dsq-brlink">blog comments powered by <span class="logo-disqus">Disqus</span></a>
</footer>
</article>
]]></description>
    <pubDate>Mon, 08 Jan 2018 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/posts/2018-01-08-prefix-tree-parsing.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>nice-html</title>
    <link>http://quasimal.com/projects/nice-html.html</link>
    <description><![CDATA[A nice HTML templating library]]></description>
    <pubDate>2018-01-08</pubDate>
    <guid>http://quasimal.com/projects/nice-html.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2018-01-07-lady</title>
    <link>http://quasimal.com/gallery/2018-01-07-lady.html</link>
    <description><![CDATA[<header>
  <h1>2018-01-07-lady</h1>
  <time datetime="January  7, 2018">January  7, 2018</time>
</header>
<figure>
  <a href="/gallery/raw/2018-01-07-lady.webp">
    <img
      src="/gallery/raw/2018-01-07-lady.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Sun, 07 Jan 2018 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2018-01-07-lady.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2018-01-07-farmer</title>
    <link>http://quasimal.com/gallery/2018-01-07-farmer.html</link>
    <description><![CDATA[<header>
  <h1>2018-01-07-farmer</h1>
  <time datetime="January  7, 2018">January  7, 2018</time>
</header>
<figure>
  <a href="/gallery/raw/2018-01-07-farmer.webp">
    <img
      src="/gallery/raw/2018-01-07-farmer.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Sun, 07 Jan 2018 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2018-01-07-farmer.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2018-01-02-leone-a-la-frumpy</title>
    <link>http://quasimal.com/gallery/2018-01-02-leone-a-la-frumpy.html</link>
    <description><![CDATA[<header>
  <h1>2018-01-02-leone-a-la-frumpy</h1>
  <time datetime="January  2, 2018">January  2, 2018</time>
</header>
<figure>
  <a href="/gallery/raw/2018-01-02-leone-a-la-frumpy.webp">
    <img
      src="/gallery/raw/2018-01-02-leone-a-la-frumpy.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Tue, 02 Jan 2018 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2018-01-02-leone-a-la-frumpy.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2017-12-30-shadow</title>
    <link>http://quasimal.com/gallery/2017-12-30-shadow.html</link>
    <description><![CDATA[<header>
  <h1>2017-12-30-shadow</h1>
  <time datetime="December 30, 2017">December 30, 2017</time>
</header>
<figure>
  <a href="/gallery/raw/2017-12-30-shadow.webp">
    <img
      src="/gallery/raw/2017-12-30-shadow.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Sat, 30 Dec 2017 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2017-12-30-shadow.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2017-12-22-crumbs-man</title>
    <link>http://quasimal.com/gallery/2017-12-22-crumbs-man.html</link>
    <description><![CDATA[<header>
  <h1>2017-12-22-crumbs-man</h1>
  <time datetime="December 22, 2017">December 22, 2017</time>
</header>
<figure>
  <a href="/gallery/raw/2017-12-22-crumbs-man.webp">
    <img
      src="/gallery/raw/2017-12-22-crumbs-man.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Fri, 22 Dec 2017 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2017-12-22-crumbs-man.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2017-12-12-laika</title>
    <link>http://quasimal.com/gallery/2017-12-12-laika.html</link>
    <description><![CDATA[<header>
  <h1>2017-12-12-laika</h1>
  <time datetime="December 12, 2017">December 12, 2017</time>
</header>
<figure>
  <a href="/gallery/raw/2017-12-12-laika.webp">
    <img
      src="/gallery/raw/2017-12-12-laika.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Tue, 12 Dec 2017 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2017-12-12-laika.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2017-12-01-crumbs-santa</title>
    <link>http://quasimal.com/gallery/2017-12-01-crumbs-santa.html</link>
    <description><![CDATA[<header>
  <h1>2017-12-01-crumbs-santa</h1>
  <time datetime="December  1, 2017">December  1, 2017</time>
</header>
<figure>
  <a href="/gallery/raw/2017-12-01-crumbs-santa.webp">
    <img
      src="/gallery/raw/2017-12-01-crumbs-santa.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Fri, 01 Dec 2017 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2017-12-01-crumbs-santa.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2017-11-18-cacodemon</title>
    <link>http://quasimal.com/gallery/2017-11-18-cacodemon.html</link>
    <description><![CDATA[<header>
  <h1>2017-11-18-cacodemon</h1>
  <time datetime="November 18, 2017">November 18, 2017</time>
</header>
<figure>
  <a href="/gallery/raw/2017-11-18-cacodemon.webp">
    <img
      src="/gallery/raw/2017-11-18-cacodemon.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Sat, 18 Nov 2017 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2017-11-18-cacodemon.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2017-01-25-zombie</title>
    <link>http://quasimal.com/gallery/2017-01-25-zombie.html</link>
    <description><![CDATA[<header>
  <h1>2017-01-25-zombie</h1>
  <time datetime="January 25, 2017">January 25, 2017</time>
</header>
<figure>
  <a href="/gallery/raw/2017-01-25-zombie.webp">
    <img
      src="/gallery/raw/2017-01-25-zombie.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Wed, 25 Jan 2017 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2017-01-25-zombie.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2016-01-13-soviet-space-dog</title>
    <link>http://quasimal.com/gallery/2016-01-13-soviet-space-dog.html</link>
    <description><![CDATA[<header>
  <h1>2016-01-13-soviet-space-dog</h1>
  <time datetime="January 13, 2016">January 13, 2016</time>
</header>
<figure>
  <a href="/gallery/raw/2016-01-13-soviet-space-dog.webp">
    <img
      src="/gallery/raw/2016-01-13-soviet-space-dog.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Wed, 13 Jan 2016 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2016-01-13-soviet-space-dog.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2015-07-10-afternoon</title>
    <link>http://quasimal.com/gallery/2015-07-10-afternoon.html</link>
    <description><![CDATA[<header>
  <h1>2015-07-10-afternoon</h1>
  <time datetime="July 10, 2015">July 10, 2015</time>
</header>
<figure>
  <a href="/gallery/raw/2015-07-10-afternoon.webp">
    <img
      src="/gallery/raw/2015-07-10-afternoon.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Fri, 10 Jul 2015 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2015-07-10-afternoon.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>Static indices for speedy array-based algorithms</title>
    <link>http://quasimal.com/posts/2014-12-21-indices.html</link>
    <description><![CDATA[<article class="post">
  <header>
    <h1>Static indices for speedy array-based algorithms</h1>
    <time datetime="2014-12-21">December 21, 2014  by Mike Ledger </time>
    <clearfix></clearfix>
  </header>
  <section><p><code>GHC.TypeLits</code> gives us some very powerful type-level functionality (with some
caveats that will hopefully be worked out in future) that has not yet seen very
widespread use. With some mildly ugly leg-work, I’ve made a small library that
provides a n-dimensional statically bounded index type, and associated functions
for their application with as minimal overhead as possible.</p>
<h2 id="motivation">Motivation</h2>
<p>Making <a href="http://quasimal.com/projects/plissken.html">plissken</a>, I was unhappy
with the state of linear algebra libraries on Hackage. The venerable
<a href="http://hackage.haskell.org/package/hmatrix">hmatrix</a> is excellent - once your
input sizes outweigh the constant factors involved. For the matrices
that my game engine lived and survived on – 4x4 matrices and 4-vectors –
hmatrix was easily outperformed in simple 4x4 matrix multiplication by
<a href="http://hackage.haskell.org/package/linear">linear</a>, which is a naiive Haskell
implementation of the sort of “small” linear algebra that I need here. I ended
up using hmatrix though, since it being built on <code>Storable</code> made for very easy
interaction with OpenGL.</p>
<p>A trick I found in order to inline away “static recursion” was to abstract the
use of the recursion parameter into a typeclass whose function is inlined. So
long as you remember the all-powerful <code>-O2</code> switch, GHC will happily inline away
every recursive call, which will hopefully give way to further compile-time
evaluation.</p>
<p>Here’s a little demonstration of such a class:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE DataKinds #-}</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE FlexibleContexts #-}</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE KindSignatures #-}</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE TypeFamilies #-}</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE TypeOperators #-}</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE Undecidableinstances #-}</span> <span class="co">-- our use of this should be fine.</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="kw">module</span> <span class="dt">Playaround</span> <span class="kw">where</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Applicative</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">GHC.TypeLits</span></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Peano</span> <span class="ot">=</span> <span class="dt">Succ</span> <span class="dt">Peano</span> <span class="op">|</span> <span class="dt">Zero</span></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> <span class="dt">For</span> (<span class="ot">n ::</span> <span class="dt">Peano</span>) <span class="kw">where</span></span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a><span class="ot">  for ::</span> <span class="dt">Applicative</span> m <span class="ot">=&gt;</span> <span class="dt">Proxy</span> n <span class="ot">-&gt;</span> (<span class="dt">Integer</span> <span class="ot">-&gt;</span> m ()) <span class="ot">-&gt;</span> m () <span class="ot">-&gt;</span> m ()</span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">For</span> <span class="dt">Zero</span> <span class="kw">where</span></span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a>  <span class="ot">{-# INLINE for #-}</span></span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a>  for _ _ acc <span class="ot">=</span> acc</span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> (<span class="dt">KnownNat</span> (<span class="dv">1</span><span class="op">+</span><span class="dt">FromPeano</span> n), <span class="dt">For</span> n) <span class="ot">=&gt;</span> <span class="dt">For</span> (<span class="dt">Succ</span> n) <span class="kw">where</span></span>
<span id="cb1-21"><a href="#cb1-21" aria-hidden="true" tabindex="-1"></a>  <span class="ot">{-# INLINE for #-}</span></span>
<span id="cb1-22"><a href="#cb1-22" aria-hidden="true" tabindex="-1"></a>  for p f acc <span class="ot">=</span> for (next p) f (acc <span class="op">*&gt;</span> f (peanoVal p))</span>
<span id="cb1-23"><a href="#cb1-23" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-24"><a href="#cb1-24" aria-hidden="true" tabindex="-1"></a><span class="ot">next ::</span> <span class="dt">Proxy</span> (<span class="dt">Succ</span> n) <span class="ot">-&gt;</span> <span class="dt">Proxy</span> n</span>
<span id="cb1-25"><a href="#cb1-25" aria-hidden="true" tabindex="-1"></a>next _ <span class="ot">=</span> <span class="dt">Proxy</span></span>
<span id="cb1-26"><a href="#cb1-26" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-27"><a href="#cb1-27" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> <span class="kw">family</span> <span class="dt">FromPeano</span> (<span class="ot">n ::</span> <span class="dt">Peano</span>)<span class="ot"> ::</span> <span class="dt">Nat</span> <span class="kw">where</span></span>
<span id="cb1-28"><a href="#cb1-28" aria-hidden="true" tabindex="-1"></a>  <span class="dt">FromPeano</span> <span class="dt">Zero</span>     <span class="ot">=</span> <span class="dv">0</span></span>
<span id="cb1-29"><a href="#cb1-29" aria-hidden="true" tabindex="-1"></a>  <span class="dt">FromPeano</span> (<span class="dt">Succ</span> n) <span class="ot">=</span> <span class="dv">1</span> <span class="op">+</span> <span class="dt">FromPeano</span> n</span>
<span id="cb1-30"><a href="#cb1-30" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-31"><a href="#cb1-31" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> <span class="kw">family</span> <span class="dt">ToPeano</span> (<span class="ot">n ::</span> <span class="dt">Nat</span>)<span class="ot"> ::</span> <span class="dt">Peano</span> <span class="kw">where</span></span>
<span id="cb1-32"><a href="#cb1-32" aria-hidden="true" tabindex="-1"></a>  <span class="dt">ToPeano</span> <span class="dv">0</span> <span class="ot">=</span> <span class="dt">Zero</span></span>
<span id="cb1-33"><a href="#cb1-33" aria-hidden="true" tabindex="-1"></a>  <span class="dt">ToPeano</span> n <span class="ot">=</span> <span class="dt">Succ</span> (<span class="dt">ToPeano</span> (n<span class="op">-</span><span class="dv">1</span>))</span>
<span id="cb1-34"><a href="#cb1-34" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-35"><a href="#cb1-35" aria-hidden="true" tabindex="-1"></a><span class="ot">fromPeano ::</span> <span class="dt">Proxy</span> (<span class="ot">n ::</span> <span class="dt">Peano</span>) <span class="ot">-&gt;</span> <span class="dt">Proxy</span> (<span class="dt">FromPeano</span> n)</span>
<span id="cb1-36"><a href="#cb1-36" aria-hidden="true" tabindex="-1"></a>fromPeano _ <span class="ot">=</span> <span class="dt">Proxy</span></span>
<span id="cb1-37"><a href="#cb1-37" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-38"><a href="#cb1-38" aria-hidden="true" tabindex="-1"></a><span class="ot">toPeano ::</span> <span class="dt">Proxy</span> (<span class="ot">n ::</span> <span class="dt">Nat</span>) <span class="ot">-&gt;</span> <span class="dt">Proxy</span> (<span class="dt">ToPeano</span> n)</span>
<span id="cb1-39"><a href="#cb1-39" aria-hidden="true" tabindex="-1"></a>toPeano _ <span class="ot">=</span> <span class="dt">Proxy</span></span>
<span id="cb1-40"><a href="#cb1-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-41"><a href="#cb1-41" aria-hidden="true" tabindex="-1"></a><span class="ot">peanoVal ::</span> <span class="dt">KnownNat</span> (<span class="dt">FromPeano</span> n) <span class="ot">=&gt;</span> <span class="dt">Proxy</span> (<span class="ot">n ::</span> <span class="dt">Peano</span>) <span class="ot">-&gt;</span> <span class="dt">Integer</span></span>
<span id="cb1-42"><a href="#cb1-42" aria-hidden="true" tabindex="-1"></a>peanoVal <span class="ot">=</span> <span class="fu">fromInteger</span> <span class="op">.</span> natVal <span class="op">.</span> fromPeano</span></code></pre></div>
<p>That’s pretty gross. We can improve it by wrapping it in a small helper:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="ot">niceFor ::</span> (<span class="dt">For</span> (<span class="dt">ToPeano</span> n), <span class="dt">Applicative</span> m)</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>        <span class="ot">=&gt;</span> <span class="dt">Proxy</span> (<span class="ot">n ::</span> <span class="dt">Nat</span>) <span class="ot">-&gt;</span> (<span class="dt">Integer</span> <span class="ot">-&gt;</span> m ()) <span class="ot">-&gt;</span> m ()</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>niceFor p f <span class="ot">=</span> for (toPeano p) f (<span class="fu">pure</span> ())</span></code></pre></div>
<p>Unfortunately GHC doesn’t see as we can intuitively, that since all <code>Nat</code> has an
instance for <code>ToPeano</code>, <code>For</code> has the instance <code>For (ToPeano (n :: Nat))</code>, so
we have to add that constraint.</p>
<p>Let’s try it out:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE DataKinds #-}</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="kw">module</span> <span class="dt">Main</span> <span class="kw">where</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Playaround</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Data.Vector.Mutable</span> <span class="kw">as</span> <span class="dt">VM</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="ot">main ::</span> <span class="dt">IO</span> ()</span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a>main <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a>  vect <span class="ot">&lt;-</span> M.new <span class="dv">16</span></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a>  niceFor (<span class="dt">Proxy</span><span class="ot"> ::</span> <span class="dt">Proxy</span> <span class="dv">15</span>) <span class="op">$</span> \ix <span class="ot">-&gt;</span> </span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a>    M.write vect ix ix</span></code></pre></div>
<div class="sourceCode" id="cb4"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> ghc-core Main.hs</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>    <span class="ex">-dsuppress-idinfo</span> <span class="dt">\</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>    <span class="at">-dsuppress-coercions</span> <span class="dt">\</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a>    <span class="at">-dsuppress-type-applications</span> <span class="dt">\</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>    <span class="at">-dsuppress-uniques</span> <span class="dt">\</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a>    <span class="at">-dsuppress-module-prefixes</span></span></code></pre></div>
<p>Some scrolling later…</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="ot">main1 ::</span> <span class="dt">State</span><span class="op">#</span> <span class="dt">RealWorld</span> <span class="ot">-&gt;</span> (<span class="op">#</span> <span class="dt">State</span><span class="op">#</span> <span class="dt">RealWorld</span>, () <span class="op">#</span>)</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a>main1 <span class="ot">=</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>  \ (<span class="ot">eta ::</span> <span class="dt">State</span><span class="op">#</span> <span class="dt">RealWorld</span>) <span class="ot">-&gt;</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a>    <span class="kw">case</span> newArray<span class="op">#</span> <span class="dv">16</span> (uninitialised) (eta <span class="ot">`cast`</span> <span class="op">...</span>)</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a>    <span class="kw">of</span> _ { (<span class="op">#</span> ipv, ipv1 <span class="op">#</span>) <span class="ot">-&gt;</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a>    <span class="kw">case</span> writeArray<span class="op">#</span> ipv1 <span class="dv">15</span> (<span class="dt">I</span><span class="op">#</span> <span class="dv">15</span>) ipv <span class="kw">of</span> s&#39;<span class="op">#</span> { __DEFAULT <span class="ot">-&gt;</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a>    <span class="kw">case</span> writeArray<span class="op">#</span> ipv1 <span class="dv">14</span> (<span class="dt">I</span><span class="op">#</span> <span class="dv">14</span>) s&#39;<span class="op">#</span> <span class="kw">of</span> s&#39;<span class="op">#</span><span class="dv">1</span> { __DEFAULT <span class="ot">-&gt;</span></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a>    <span class="kw">case</span> writeArray<span class="op">#</span> ipv1 <span class="dv">13</span> (<span class="dt">I</span><span class="op">#</span> <span class="dv">13</span>) s&#39;<span class="op">#</span><span class="dv">1</span> <span class="kw">of</span> s&#39;<span class="op">#</span><span class="dv">2</span> { __DEFAULT <span class="ot">-&gt;</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a>    <span class="kw">case</span> writeArray<span class="op">#</span> ipv1 <span class="dv">12</span> (<span class="dt">I</span><span class="op">#</span> <span class="dv">12</span>) s&#39;<span class="op">#</span><span class="dv">2</span> <span class="kw">of</span> s&#39;<span class="op">#</span><span class="dv">3</span> { __DEFAULT <span class="ot">-&gt;</span></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a>    <span class="kw">case</span> writeArray<span class="op">#</span> ipv1 <span class="dv">11</span> (<span class="dt">I</span><span class="op">#</span> <span class="dv">11</span>) s&#39;<span class="op">#</span><span class="dv">3</span> <span class="kw">of</span> s&#39;<span class="op">#</span><span class="dv">4</span> { __DEFAULT <span class="ot">-&gt;</span></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a>    <span class="kw">case</span> writeArray<span class="op">#</span> ipv1 <span class="dv">10</span> (<span class="dt">I</span><span class="op">#</span> <span class="dv">10</span>) s&#39;<span class="op">#</span><span class="dv">4</span> <span class="kw">of</span> s&#39;<span class="op">#</span><span class="dv">5</span> { __DEFAULT <span class="ot">-&gt;</span></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a>    <span class="kw">case</span> writeArray<span class="op">#</span> ipv1 <span class="dv">9</span> (<span class="dt">I</span><span class="op">#</span> <span class="dv">9</span>) s&#39;<span class="op">#</span><span class="dv">5</span> <span class="kw">of</span> s&#39;<span class="op">#</span><span class="dv">6</span> { __DEFAULT <span class="ot">-&gt;</span></span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a>    <span class="kw">case</span> writeArray<span class="op">#</span> ipv1 <span class="dv">8</span> (<span class="dt">I</span><span class="op">#</span> <span class="dv">8</span>) s&#39;<span class="op">#</span><span class="dv">6</span> <span class="kw">of</span> s&#39;<span class="op">#</span><span class="dv">7</span> { __DEFAULT <span class="ot">-&gt;</span></span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a>    <span class="kw">case</span> writeArray<span class="op">#</span> ipv1 <span class="dv">7</span> (<span class="dt">I</span><span class="op">#</span> <span class="dv">7</span>) s&#39;<span class="op">#</span><span class="dv">7</span> <span class="kw">of</span> s&#39;<span class="op">#</span><span class="dv">8</span> { __DEFAULT <span class="ot">-&gt;</span></span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a>    <span class="kw">case</span> writeArray<span class="op">#</span> ipv1 <span class="dv">6</span> (<span class="dt">I</span><span class="op">#</span> <span class="dv">6</span>) s&#39;<span class="op">#</span><span class="dv">8</span> <span class="kw">of</span> s&#39;<span class="op">#</span><span class="dv">9</span> { __DEFAULT <span class="ot">-&gt;</span></span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a>    <span class="kw">case</span> writeArray<span class="op">#</span> ipv1 <span class="dv">5</span> (<span class="dt">I</span><span class="op">#</span> <span class="dv">5</span>) s&#39;<span class="op">#</span><span class="dv">9</span> <span class="kw">of</span> s&#39;<span class="op">#</span><span class="dv">10</span> { __DEFAULT <span class="ot">-&gt;</span></span>
<span id="cb5-17"><a href="#cb5-17" aria-hidden="true" tabindex="-1"></a>    <span class="kw">case</span> writeArray<span class="op">#</span> ipv1 <span class="dv">4</span> (<span class="dt">I</span><span class="op">#</span> <span class="dv">4</span>) s&#39;<span class="op">#</span><span class="dv">10</span> <span class="kw">of</span> s&#39;<span class="op">#</span><span class="dv">11</span> { __DEFAULT <span class="ot">-&gt;</span></span>
<span id="cb5-18"><a href="#cb5-18" aria-hidden="true" tabindex="-1"></a>    <span class="kw">case</span> writeArray<span class="op">#</span> ipv1 <span class="dv">3</span> (<span class="dt">I</span><span class="op">#</span> <span class="dv">3</span>) s&#39;<span class="op">#</span><span class="dv">11</span> <span class="kw">of</span> s&#39;<span class="op">#</span><span class="dv">12</span> { __DEFAULT <span class="ot">-&gt;</span></span>
<span id="cb5-19"><a href="#cb5-19" aria-hidden="true" tabindex="-1"></a>    <span class="kw">case</span> writeArray<span class="op">#</span> ipv1 <span class="dv">2</span> (<span class="dt">I</span><span class="op">#</span> <span class="dv">2</span>) s&#39;<span class="op">#</span><span class="dv">12</span> <span class="kw">of</span> s&#39;<span class="op">#</span><span class="dv">13</span> { __DEFAULT <span class="ot">-&gt;</span></span>
<span id="cb5-20"><a href="#cb5-20" aria-hidden="true" tabindex="-1"></a>    <span class="kw">case</span> writeArray<span class="op">#</span> ipv1 <span class="dv">1</span> (<span class="dt">I</span><span class="op">#</span> <span class="dv">1</span>) s&#39;<span class="op">#</span><span class="dv">13</span> <span class="kw">of</span> s&#39;<span class="op">#</span><span class="dv">14</span> { __DEFAULT <span class="ot">-&gt;</span></span>
<span id="cb5-21"><a href="#cb5-21" aria-hidden="true" tabindex="-1"></a>    (<span class="op">#</span> s&#39;<span class="op">#</span><span class="dv">14</span>, () <span class="op">#</span>) <span class="ot">`cast`</span> <span class="op">...</span></span>
<span id="cb5-22"><a href="#cb5-22" aria-hidden="true" tabindex="-1"></a>    } } } } } } } } } } } } } } } }</span></code></pre></div>
<p>Great! The loop is unrolled rather nicely.</p>
<h2 id="enter-indices">Enter <em>indices</em></h2>
<p><code>indices</code> provides a multi-dimensional (<code>Int</code> based) index type, with use for
array-heavy code in mind. I hope Soon® to release on Hackage the package
<a href="https://github.com/mikeplus64/static">static</a> (patches
welcome and appreciated) which provides a <code>ForiegnPtr</code> based array type that
leverages <code>indices</code>’s, ahem, indices, just about everywhere.</p>
<p>Indices in <code>indices</code> are these two types:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> (<span class="ot">a ::</span> <span class="dt">Nat</span>) <span class="op">:.</span> b <span class="ot">=</span> <span class="op">!</span><span class="dt">Int</span> <span class="op">:.</span> b</span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Z</span> <span class="ot">=</span> <span class="dt">Z</span></span></code></pre></div>
<p>This is similar to the design seen in
<a href="https://hackage.haskell.org/package/repa">repa</a>, except the “bound” of an index
is its type. That’s crippling for code with arbitrarily-bounded arrays, but very
nice otherwise. For instance, an index into a 4x4 matrix is <code>0:.0:.Z :: 4:.4:.Z</code>
.</p>
<p>For now, you can use <code>indices</code> for array-based code by leveraging its <code>Ix</code>
instance. This is a little confusing due to the design of <code>Ix</code>, but it’s fairly
simple: the type is always the upper bound, and zero is always the lower bound,
not a value you give. That means that arrays constructed by <code>array (_, _ :: t)</code>
are bounded by [0,t). As an aside, I’m leaning towards reworking <code>static</code> to
simply use the array types found in
<a href="https://hackage.haskell.org/package/arrays">arrays</a> to simplify it greatly into
the small linear-algebra package it yearns to be. Input is appreciated here. (At
the moment I’m irked at having to store two superfluous lower/upper bounds –
linked lists of <code>Int</code> – in the array constructors).</p>
<p>Here’s a demonstration, implementing vector dot product with a static, unrolled
loop:</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE DataKinds     #-}</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE PolyKinds     #-}</span></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE TypeOperators #-}</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE FlexibleContexts #-}</span></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a><span class="kw">module</span> <span class="dt">MM</span> <span class="kw">where</span></span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Array.Unboxed</span></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Index</span></span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> <span class="dt">Vector</span> m <span class="ot">=</span> <span class="dt">UArray</span> (m<span class="op">:.</span><span class="dt">Z</span>)</span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-11"><a href="#cb7-11" aria-hidden="true" tabindex="-1"></a><span class="ot">sizeV ::</span> <span class="dt">Vector</span> m a <span class="ot">-&gt;</span> <span class="dt">Proxy</span> (m<span class="op">:.</span><span class="dt">Z</span>)</span>
<span id="cb7-12"><a href="#cb7-12" aria-hidden="true" tabindex="-1"></a>sizeV _ <span class="ot">=</span> <span class="dt">Proxy</span></span>
<span id="cb7-13"><a href="#cb7-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-14"><a href="#cb7-14" aria-hidden="true" tabindex="-1"></a><span class="ot">vector ::</span> (<span class="dt">IArray</span> <span class="dt">UArray</span> a, <span class="dt">Dim</span> (m<span class="op">:.</span><span class="dt">Z</span>)) <span class="ot">=&gt;</span> <span class="dt">Proxy</span> (m<span class="op">:.</span><span class="dt">Z</span>) <span class="ot">-&gt;</span> [a] <span class="ot">-&gt;</span> <span class="dt">Vector</span> m a</span>
<span id="cb7-15"><a href="#cb7-15" aria-hidden="true" tabindex="-1"></a>vector b <span class="ot">=</span> listArray (zero, <span class="fu">maxBound</span> <span class="ot">`asProxyTypeOf`</span> b)</span>
<span id="cb7-16"><a href="#cb7-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-17"><a href="#cb7-17" aria-hidden="true" tabindex="-1"></a>dot a b <span class="ot">=</span></span>
<span id="cb7-18"><a href="#cb7-18" aria-hidden="true" tabindex="-1"></a>  sfoldlRange</span>
<span id="cb7-19"><a href="#cb7-19" aria-hidden="true" tabindex="-1"></a>    (sizeV a <span class="ot">`asTypeOf`</span> sizeV b)</span>
<span id="cb7-20"><a href="#cb7-20" aria-hidden="true" tabindex="-1"></a>    (\<span class="fu">sum</span> ix <span class="ot">-&gt;</span> <span class="fu">sum</span> <span class="op">+</span> a<span class="op">!</span>ix <span class="op">*</span> b<span class="op">!</span>ix)</span>
<span id="cb7-21"><a href="#cb7-21" aria-hidden="true" tabindex="-1"></a>    <span class="dv">0</span></span>
<span id="cb7-22"><a href="#cb7-22" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-23"><a href="#cb7-23" aria-hidden="true" tabindex="-1"></a>v4 x y z w <span class="ot">=</span> vector (<span class="dt">Proxy</span><span class="ot"> ::</span> <span class="dt">Proxy</span> (<span class="dv">4</span><span class="op">:.</span><span class="dt">Z</span>)) [x,y,z,w]</span>
<span id="cb7-24"><a href="#cb7-24" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-25"><a href="#cb7-25" aria-hidden="true" tabindex="-1"></a><span class="ot">main ::</span> <span class="dt">IO</span> ()</span>
<span id="cb7-26"><a href="#cb7-26" aria-hidden="true" tabindex="-1"></a>main <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb7-27"><a href="#cb7-27" aria-hidden="true" tabindex="-1"></a>  <span class="fu">print</span> (dot (v4 <span class="dv">1</span> <span class="dv">2</span> <span class="dv">3</span> <span class="dv">4</span>) (v4 <span class="dv">2</span> <span class="dv">3</span> <span class="dv">4</span> <span class="dv">5</span>)<span class="ot"> ::</span> <span class="dt">Double</span>)</span>
<span id="cb7-28"><a href="#cb7-28" aria-hidden="true" tabindex="-1"></a>  <span class="fu">print</span> (<span class="fu">sum</span> (<span class="fu">zipWith</span> (<span class="op">*</span>) [<span class="dv">1</span>,<span class="dv">2</span>,<span class="dv">3</span>,<span class="dv">4</span>] [<span class="dv">2</span>,<span class="dv">3</span>,<span class="dv">4</span>,<span class="dv">5</span>])<span class="ot"> ::</span> <span class="dt">Double</span>)</span></code></pre></div>
<p>Note the slyness in me not writing the type signature to <code>dot</code>… Well, the
important thing is that GHC happily infers the type without needing any hints.
<em>Right… guys?</em></p>
<p>You can contribute to <code>indices</code>
<a href="https://github.com/mikeplus64/indices">on GitHub</a>, find its documentation
<a href="https://hackage.haskell.org/package/indices">on Hackage</a>, and install it with
<code>cabal</code>.</p></section>
  <footer><br />

<div id="disqus_thread"></div>
<script type="text/javascript">
  var disqus_shortname = 'quasimal';
  (function() {
    var dsq = document.createElement('script');
    dsq.type = 'text/javascript';
    dsq.async = true;
    dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
    (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
  })();
</script>
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
<a href="http://disqus.com" class="dsq-brlink">blog comments powered by <span class="logo-disqus">Disqus</span></a>
</footer>
</article>
]]></description>
    <pubDate>Sun, 21 Dec 2014 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/posts/2014-12-21-indices.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>Plissken</title>
    <link>http://quasimal.com/projects/plissken.html</link>
    <description><![CDATA[Snake Plissken... I heard of you.]]></description>
    <pubDate>2014-12-21</pubDate>
    <guid>http://quasimal.com/projects/plissken.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2014-09-28-wally_why</title>
    <link>http://quasimal.com/gallery/2014-09-28-wally_why.html</link>
    <description><![CDATA[<header>
  <h1>2014-09-28-wally_why</h1>
  <time datetime="September 28, 2014">September 28, 2014</time>
</header>
<figure>
  <a href="/gallery/raw/2014-09-28-wally_why.webp">
    <img
      src="/gallery/raw/2014-09-28-wally_why.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Sun, 28 Sep 2014 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2014-09-28-wally_why.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2014-09-28-wallace</title>
    <link>http://quasimal.com/gallery/2014-09-28-wallace.html</link>
    <description><![CDATA[<header>
  <h1>2014-09-28-wallace</h1>
  <time datetime="September 28, 2014">September 28, 2014</time>
</header>
<figure>
  <a href="/gallery/raw/2014-09-28-wallace.webp">
    <img
      src="/gallery/raw/2014-09-28-wallace.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Sun, 28 Sep 2014 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2014-09-28-wallace.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2013-08-24-face</title>
    <link>http://quasimal.com/gallery/2013-08-24-face.html</link>
    <description><![CDATA[<header>
  <h1>2013-08-24-face</h1>
  <time datetime="August 24, 2013">August 24, 2013</time>
</header>
<figure>
  <a href="/gallery/raw/2013-08-24-face.webp">
    <img
      src="/gallery/raw/2013-08-24-face.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Sat, 24 Aug 2013 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2013-08-24-face.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>Metal Slug</title>
    <link>http://quasimal.com/gallery/2013-07-23-metalslug.html</link>
    <description><![CDATA[The tank "Metal Slug" from the game "Metal Slug". As with other grid/line paper drawings this was done at the end of high school.]]></description>
    <pubDate>Tue, 23 Jul 2013 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2013-07-23-metalslug.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>tiger (?)</title>
    <link>http://quasimal.com/gallery/2013-06-27-animal.html</link>
    <description><![CDATA[In the style of tiger(?)s from the manga Berserk. Another high school doodle.]]></description>
    <pubDate>Thu, 27 Jun 2013 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2013-06-27-animal.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>what does it mean to bear witness?</title>
    <link>http://quasimal.com/gallery/2013-06-13-bear-witness.html</link>
    <description><![CDATA[Here is the answer I gave to a high-school English class question back in 2012.]]></description>
    <pubDate>Thu, 13 Jun 2013 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2013-06-13-bear-witness.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2013-02-01-sailing-venus</title>
    <link>http://quasimal.com/gallery/2013-02-01-sailing-venus.html</link>
    <description><![CDATA[<header>
  <h1>2013-02-01-sailing-venus</h1>
  <time datetime="February  1, 2013">February  1, 2013</time>
</header>
<figure>
  <a href="/gallery/raw/2013-02-01-sailing-venus.webp">
    <img
      src="/gallery/raw/2013-02-01-sailing-venus.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Fri, 01 Feb 2013 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2013-02-01-sailing-venus.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2013-01-01-monster</title>
    <link>http://quasimal.com/gallery/2013-01-01-monster.html</link>
    <description><![CDATA[<header>
  <h1>2013-01-01-monster</h1>
  <time datetime="January  1, 2013">January  1, 2013</time>
</header>
<figure>
  <a href="/gallery/raw/2013-01-01-monster.webp">
    <img
      src="/gallery/raw/2013-01-01-monster.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Tue, 01 Jan 2013 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2013-01-01-monster.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2012-10-02-shuangjing</title>
    <link>http://quasimal.com/gallery/2012-10-02-shuangjing.html</link>
    <description><![CDATA[<header>
  <h1>2012-10-02-shuangjing</h1>
  <time datetime="October  2, 2012">October  2, 2012</time>
</header>
<figure>
  <a href="/gallery/raw/2012-10-02-shuangjing.webp">
    <img
      src="/gallery/raw/2012-10-02-shuangjing.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Tue, 02 Oct 2012 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2012-10-02-shuangjing.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2012-10-01-beijing</title>
    <link>http://quasimal.com/gallery/2012-10-01-beijing.html</link>
    <description><![CDATA[<header>
  <h1>2012-10-01-beijing</h1>
  <time datetime="October  1, 2012">October  1, 2012</time>
</header>
<figure>
  <a href="/gallery/raw/2012-10-01-beijing.webp">
    <img
      src="/gallery/raw/2012-10-01-beijing.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Mon, 01 Oct 2012 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2012-10-01-beijing.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>2012-09-01-thing</title>
    <link>http://quasimal.com/gallery/2012-09-01-thing.html</link>
    <description><![CDATA[<header>
  <h1>2012-09-01-thing</h1>
  <time datetime="September  1, 2012">September  1, 2012</time>
</header>
<figure>
  <a href="/gallery/raw/2012-09-01-thing.webp">
    <img
      src="/gallery/raw/2012-09-01-thing.webp"
      onload="
        if (window.devicePixelRatio >= 2 && this.naturalWidth >= 900) {
          this.width = this.naturalWidth * 0.5;
        }
      "
    />
  </a>
  <figcaption></figcaption>
</figure>
<section>
  <p>All images © Michael Ledger, all rights reserved.</p>
</section>
]]></description>
    <pubDate>Sat, 01 Sep 2012 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/gallery/2012-09-01-thing.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>A look at QuasiQuotation</title>
    <link>http://quasimal.com/posts/2012-05-25-quasitext-and-quasiquoting.html</link>
    <description><![CDATA[A QuasiQuotation tutorial.]]></description>
    <pubDate>Fri, 25 May 2012 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/posts/2012-05-25-quasitext-and-quasiquoting.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>Functional programming in sh</title>
    <link>http://quasimal.com/posts/2012-05-21-funsh.html</link>
    <description><![CDATA[<article class="post">
  <header>
    <h1>Functional programming in sh</h1>
    <time datetime="2012-05-21">May 21, 2012  by Mike Ledger </time>
    <clearfix></clearfix>
  </header>
  <section><h4 id="warning-this-code-is-horribly-broken-outside-of-use-in-zsh.">Warning: this code is horribly broken outside of use in Zsh.</h4>
<h4 id="as-such-all-occurences-of-sh-are-referring-to-posix-shells-ie-bash-and-binsh-which-is-usually-symlinked-to-bash-minus-some-bash-only-features.">As such, all occurences of “sh” are referring to POSIX shells ie bash and /bin/sh, (which is usually symlinked to bash, minus some bash-only features).</h4>
<p>I find myself trying to do things I can do in GHCi more each passing day in my innocent /bin/sh. I find myself seeking a good balance between the numerous layers of hacks that compose shell script and the <em>purely functional wonder</em> of a Haskell program. Such efforts already exist, most recently in <a href="https://github.com/yesodweb/Shelly.hs#readme">Shelly.hs</a>, but I believe this to be tackling the problem from the wrong end. Instead of hacking together a library to make Haskell code reminiscent of the beloved /bin/sh, the problem should be tackled with extreme prejudice by hacking together a sh script to mimic the best of Haskell, and retain the beauty of shell scripts.</p>
<h3 id="the-beauty-of-shell-scripts">The beauty of shell scripts?</h3>
<ul>
<li>Piping</li>
<li>(almost) Painless concurrency</li>
<li>Small overhead (this problem can be alleviated by my efforts, however)</li>
<li>Easy syntax</li>
<li>Simplest type system in the world: everything is a string!
<ul>
<li>If it’s not a string, it’s a dirty lie.</li>
<li>$(( A dirty lie. ))</li>
</ul></li>
</ul>
<h3 id="the-horror-of-shell-scripts">The horror of shell scripts</h3>
<ul>
<li>The type system makes Visual Basic 6 cry.</li>
<li>The type system makes a Haskell programmer spend days in solitude contemplating the purpose of human life.</li>
<li>sh arrays suck
<ul>
<li>They are strings</li>
<li>Sometimes</li>
</ul></li>
<li>sh syntax quickly piles up; leaning pipe syndrome</li>
<li>sh is slow (thanks zsh and dash!)</li>
</ul>
<h3 id="complexity-and-fun-sh-vs-haskell">Complexity and fun: sh vs Haskell</h3>
<p><img class="centre" src="/images/funsh/funsh.png">
When we are compare such vastly different languages as these, you have to remember that they both have extremely different purposes and real world uses. Haskell is an extremely feature rich general purpose programming language. sh is very convenient for simple and mundane tools that are best made by composing other programs together via piping. For those of us who have no idea of what they’re doing 90% of the time when they are using bash (that would be me), there is:</p>
<p><img class="centre" src="/images/funsh/logo.png"></p>
<h3 id="enter-fun.sh">Enter fun.sh</h3>
<p>fun.sh adds some tools seen and used mainly by functional programmers including take, drop, scanl, foldl, map and lambdas. Because sh has no real type system, the convention is to use pipes as you would use lists in Haskell, which allows fun.sh functions to be easily composable via piping.</p>
<p>Anonymous functions in fun.sh are deceptively dumb. fun.sh will simply consume
and remember every argument given up until a “.”, “-&gt;”, “→” or “:” is found,
then reading each argument before evaluating the body of the function.</p>
<h3 id="a-few-demos">A few demos</h3>
<div class="sourceCode" id="cb1"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="co"># sum of a bash array</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="fu">boring_sum()</span> <span class="kw">{</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>    <span class="va">array</span><span class="op">=</span><span class="st">&quot;</span><span class="va">$@</span><span class="st">&quot;</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>    <span class="va">s</span><span class="op">=</span>0</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> i <span class="kw">in</span> <span class="va">$array</span><span class="kw">;</span> <span class="cf">do</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>        <span class="va">s</span><span class="op">=</span><span class="va">$(($i</span> <span class="op">+</span> <span class="va">$s))</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>    <span class="cf">done</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>    <span class="bu">echo</span> <span class="va">$s</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a><span class="kw">}</span></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="co"># sum of a bash array, piping the array in</span></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a><span class="fu">boring_sum_pipe()</span> <span class="kw">{</span></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a>    <span class="va">s</span><span class="op">=</span>0</span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a>    <span class="cf">while</span> <span class="bu">read</span> <span class="va">i</span><span class="kw">;</span> <span class="cf">do</span></span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a>        <span class="va">s</span><span class="op">=</span><span class="va">$(($s</span> <span class="op">+</span> <span class="va">$i))</span></span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a>    <span class="cf">done</span></span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a>    <span class="bu">echo</span> <span class="va">$s</span></span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a><span class="kw">}</span></span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true" tabindex="-1"></a><span class="co"># now, with fun.sh</span></span>
<span id="cb1-21"><a href="#cb1-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-22"><a href="#cb1-22" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> sum<span class="er">(</span><span class="kw">){</span> <span class="ex">foldl</span> λ a b . <span class="st">&#39;echo $(($a + $b))&#39;</span> } </span>
<span id="cb1-23"><a href="#cb1-23" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> list <span class="dt">{</span><span class="dv">1</span><span class="dt">..</span><span class="dv">100</span><span class="dt">}</span> <span class="kw">|</span> <span class="fu">sum</span></span>
<span id="cb1-24"><a href="#cb1-24" aria-hidden="true" tabindex="-1"></a><span class="ex">5050</span></span>
<span id="cb1-25"><a href="#cb1-25" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-26"><a href="#cb1-26" aria-hidden="true" tabindex="-1"></a><span class="co"># and to show off, </span></span>
<span id="cb1-27"><a href="#cb1-27" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-28"><a href="#cb1-28" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> product<span class="er">(</span><span class="kw">){</span> <span class="ex">foldl</span> λ a b . <span class="st">&#39;echo $(($a * $b))&#39;</span> }</span>
<span id="cb1-29"><a href="#cb1-29" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> list <span class="dt">{</span><span class="dv">1</span><span class="dt">..</span><span class="dv">20</span><span class="dt">}</span> <span class="kw">|</span> <span class="ex">product</span></span>
<span id="cb1-30"><a href="#cb1-30" aria-hidden="true" tabindex="-1"></a><span class="ex">2432902008176640000</span></span>
<span id="cb1-31"><a href="#cb1-31" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-32"><a href="#cb1-32" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> factorial<span class="er">(</span><span class="kw">){</span> <span class="ex">list</span> {1..<span class="va">$1</span>} <span class="kw">|</span> <span class="ex">product</span> }</span>
<span id="cb1-33"><a href="#cb1-33" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> factorial 5</span>
<span id="cb1-34"><a href="#cb1-34" aria-hidden="true" tabindex="-1"></a><span class="ex">120</span></span>
<span id="cb1-35"><a href="#cb1-35" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-36"><a href="#cb1-36" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> foobar<span class="er">(</span><span class="kw">){</span> <span class="ex">product</span> <span class="kw">|</span> <span class="ex">λ</span> l . <span class="st">&#39;list {1..$l}&#39;</span> <span class="kw">|</span> <span class="fu">sum</span> <span class="kw">|</span> <span class="fu">md5sum</span> }</span>
<span id="cb1-37"><a href="#cb1-37" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> list <span class="dt">{1</span><span class="op">,</span><span class="dt">2</span><span class="op">,</span><span class="dt">3}</span> <span class="kw">|</span> <span class="ex">foobar</span></span>
<span id="cb1-38"><a href="#cb1-38" aria-hidden="true" tabindex="-1"></a><span class="ex">fe9d26c3e620eeb69bd166c8be89fb8f</span>  <span class="at">-</span></span>
<span id="cb1-39"><a href="#cb1-39" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-40"><a href="#cb1-40" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> id<span class="er">(</span><span class="kw">){</span> <span class="ex">λ</span> x . <span class="st">&#39;$x&#39;</span> }</span>
<span id="cb1-41"><a href="#cb1-41" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> id <span class="op">&lt;&lt;&lt;</span> <span class="st">&#39;echo :)&#39;</span></span>
<span id="cb1-42"><a href="#cb1-42" aria-hidden="true" tabindex="-1"></a><span class="bu">:</span><span class="er">)</span></span>
<span id="cb1-43"><a href="#cb1-43" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-44"><a href="#cb1-44" aria-hidden="true" tabindex="-1"></a><span class="co"># Oh no, the boss wants me to calculate the sum of every integer between 400 and 500! Whatever shall I do?!</span></span>
<span id="cb1-45"><a href="#cb1-45" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> list <span class="dt">{</span><span class="dv">400</span><span class="dt">..</span><span class="dv">500</span><span class="dt">}</span> <span class="kw">|</span> <span class="ex">foldl</span> λ x y . <span class="st">&#39;echo $(($x + $y))&#39;</span></span>
<span id="cb1-46"><a href="#cb1-46" aria-hidden="true" tabindex="-1"></a><span class="ex">45450</span></span>
<span id="cb1-47"><a href="#cb1-47" aria-hidden="true" tabindex="-1"></a><span class="co"># Thanks fun.sh, I don&#39;t know where I would be without you.</span></span></code></pre></div>
<p><a href="https://github.com/mikeplus64/fun.sh">GitHub project</a>.</p></section>
  <footer><br />

<div id="disqus_thread"></div>
<script type="text/javascript">
  var disqus_shortname = 'quasimal';
  (function() {
    var dsq = document.createElement('script');
    dsq.type = 'text/javascript';
    dsq.async = true;
    dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
    (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
  })();
</script>
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
<a href="http://disqus.com" class="dsq-brlink">blog comments powered by <span class="logo-disqus">Disqus</span></a>
</footer>
</article>
]]></description>
    <pubDate>Mon, 21 May 2012 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/posts/2012-05-21-funsh.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>Level 0</title>
    <link>http://quasimal.com/projects/level_0.html</link>
    <description><![CDATA[<article class="project">
  <header>
    <h1>Level 0</h1>
    
    <clearfix></clearfix>
  </header>
  <section></section>
  
  <p>Moved to <a href="../posts/2012-02-25-level-0.html">../posts/2012-02-25-level-0.html</a>.</p>
  <script>
    window.location.href = "../posts/2012-02-25-level-0.html";
  </script>
  
</article>
]]></description>
    <pubDate>2012-04-29</pubDate>
    <guid>http://quasimal.com/projects/level_0.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>Level 0</title>
    <link>http://quasimal.com/posts/2012-02-25-level-0.html</link>
    <description><![CDATA[<article class="post">
  <header>
    <h1>Level 0</h1>
    <time datetime="2012-02-25">February 25, 2012  by Mike Ledger </time>
    <clearfix></clearfix>
  </header>
  <section><p><a href="https://github.com/mikeplus64/Level-0">GitHub project</a></p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/mJeEqRwLXsA?si=uMPV2heFbNcl2CC3" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen>
</iframe>
<p>Level 0 is a Snake II (think old-ish Nokia phones) clone written in Haskell,
using SDL. <code>cloc</code> tells me it’s 301 LOC, in addition to 49 comments which I’m
happy with.</p>
<h2 id="features">Features</h2>
<ul>
<li>it works</li>
<li>it’s fast</li>
<li>readable code (it’s readable to me!)</li>
<li>map loading</li>
<li>map editing</li>
<li>map saving</li>
<li>scoreboard</li>
</ul>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>GHC (tested with 7.0.3 and 7.4.1)</li>
<li>SDL from Hackage</li>
<li>SDL-ttf from Hackage</li>
<li>a font (by default tries to get /usr/share/fonts/TTF/TerminusBold.ttf)</li>
</ul>
<h2 id="installation-usage">Installation / usage</h2>
<p><code>$ make</code></p>
<p><code>$ bin/level_0 [ms between frames [path to map file]]</code></p>
<p>eg</p>
<p><code>$ bin/level_0 16 map</code></p>
<p>I don’t know if it’s buildable on Windows.</p>
<p>A map is a plain text file, the first 32 characters on the first 32 lines are read, and when there is an ‘x’, you will have a wall that kills your snake when hit.</p></section>
  <footer><br />

<div id="disqus_thread"></div>
<script type="text/javascript">
  var disqus_shortname = 'quasimal';
  (function() {
    var dsq = document.createElement('script');
    dsq.type = 'text/javascript';
    dsq.async = true;
    dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
    (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
  })();
</script>
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
<a href="http://disqus.com" class="dsq-brlink">blog comments powered by <span class="logo-disqus">Disqus</span></a>
</footer>
</article>
]]></description>
    <pubDate>Sat, 25 Feb 2012 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/posts/2012-02-25-level-0.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>The mandelbrot set having a bad day.</title>
    <link>http://quasimal.com/posts/2011-12-27-the-mandelbrot-set-having-a-ba.html</link>
    <description><![CDATA[Just a video I made.]]></description>
    <pubDate>Tue, 27 Dec 2011 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/posts/2011-12-27-the-mandelbrot-set-having-a-ba.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>Fractals and patterns.</title>
    <link>http://quasimal.com/posts/2011-12-24-fractalsandpatterns.html</link>
    <description><![CDATA[Some pretty pictures.]]></description>
    <pubDate>Sat, 24 Dec 2011 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/posts/2011-12-24-fractalsandpatterns.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>
<item>
    <title>I have a website!</title>
    <link>http://quasimal.com/posts/2011-12-23-ihaveawebsite.html</link>
    <description><![CDATA[zomg, first post]]></description>
    <pubDate>Fri, 23 Dec 2011 00:00:00 UT</pubDate>
    <guid>http://quasimal.com/posts/2011-12-23-ihaveawebsite.html</guid>
    <dc:creator>Mike Ledger</dc:creator>
</item>

    </channel>
</rss>
