eighty-twenty news feed for tag "meta"
2024-01-24T14:37:46+00:00
http://eighty-twenty.org/tag/meta/index.atom
tonyg
mikeb
TypeScript: Messages from Interfaces and back
2021-02-16T19:27:47+00:00
http://eighty-twenty.org/2021/02/16/typescript-interfaces-and-message-passing
tonyg
<p><strong>UPDATE:</strong> Full code available at
<a href="https://gist.github.com/tonyg/a1f57d5d52f4363ddf03d7a06c69788f">this gist</a>
(also embedded below).</p>
<hr />
<p>Say you have the following
<a href="https://www.typescriptlang.org/">TypeScript</a> interface <code class="language-plaintext highlighter-rouge">I</code> that you
want to invoke remotely by passing messages of type <code class="language-plaintext highlighter-rouge">M</code>; or that you
receive messages of type <code class="language-plaintext highlighter-rouge">M</code> and want to handle them using an object
of type <code class="language-plaintext highlighter-rouge">I</code>:</p>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="kr">interface</span> <span class="nx">I</span> <span class="p">{</span>
<span class="nx">m1</span><span class="p">(</span><span class="nx">a</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">b</span><span class="p">:</span> <span class="kr">number</span><span class="p">):</span> <span class="nx">boolean</span><span class="p">;</span>
<span class="nx">m2</span><span class="p">():</span> <span class="k">void</span><span class="p">;</span>
<span class="nx">m3</span><span class="p">(</span><span class="nx">n</span><span class="p">:</span> <span class="kr">number</span><span class="p">):</span> <span class="k">void</span><span class="p">;</span>
<span class="nx">m4</span><span class="p">(</span><span class="nx">x</span><span class="p">:</span> <span class="p">[</span><span class="kr">string</span><span class="p">,</span> <span class="kr">string</span><span class="p">]):</span> <span class="p">{</span> <span class="nl">k</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">j</span><span class="p">:</span> <span class="kr">string</span> <span class="p">};</span>
<span class="nx">m5</span><span class="p">(</span><span class="nx">a</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">b</span><span class="p">:</span> <span class="kr">string</span><span class="p">[]):</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">type</span> <span class="nx">M</span> <span class="o">=</span> <span class="p">{</span> <span class="na">selector</span><span class="p">:</span> <span class="dl">"</span><span class="s2">m1</span><span class="dl">"</span><span class="p">,</span> <span class="na">args</span><span class="p">:</span> <span class="p">[</span><span class="kr">string</span><span class="p">,</span> <span class="kr">number</span><span class="p">],</span> <span class="na">callback</span><span class="p">:</span> <span class="p">(</span><span class="na">result</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">)</span> <span class="o">=></span> <span class="k">void</span> <span class="p">}</span>
<span class="o">|</span> <span class="p">{</span> <span class="na">selector</span><span class="p">:</span> <span class="dl">"</span><span class="s2">m2</span><span class="dl">"</span><span class="p">,</span> <span class="na">args</span><span class="p">:</span> <span class="p">[],</span> <span class="na">callback</span><span class="p">:</span> <span class="p">(</span><span class="na">result</span><span class="p">:</span> <span class="k">void</span><span class="p">)</span> <span class="o">=></span> <span class="k">void</span> <span class="p">}</span>
<span class="o">|</span> <span class="p">{</span> <span class="na">selector</span><span class="p">:</span> <span class="dl">"</span><span class="s2">m3</span><span class="dl">"</span><span class="p">,</span> <span class="na">args</span><span class="p">:</span> <span class="p">[</span><span class="kr">number</span><span class="p">],</span> <span class="na">callback</span><span class="p">:</span> <span class="p">(</span><span class="na">result</span><span class="p">:</span> <span class="k">void</span><span class="p">)</span> <span class="o">=></span> <span class="k">void</span> <span class="p">}</span>
<span class="o">|</span> <span class="p">{</span> <span class="na">selector</span><span class="p">:</span> <span class="dl">"</span><span class="s2">m4</span><span class="dl">"</span><span class="p">,</span> <span class="na">args</span><span class="p">:</span> <span class="p">[[</span><span class="kr">string</span><span class="p">,</span> <span class="kr">string</span><span class="p">]],</span> <span class="na">callback</span><span class="p">:</span> <span class="p">(</span><span class="na">result</span><span class="p">:</span> <span class="p">{</span> <span class="na">k</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">j</span><span class="p">:</span> <span class="kr">string</span> <span class="p">})</span> <span class="o">=></span> <span class="k">void</span> <span class="p">}</span>
<span class="o">|</span> <span class="p">{</span> <span class="na">selector</span><span class="p">:</span> <span class="dl">"</span><span class="s2">m5</span><span class="dl">"</span><span class="p">,</span> <span class="na">args</span><span class="p">:</span> <span class="p">[</span><span class="kr">string</span><span class="p">,</span> <span class="kr">string</span><span class="p">[]],</span> <span class="na">callback</span><span class="p">:</span> <span class="p">(</span><span class="na">result</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="o">=></span> <span class="k">void</span> <span class="p">}</span></code></pre></figure>
<p>Keeping things type-safe looks really tedious! There’s obviously a
connection between <code class="language-plaintext highlighter-rouge">I</code> and <code class="language-plaintext highlighter-rouge">M</code>. Can we avoid writing them by hand?</p>
<p>Can we derive <code class="language-plaintext highlighter-rouge">M</code> from <code class="language-plaintext highlighter-rouge">I</code>? Can we derive <code class="language-plaintext highlighter-rouge">I</code> from <code class="language-plaintext highlighter-rouge">M</code>?</p>
<p>The answer to all of these questions is <strong>yes</strong>!<sup id="fnref:at-least-in-ts-4" role="doc-noteref"><a href="#fn:at-least-in-ts-4" class="footnote" rel="footnote">1</a></sup></p>
<p><strong>TL;DR</strong> TypeScript lets us define generic types <code class="language-plaintext highlighter-rouge">Messages</code> and
<code class="language-plaintext highlighter-rouge">Methods</code> such that <code class="language-plaintext highlighter-rouge">M = Messages<I></code> and <code class="language-plaintext highlighter-rouge">I = Methods<M></code>. Read on
for the details.</p>
<h3 id="interface--messages">Interface ⟶ Messages</h3>
<p>Let’s start with what, for me, has been the common case: given an
interface type, automatically produce the type of messages that can be
sent to implementors of the interface.</p>
<p>First, how do we want to represent messages?</p>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="kd">type</span> <span class="nx">Message</span><span class="o"><</span><span class="nx">Selector</span> <span class="kd">extends</span> <span class="nx">ValidSelector</span><span class="p">,</span> <span class="nx">Args</span> <span class="kd">extends</span> <span class="kr">any</span><span class="p">[],</span> <span class="nx">Result</span><span class="o">></span> <span class="o">=</span>
<span class="nx">Args</span> <span class="kd">extends</span> <span class="nx">never</span><span class="p">[]</span>
<span class="p">?</span> <span class="p">{</span> <span class="na">selector</span><span class="p">:</span> <span class="nx">Selector</span><span class="p">,</span> <span class="na">args</span><span class="p">:</span> <span class="p">[],</span> <span class="na">callback</span><span class="p">:</span> <span class="p">(</span><span class="na">result</span><span class="p">:</span> <span class="nx">Result</span><span class="p">)</span> <span class="o">=></span> <span class="k">void</span> <span class="p">}</span>
<span class="p">:</span> <span class="p">{</span> <span class="na">selector</span><span class="p">:</span> <span class="nx">Selector</span><span class="p">,</span> <span class="na">args</span><span class="p">:</span> <span class="nx">Args</span><span class="p">,</span> <span class="na">callback</span><span class="p">:</span> <span class="p">(</span><span class="na">result</span><span class="p">:</span> <span class="nx">Result</span><span class="p">)</span> <span class="o">=></span> <span class="k">void</span> <span class="p">}</span>
<span class="kd">type</span> <span class="nx">ValidSelector</span> <span class="o">=</span> <span class="kr">string</span> <span class="o">|</span> <span class="kr">number</span> <span class="o">|</span> <span class="nx">symbol</span></code></pre></figure>
<p>I’ve taken a leaf out of Smalltalk’s book, and made a message include
a <code class="language-plaintext highlighter-rouge">selector</code>, the name of the method the message intends to invoke,
and some <code class="language-plaintext highlighter-rouge">args</code>, the provided arguments to the method. The <code class="language-plaintext highlighter-rouge">Args
extends never[]</code> check is to help type inference deduce the empty
argument tuple: without it, the type system won’t complain about
missing arguments.</p>
<p>I’ve also added a <code class="language-plaintext highlighter-rouge">callback</code> to <code class="language-plaintext highlighter-rouge">Message</code>. The technique I describe
here can be further extended to “asynchronous” or callbackless
settings with minor modifications.</p>
<p>The next definition, of type <code class="language-plaintext highlighter-rouge">Messages<I></code>, is where the magic
happens. It expands to a union of <code class="language-plaintext highlighter-rouge">Message</code>s representing the methods
defined in <code class="language-plaintext highlighter-rouge">I</code>:</p>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="kd">type</span> <span class="nx">Messages</span><span class="o"><</span><span class="nx">I</span><span class="o">></span> <span class="o">=</span> <span class="nx">MessagesProduct</span><span class="o"><</span><span class="nx">I</span><span class="o">></span><span class="p">[</span><span class="kr">keyof</span> <span class="nx">I</span><span class="p">]</span>
<span class="kd">type</span> <span class="nx">MessagesProduct</span><span class="o"><</span><span class="nx">I</span><span class="o">></span> <span class="o">=</span> <span class="p">{</span>
<span class="p">[</span><span class="nx">K</span> <span class="k">in</span> <span class="kr">keyof</span> <span class="nx">I</span><span class="p">]:</span> <span class="p">(</span><span class="nx">I</span><span class="p">[</span><span class="nx">K</span><span class="p">]</span> <span class="kd">extends</span> <span class="p">(...</span><span class="na">args</span><span class="p">:</span> <span class="nx">infer</span> <span class="nx">P</span><span class="p">)</span> <span class="o">=></span> <span class="nx">infer</span> <span class="nx">Q</span> <span class="p">?</span> <span class="nx">Message</span><span class="o"><</span><span class="nx">K</span><span class="p">,</span> <span class="nx">P</span><span class="p">,</span> <span class="nx">Q</span><span class="o">></span> <span class="p">:</span> <span class="nx">never</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p>And that’s it! Here’s how it works:</p>
<ul>
<li>
<p><code class="language-plaintext highlighter-rouge">MessagesProduct</code> is a <a href="https://www.typescriptlang.org/docs/handbook/advanced-types.html#mapped-types">mapped type</a> that describes a modified
interface, where all (and only) the method properties of interface
<code class="language-plaintext highlighter-rouge">I</code> are rewritten to have a <code class="language-plaintext highlighter-rouge">Message</code> as their type, but keeping
the same key;</p>
</li>
<li>
<p>then, the <code class="language-plaintext highlighter-rouge">...[keyof I]</code> part in the definition of <code class="language-plaintext highlighter-rouge">Messages</code> uses
<a href="https://www.typescriptlang.org/docs/handbook/advanced-types.html#index-types">index types</a> to set up a <a href="https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#union-types">union type</a> built from all
the value types (“indexed access operator”) associated with all the
keys in <code class="language-plaintext highlighter-rouge">I</code> (“index type query operator”).</p>
</li>
</ul>
<h3 id="messages--interface">Messages ⟶ Interface</h3>
<p>Going in the other direction is simpler:</p>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="kd">type</span> <span class="nx">Methods</span><span class="o"><</span><span class="nx">M</span> <span class="kd">extends</span> <span class="p">{</span> <span class="na">selector</span><span class="p">:</span> <span class="nx">ValidSelector</span> <span class="p">}</span><span class="o">></span> <span class="o">=</span> <span class="p">{</span>
<span class="p">[</span><span class="nx">S</span> <span class="k">in</span> <span class="nx">M</span><span class="p">[</span><span class="dl">'</span><span class="s1">selector</span><span class="dl">'</span><span class="p">]]:</span> <span class="p">(</span>
<span class="nx">M</span> <span class="kd">extends</span> <span class="nx">Message</span><span class="o"><</span><span class="nx">S</span><span class="p">,</span> <span class="nx">infer</span> <span class="nx">P</span><span class="p">,</span> <span class="nx">infer</span> <span class="nx">R</span><span class="o">></span> <span class="p">?</span> <span class="p">(...</span><span class="nx">args</span><span class="p">:</span> <span class="nx">P</span><span class="p">)</span> <span class="o">=></span> <span class="na">R</span> <span class="p">:</span>
<span class="nx">never</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p>It’s a <a href="https://www.typescriptlang.org/docs/handbook/advanced-types.html#mapped-types">mapped type</a>, again, that maps union members that have
<code class="language-plaintext highlighter-rouge">Message</code> type to an appropriate function signature. It takes
advantage of TypeScript’s automatic distributivity: a union of
products gets rewritten to be a product of unions. Then, in the
<a href="https://www.typescriptlang.org/docs/handbook/advanced-types.html#conditional-types">conditional type</a> <code class="language-plaintext highlighter-rouge">M extends Message<...> ? ...</code>, it projects out
just exactly the member of interest again.<sup id="fnref:not-quite-sure" role="doc-noteref"><a href="#fn:not-quite-sure" class="footnote" rel="footnote">2</a></sup></p>
<p>This time we use the mapped type as-is instead of re-projecting it
into a union using indexed access like we did with <code class="language-plaintext highlighter-rouge">MessagesProduct</code>
above.</p>
<h3 id="type-safe-interpretation-of-messages">Type-safe interpretation of messages</h3>
<p>Now we have types for our interfaces, and types for the messages that
match them, can we write a type-safe generic <code class="language-plaintext highlighter-rouge">perform</code> function? Yes,
we can!</p>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="kd">function</span> <span class="nx">perform</span><span class="o"><</span><span class="nx">I</span> <span class="kd">extends</span> <span class="nx">Methods</span><span class="o"><</span><span class="nx">M</span><span class="o">></span><span class="p">,</span>
<span class="nx">S</span> <span class="kd">extends</span> <span class="nx">ValidSelector</span><span class="p">,</span>
<span class="nx">M</span> <span class="kd">extends</span> <span class="nx">Message</span><span class="o"><</span><span class="nx">S</span><span class="p">,</span> <span class="kr">any</span><span class="p">,</span> <span class="kr">any</span><span class="o">>></span><span class="p">(</span><span class="nx">i</span><span class="p">:</span> <span class="nx">I</span><span class="p">,</span> <span class="nx">m</span><span class="p">:</span> <span class="nx">M</span><span class="p">):</span> <span class="k">void</span>
<span class="p">{</span>
<span class="nx">m</span><span class="p">.</span><span class="nx">callback</span><span class="p">(</span><span class="nx">i</span><span class="p">[</span><span class="nx">m</span><span class="p">.</span><span class="nx">selector</span><span class="p">](...</span> <span class="nx">m</span><span class="p">.</span><span class="nx">args</span><span class="p">));</span>
<span class="p">}</span></code></pre></figure>
<h3 id="an-example">An example</h3>
<p>Given the above definition for <code class="language-plaintext highlighter-rouge">I</code>, actually using <code class="language-plaintext highlighter-rouge">Messages<I></code>
produces the following type definition<sup id="fnref:named-tuple-slots" role="doc-noteref"><a href="#fn:named-tuple-slots" class="footnote" rel="footnote">3</a></sup> (according
to <a href="#my-ide">the IDE that I use</a>):</p>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="kd">type</span> <span class="nx">M</span> <span class="o">=</span> <span class="nx">Message</span><span class="o"><</span><span class="dl">"</span><span class="s2">m1</span><span class="dl">"</span><span class="p">,</span> <span class="p">[</span><span class="nx">a</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">b</span><span class="p">:</span> <span class="kr">number</span><span class="p">],</span> <span class="nx">boolean</span><span class="o">></span>
<span class="o">|</span> <span class="nx">Message</span><span class="o"><</span><span class="dl">"</span><span class="s2">m2</span><span class="dl">"</span><span class="p">,</span> <span class="p">[],</span> <span class="k">void</span><span class="o">></span>
<span class="o">|</span> <span class="nx">Message</span><span class="o"><</span><span class="dl">"</span><span class="s2">m3</span><span class="dl">"</span><span class="p">,</span> <span class="p">[</span><span class="nx">n</span><span class="p">:</span> <span class="kr">number</span><span class="p">],</span> <span class="k">void</span><span class="o">></span>
<span class="o">|</span> <span class="nx">Message</span><span class="o"><</span><span class="dl">"</span><span class="s2">m4</span><span class="dl">"</span><span class="p">,</span> <span class="p">[</span><span class="nx">x</span><span class="p">:</span> <span class="p">[</span><span class="kr">string</span><span class="p">,</span> <span class="kr">string</span><span class="p">]],</span> <span class="p">{</span> <span class="na">k</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">j</span><span class="p">:</span> <span class="kr">string</span> <span class="p">}</span><span class="o">></span>
<span class="o">|</span> <span class="nx">Message</span><span class="o"><</span><span class="dl">"</span><span class="s2">m5</span><span class="dl">"</span><span class="p">,</span> <span class="p">[</span><span class="nx">a</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">b</span><span class="p">:</span> <span class="kr">string</span><span class="p">[]],</span> <span class="kr">number</span><span class="o">></span></code></pre></figure>
<p>Conversely, given the <code class="language-plaintext highlighter-rouge">M</code> from the top of the file, we get the
following for <code class="language-plaintext highlighter-rouge">Methods<M></code>:</p>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="kd">type</span> <span class="nx">I</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">m1</span><span class="p">:</span> <span class="p">(</span><span class="na">a</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="na">b</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="o">=></span> <span class="nx">boolean</span><span class="p">;</span>
<span class="nl">m2</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="k">void</span><span class="p">;</span>
<span class="nl">m3</span><span class="p">:</span> <span class="p">(</span><span class="na">n</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="o">=></span> <span class="k">void</span><span class="p">;</span>
<span class="nl">m4</span><span class="p">:</span> <span class="p">(</span><span class="na">x</span><span class="p">:</span> <span class="p">[</span><span class="kr">string</span><span class="p">,</span> <span class="kr">string</span><span class="p">])</span> <span class="o">=></span> <span class="p">{</span> <span class="na">k</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">j</span><span class="p">:</span> <span class="kr">string</span> <span class="p">};</span>
<span class="nl">m5</span><span class="p">:</span> <span class="p">(</span><span class="na">a</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="na">b</span><span class="p">:</span> <span class="kr">string</span><span class="p">[])</span> <span class="o">=></span> <span class="kr">number</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>Roundtripping works too: both <code class="language-plaintext highlighter-rouge">Methods<Messages<I>></code> and
<code class="language-plaintext highlighter-rouge">Messages<Methods<M>></code> give what you expect.</p>
<h3 id="typescript-is-really-cool">TypeScript is really cool</h3>
<p>It’s a fully-fledged, ergonomic realization of the research started by
<a href="https://samth.github.io/">Sam Tobin-Hochstadt</a>, who invented
<a href="https://docs.racket-lang.org/ts-guide/occurrence-typing.html">Occurrence Typing</a>,
the technology at the heart of TypeScript.</p>
<p>Then, building on the language itself, <a name="my-ide"></a>emacs with
<a href="https://github.com/ananthakumaran/tide#readme">tide</a>,
<a href="https://www.flycheck.org/en/latest/">flycheck</a>, and
<a href="https://company-mode.github.io/">company</a> makes for a <em>very</em> pleasant
IDE.<sup id="fnref:emacs-typescript-setup" role="doc-noteref"><a href="#fn:emacs-typescript-setup" class="footnote" rel="footnote">4</a></sup></p>
<p>Congratulations to Sam, whose ideas really have worked out amazingly
well, and to the TypeScript team for producing such a polished and
pleasant language.</p>
<hr />
<h3 id="appendix-full-code-implementing-this-idea">Appendix: Full code implementing this idea</h3>
<p>This module implements the idea described in this article, extended
with the notion of <code class="language-plaintext highlighter-rouge">EventMessage</code>s, which don’t have a callback.</p>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="c1">// This Tuple type (and tuple() function) is a hack to induce</span>
<span class="c1">// TypeScript to infer tuple types rather than array types. (Source:</span>
<span class="c1">// https://github.com/microsoft/TypeScript/issues/27179#issuecomment-422606990)</span>
<span class="c1">//</span>
<span class="c1">// Without it, [123, 'hi', true] will often get the type (string |</span>
<span class="c1">// number | boolean)[] instead of [number, string, boolean].</span>
<span class="c1">//</span>
<span class="k">export</span> <span class="kd">type</span> <span class="nx">Tuple</span> <span class="o">=</span> <span class="kr">any</span><span class="p">[]</span> <span class="o">|</span> <span class="p">[</span><span class="kr">any</span><span class="p">];</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">tuple</span> <span class="o">=</span> <span class="o"><</span><span class="nx">A</span> <span class="kd">extends</span> <span class="nx">Tuple</span><span class="o">></span><span class="p">(...</span> <span class="nx">args</span><span class="p">:</span> <span class="nx">A</span><span class="p">)</span> <span class="o">=></span> <span class="nx">args</span><span class="p">;</span>
<span class="c1">// Type ValidSelector captures TypeScript's notion of a valid object</span>
<span class="c1">// property name.</span>
<span class="c1">//</span>
<span class="k">export</span> <span class="kd">type</span> <span class="nx">ValidSelector</span> <span class="o">=</span> <span class="kr">string</span> <span class="o">|</span> <span class="kr">number</span> <span class="o">|</span> <span class="nx">symbol</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">type</span> <span class="nx">EventMessage</span><span class="o"><</span><span class="nx">Selector</span> <span class="kd">extends</span> <span class="nx">ValidSelector</span><span class="p">,</span> <span class="nx">Args</span> <span class="kd">extends</span> <span class="kr">any</span><span class="p">[]</span><span class="o">></span> <span class="o">=</span>
<span class="p">{</span> <span class="na">selector</span><span class="p">:</span> <span class="nx">Selector</span><span class="p">,</span> <span class="na">args</span><span class="p">:</span> <span class="nx">Args</span> <span class="p">};</span>
<span class="k">export</span> <span class="kd">type</span> <span class="nx">RequestMessage</span><span class="o"><</span><span class="nx">Selector</span> <span class="kd">extends</span> <span class="nx">ValidSelector</span><span class="p">,</span> <span class="nx">Args</span> <span class="kd">extends</span> <span class="kr">any</span><span class="p">[],</span> <span class="nx">Result</span> <span class="kd">extends</span> <span class="nx">Exclude</span><span class="o"><</span><span class="kr">any</span><span class="p">,</span> <span class="k">void</span><span class="o">>></span> <span class="o">=</span>
<span class="p">{</span> <span class="na">selector</span><span class="p">:</span> <span class="nx">Selector</span><span class="p">,</span> <span class="na">args</span><span class="p">:</span> <span class="nx">Args</span><span class="p">,</span> <span class="na">callback</span><span class="p">:</span> <span class="p">(</span><span class="na">result</span><span class="p">:</span> <span class="nx">Result</span><span class="p">)</span> <span class="o">=></span> <span class="k">void</span> <span class="p">};</span>
<span class="k">export</span> <span class="kd">type</span> <span class="nx">Message</span><span class="o"><</span><span class="nx">Selector</span> <span class="kd">extends</span> <span class="nx">ValidSelector</span><span class="p">,</span> <span class="nx">Args</span> <span class="kd">extends</span> <span class="kr">any</span><span class="p">[],</span> <span class="nx">Result</span><span class="o">></span> <span class="o">=</span>
<span class="k">void</span> <span class="kd">extends</span> <span class="nx">Result</span> <span class="p">?</span> <span class="nx">EventMessage</span><span class="o"><</span><span class="nx">Selector</span><span class="p">,</span> <span class="nx">Args</span><span class="o">></span> <span class="p">:</span> <span class="nx">RequestMessage</span><span class="o"><</span><span class="nx">Selector</span><span class="p">,</span> <span class="nx">Args</span><span class="p">,</span> <span class="nx">Result</span><span class="o">></span><span class="p">;</span>
<span class="c1">// Function message() is needed for similar reasons to tuple() above:</span>
<span class="c1">// to help TypeScript infer the correct literal type for the selector</span>
<span class="c1">// (as well as the arguments).</span>
<span class="c1">//</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">message</span> <span class="o">=</span> <span class="o"><</span><span class="nx">S</span> <span class="kd">extends</span> <span class="nx">ValidSelector</span><span class="p">,</span> <span class="nx">A</span> <span class="kd">extends</span> <span class="nx">Tuple</span><span class="p">,</span> <span class="nx">R</span><span class="o">></span><span class="p">(</span><span class="nx">m</span><span class="p">:</span> <span class="nx">Message</span><span class="o"><</span><span class="nx">S</span><span class="p">,</span> <span class="nx">A</span><span class="p">,</span> <span class="nx">R</span><span class="o">></span><span class="p">)</span> <span class="o">=></span> <span class="nx">m</span><span class="p">;</span>
<span class="kd">type</span> <span class="nx">MessagesProduct</span><span class="o"><</span><span class="nx">I</span><span class="p">,</span> <span class="nx">ContextArgs</span> <span class="kd">extends</span> <span class="kr">any</span><span class="p">[]</span><span class="o">></span> <span class="o">=</span> <span class="p">{</span>
<span class="p">[</span><span class="nx">K</span> <span class="k">in</span> <span class="kr">keyof</span> <span class="nx">I</span><span class="p">]:</span> <span class="p">(</span><span class="nx">I</span><span class="p">[</span><span class="nx">K</span><span class="p">]</span> <span class="kd">extends</span> <span class="p">(...</span><span class="na">args</span><span class="p">:</span> <span class="p">[...</span><span class="nx">ContextArgs</span><span class="p">,</span> <span class="p">...</span><span class="nx">infer</span> <span class="nx">P</span><span class="p">])</span> <span class="o">=></span> <span class="nx">infer</span> <span class="nx">Q</span>
<span class="p">?</span> <span class="nx">Message</span><span class="o"><</span><span class="nx">K</span><span class="p">,</span> <span class="nx">P</span><span class="p">,</span> <span class="nx">Q</span><span class="o">></span>
<span class="p">:</span> <span class="nx">never</span><span class="p">);</span>
<span class="p">};</span>
<span class="k">export</span> <span class="kd">type</span> <span class="nx">Messages</span><span class="o"><</span><span class="nx">I</span><span class="p">,</span> <span class="nx">ContextArgs</span> <span class="kd">extends</span> <span class="kr">any</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[]</span><span class="o">></span> <span class="o">=</span> <span class="nx">MessagesProduct</span><span class="o"><</span><span class="nx">I</span><span class="p">,</span> <span class="nx">ContextArgs</span><span class="o">></span><span class="p">[</span><span class="kr">keyof</span> <span class="nx">I</span><span class="p">];</span>
<span class="k">export</span> <span class="kd">type</span> <span class="nx">Methods</span><span class="o"><</span><span class="nx">M</span> <span class="kd">extends</span> <span class="p">{</span> <span class="na">selector</span><span class="p">:</span> <span class="nx">ValidSelector</span> <span class="p">},</span> <span class="nx">ContextArgs</span> <span class="kd">extends</span> <span class="kr">any</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[]</span><span class="o">></span> <span class="o">=</span> <span class="p">{</span>
<span class="p">[</span><span class="nx">S</span> <span class="k">in</span> <span class="nx">M</span><span class="p">[</span><span class="dl">'</span><span class="s1">selector</span><span class="dl">'</span><span class="p">]]:</span> <span class="p">(</span>
<span class="nx">M</span> <span class="kd">extends</span> <span class="nx">RequestMessage</span><span class="o"><</span><span class="nx">S</span><span class="p">,</span> <span class="nx">infer</span> <span class="nx">P</span><span class="p">,</span> <span class="nx">infer</span> <span class="nx">R</span><span class="o">></span>
<span class="p">?</span> <span class="p">(</span><span class="k">void</span> <span class="kd">extends</span> <span class="nx">R</span> <span class="p">?</span> <span class="nx">never</span> <span class="p">:</span> <span class="p">(...</span><span class="nx">args</span><span class="p">:</span> <span class="p">[...</span><span class="nx">ContextArgs</span><span class="p">,</span> <span class="p">...</span><span class="nx">P</span><span class="p">])</span> <span class="o">=></span> <span class="nx">R</span><span class="p">)</span>
<span class="p">:</span> <span class="p">(</span><span class="nx">M</span> <span class="kd">extends</span> <span class="nx">EventMessage</span><span class="o"><</span><span class="nx">S</span><span class="p">,</span> <span class="nx">infer</span> <span class="nx">P</span><span class="o">></span>
<span class="p">?</span> <span class="p">(...</span><span class="nx">args</span><span class="p">:</span> <span class="p">[...</span><span class="nx">ContextArgs</span><span class="p">,</span> <span class="p">...</span><span class="nx">P</span><span class="p">])</span> <span class="o">=></span> <span class="na">void</span>
<span class="p">:</span> <span class="nx">never</span><span class="p">));</span>
<span class="p">};</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nx">perform</span><span class="o"><</span><span class="nx">I</span> <span class="kd">extends</span> <span class="nx">Methods</span><span class="o"><</span><span class="nx">M</span><span class="p">,</span> <span class="nx">ContextArgs</span><span class="o">></span><span class="p">,</span>
<span class="nx">S</span> <span class="kd">extends</span> <span class="nx">ValidSelector</span><span class="p">,</span>
<span class="nx">M</span> <span class="kd">extends</span> <span class="nx">RequestMessage</span><span class="o"><</span><span class="nx">S</span><span class="p">,</span> <span class="nx">Tuple</span><span class="p">,</span> <span class="kr">any</span><span class="o">></span><span class="p">,</span>
<span class="nx">ContextArgs</span> <span class="kd">extends</span> <span class="kr">any</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[]</span><span class="o">></span>
<span class="p">(</span><span class="nx">i</span><span class="p">:</span> <span class="nx">I</span><span class="p">,</span> <span class="nx">m</span><span class="p">:</span> <span class="nx">M</span><span class="p">,</span> <span class="p">...</span><span class="nx">ctxt</span><span class="p">:</span> <span class="nx">ContextArgs</span><span class="p">):</span> <span class="p">(</span><span class="nx">M</span> <span class="kd">extends</span> <span class="nx">RequestMessage</span><span class="o"><</span><span class="nx">S</span><span class="p">,</span> <span class="nx">Tuple</span><span class="p">,</span> <span class="nx">infer</span> <span class="nx">R</span><span class="o">></span> <span class="p">?</span> <span class="nx">R</span> <span class="p">:</span> <span class="nx">never</span><span class="p">);</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nx">perform</span><span class="o"><</span><span class="nx">I</span> <span class="kd">extends</span> <span class="nx">Methods</span><span class="o"><</span><span class="nx">M</span><span class="p">,</span> <span class="nx">ContextArgs</span><span class="o">></span><span class="p">,</span>
<span class="nx">S</span> <span class="kd">extends</span> <span class="nx">ValidSelector</span><span class="p">,</span>
<span class="nx">M</span> <span class="kd">extends</span> <span class="nx">EventMessage</span><span class="o"><</span><span class="nx">S</span><span class="p">,</span> <span class="nx">Tuple</span><span class="o">></span><span class="p">,</span>
<span class="nx">ContextArgs</span> <span class="kd">extends</span> <span class="kr">any</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[]</span><span class="o">></span>
<span class="p">(</span><span class="nx">i</span><span class="p">:</span> <span class="nx">I</span><span class="p">,</span> <span class="nx">m</span><span class="p">:</span> <span class="nx">M</span><span class="p">,</span> <span class="p">...</span><span class="nx">ctxt</span><span class="p">:</span> <span class="nx">ContextArgs</span><span class="p">):</span> <span class="k">void</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nx">perform</span><span class="o"><</span><span class="nx">I</span> <span class="kd">extends</span> <span class="nx">Methods</span><span class="o"><</span><span class="nx">M</span><span class="p">,</span> <span class="nx">ContextArgs</span><span class="o">></span><span class="p">,</span>
<span class="nx">S</span> <span class="kd">extends</span> <span class="nx">ValidSelector</span><span class="p">,</span>
<span class="nx">M</span> <span class="kd">extends</span> <span class="nx">RequestMessage</span><span class="o"><</span><span class="nx">S</span><span class="p">,</span> <span class="nx">Tuple</span><span class="p">,</span> <span class="kr">any</span><span class="o">></span><span class="p">,</span>
<span class="nx">ContextArgs</span> <span class="kd">extends</span> <span class="kr">any</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[]</span><span class="o">></span>
<span class="p">(</span><span class="nx">i</span><span class="p">:</span> <span class="nx">I</span><span class="p">,</span> <span class="nx">m</span><span class="p">:</span> <span class="nx">M</span><span class="p">,</span> <span class="p">...</span><span class="nx">ctxt</span><span class="p">:</span> <span class="nx">ContextArgs</span><span class="p">):</span> <span class="kr">any</span>
<span class="p">{</span>
<span class="kd">const</span> <span class="nx">r</span> <span class="o">=</span> <span class="nx">i</span><span class="p">[</span><span class="nx">m</span><span class="p">.</span><span class="nx">selector</span><span class="p">](...</span><span class="nx">ctxt</span><span class="p">,</span> <span class="p">...</span> <span class="nx">m</span><span class="p">.</span><span class="nx">args</span><span class="p">);</span>
<span class="nx">m</span><span class="p">.</span><span class="nx">callback</span><span class="p">?.(</span><span class="nx">r</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">r</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<hr />
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:at-least-in-ts-4" role="doc-endnote">
<p>Well, at least in TypeScript v4.x, anyway. I
don’t know about earlier versions. <a href="#fnref:at-least-in-ts-4" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:not-quite-sure" role="doc-endnote">
<p>Actually I’ll admit to not being quite sure that
this is what’s <em>really</em> going on here. TypeScript’s unions feel a
bit murky: there’s been more than one occasion I’ve been surprised
at what a union-of-products has been automatically “simplified”
(?) into. <a href="#fnref:not-quite-sure" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:named-tuple-slots" role="doc-endnote">
<p>Hey, what’s going on with those named tuple
slots? I would have expected a tuple type like <code class="language-plaintext highlighter-rouge">[number, string]</code>
not to be able to have <em>names</em> attached to the slots, but it turns
out I’m wrong and the compiler at least internally propagates
names in some circumstances! It even re-uses them if you convert a
<code class="language-plaintext highlighter-rouge">Messages<I></code> back into an interface, <code class="language-plaintext highlighter-rouge">Methods<Messages<I>></code>… <a href="#fnref:named-tuple-slots" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:emacs-typescript-setup" role="doc-endnote">
<p>Here’s my <code class="language-plaintext highlighter-rouge">.emacs</code> TypeScript setup, based on the examples in the <code class="language-plaintext highlighter-rouge">tide</code> manual:</p>
<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nb">defun</span> <span class="nv">setup-tide-mode</span> <span class="p">()</span>
<span class="p">(</span><span class="nv">interactive</span><span class="p">)</span>
<span class="p">(</span><span class="nv">tide-setup</span><span class="p">)</span>
<span class="p">(</span><span class="nv">flycheck-mode</span> <span class="mi">+1</span><span class="p">)</span>
<span class="p">(</span><span class="k">setq</span> <span class="nv">flycheck-check-syntax-automatically</span> <span class="o">'</span><span class="p">(</span><span class="nv">save</span> <span class="nv">mode-enabled</span><span class="p">))</span>
<span class="p">(</span><span class="nv">eldoc-mode</span> <span class="mi">+1</span><span class="p">)</span>
<span class="p">(</span><span class="nv">tide-hl-identifier-mode</span> <span class="mi">+1</span><span class="p">)</span>
<span class="p">(</span><span class="nv">company-mode</span> <span class="mi">+1</span><span class="p">)</span>
<span class="p">(</span><span class="nv">local-set-key</span> <span class="p">(</span><span class="nv">kbd</span> <span class="s">"TAB"</span><span class="p">)</span> <span class="nf">#'</span><span class="nv">company-indent-or-complete-common</span><span class="p">)</span>
<span class="p">(</span><span class="nv">local-set-key</span> <span class="p">(</span><span class="nv">kbd</span> <span class="s">"C-<return>"</span><span class="p">)</span> <span class="nf">#'</span><span class="nv">tide-fix</span><span class="p">))</span>
<span class="p">(</span><span class="k">setq</span> <span class="nv">company-tooltip-align-annotations</span> <span class="no">t</span><span class="p">)</span>
<span class="p">(</span><span class="nv">add-hook</span> <span class="ss">'typescript-mode-hook</span> <span class="nf">#'</span><span class="nv">setup-tide-mode</span><span class="p">)</span>
</code></pre></div> </div>
<p><a href="#fnref:emacs-typescript-setup" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>
Time Division Multiplexing; or, How I am Learning to Stop Worrying and Love the Blog
2020-08-25T19:24:47+00:00
http://eighty-twenty.org/2020/08/25/time-division-multiplexing
tonyg
<p>I am doing some fascinating and rewarding contract work that makes
direct use of some of the skills I developed and knowledge I acquired
during my PhD studies. It’s bloody wonderful and I’m very lucky.</p>
<p>I’m even luckier that it’s not currently a full-time gig. This means I
have, in principle, plenty of time to pursue my own ideas. Pandemic
and family life permitting, of course.</p>
<p>While there’s a lot of joy in building things just for myself, it’s
also a lot of fun to share the things I make with others. So I’ve
decided I’ll aim to write more here about what I’m doing.</p>
Comments enabled
2010-04-17T23:42:37+00:00
http://eighty-twenty.org/2010/04/18/disqus_enabled
tonyg
<p>I’ve installed comments, courtesy of <a href="http://disqus.com/">disqus</a>. It
looks like a very cool system; certainly, installing it and getting it
running the way I wanted it was dead straightforward.</p>
<p><em>Update 2014-08-23</em>: I’ve <a href="/meta/about_comments.html">removed disqus
again</a>, in favour of
other methods of feedback.</p>