<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://devon7925.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://devon7925.github.io/" rel="alternate" type="text/html" /><updated>2025-10-13T16:48:17+00:00</updated><id>https://devon7925.github.io/feed.xml</id><title type="html">Devon7925’s site</title><subtitle>The personal site of Devon7925</subtitle><entry><title type="html">Abusing Typescipt’s Type System to Create Generic Typeguards</title><link href="https://devon7925.github.io/2025/01/12/generic_typeguards.html" rel="alternate" type="text/html" title="Abusing Typescipt’s Type System to Create Generic Typeguards" /><published>2025-01-12T22:46:20+00:00</published><updated>2025-01-12T22:46:20+00:00</updated><id>https://devon7925.github.io/2025/01/12/generic_typeguards</id><content type="html" xml:base="https://devon7925.github.io/2025/01/12/generic_typeguards.html"><![CDATA[<style type="text/css">
   /* Indent Formatting */
   ol {list-style-type: decimal;}
   ol ol { list-style-type: lower-alpha;}
   ol ol ol { list-style-type: lower-roman;}
   ol ol ol ol { list-style-type: upper-alpha;}
   ol ol ol ol ol { list-style-type: decimal;}
   ol ol ol ol ol ol { list-style-type: upper-roman;}
   /* https://www.w3schools.com/cssref/pr_list-style-type.asp */
   /* https://stackoverflow.com/questions/11445453/css-set-li-indent */
   /* https://stackoverflow.com/questions/13366820/how-do-you-make-lettered-lists-using-markdown */
</style>

<blockquote>
  <p>The following post is about creating verifiable generic typeguards in Typescript. I don’t actually recommend using this as it slows compilation times and is a bit of a hack.</p>
</blockquote>

<h2 id="typeguards">Typeguards</h2>

<p>  Typescript is a language that prides itself on allowing developers to write type safe code. One of the ways that Typescript allows developers to write type safe code is through the use of typeguards. Typeguards are functions that return a boolean value that indicates whether a value is of a certain type. For example, the following function is a typeguard that checks if a value is a string:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">isString</span><span class="p">(</span><span class="nx">value</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">):</span> <span class="nx">value</span> <span class="k">is</span> <span class="kr">string</span> <span class="p">{</span>
  <span class="k">return</span> <span class="k">typeof</span> <span class="nx">value</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">string</span><span class="dl">'</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This can then be used to check if a value is a string:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">value</span><span class="p">:</span> <span class="nx">unknown</span> <span class="o">=</span> <span class="nx">getSomeUntrustedValue</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">isString</span><span class="p">(</span><span class="nx">value</span><span class="p">))</span> <span class="p">{</span>
  <span class="c1">// typescript knows the value is a string so we can access the length property </span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">value</span><span class="p">.</span><span class="nx">length</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>  The problem with this approach is that it requires a new typeguard function to be written for each type that needs to be checked, and each written typeguard function can be wrong and will need to be updated every time the type is updated, but won’t warn the developer when this happens. I wanted to create something that would both make writing typeguards easier and would warn the developer when the typeguard is out of date.</p>

<h2 id="alternatives">Alternatives</h2>

<p>Note there are existing alternatives to my approach that are probably better:</p>
<ul>
  <li><a href="https://github.com/rhys-vdw/ts-auto-guard">ts-auto-guard</a>: Requires you to remember to run the tool every time you update your types.</li>
  <li><a href="ajv.js.org">ajv</a>: Limited union support</li>
</ul>

<h2 id="generic-typeguards">Generic Typeguards</h2>

<p>  Typescript has a powerful type system that allows for pretty complex behavior such as <a href="https://itnext.io/implementing-arithmetic-within-typescripts-type-system-a1ef140a6f6f">building arithmetic operations</a>. However it is also an erasable type system, meaning you can’t use the type system to do anything at runtime. This means that you can’t use the type system to create typeguards. However, you can use the type system to create verified typeguards.</p>

<p>  Below is a simple example of this concept. The function <code class="language-plaintext highlighter-rouge">isArrayMatchingTypeguard</code> takes a typeguard function and returns a typeguard function that checks if an array is of the type that the original typeguard function checks for.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">function</span> <span class="nx">isArrayMatchingTypeguard</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">typeguard</span><span class="p">:</span> <span class="p">(</span><span class="nx">x</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="k">is</span> <span class="nx">T</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="p">(</span><span class="nx">data</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">):</span> <span class="nx">data</span> <span class="k">is</span> <span class="nx">T</span><span class="p">[]</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">Array</span><span class="p">.</span><span class="nx">isArray</span><span class="p">(</span><span class="nx">data</span><span class="p">))</span> <span class="k">return</span> <span class="kc">false</span>
        <span class="k">return</span> <span class="nx">data</span><span class="p">.</span><span class="nx">every</span><span class="p">(</span><span class="nx">typeguard</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">const</span> <span class="nx">value</span><span class="p">:</span> <span class="nx">unknown</span> <span class="o">=</span> <span class="nx">getSomeUntrustedValue</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">isArrayMatchingTypeguard</span><span class="p">(</span><span class="nx">isString</span><span class="p">)(</span><span class="nx">value</span><span class="p">))</span> <span class="p">{</span>
  <span class="c1">// typescript knows the value is an array of strings so we can access the length property </span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">value</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">length</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The only thing left is to create typeguards for every basic type, and every way of combining types. The basic types are easy:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">function</span> <span class="nx">isNull</span><span class="p">(</span><span class="nx">value</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">):</span> <span class="nx">value</span> <span class="k">is</span> <span class="kc">null</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">value</span> <span class="o">===</span> <span class="kc">null</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">export</span> <span class="kd">function</span> <span class="nx">isUndefined</span><span class="p">(</span><span class="nx">value</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">):</span> <span class="nx">value</span> <span class="k">is</span> <span class="kc">undefined</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">value</span> <span class="o">===</span> <span class="kc">undefined</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">export</span> <span class="kd">function</span> <span class="nx">isBoolean</span><span class="p">(</span><span class="nx">value</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">):</span> <span class="nx">value</span> <span class="k">is</span> <span class="nx">boolean</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">typeof</span> <span class="nx">value</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">boolean</span><span class="dl">"</span>
<span class="p">}</span>

<span class="k">export</span> <span class="kd">function</span> <span class="nx">isNumber</span><span class="p">(</span><span class="nx">value</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">):</span> <span class="nx">value</span> <span class="k">is</span> <span class="kr">number</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">typeof</span> <span class="nx">value</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">number</span><span class="dl">"</span>
<span class="p">}</span>

<span class="k">export</span> <span class="kd">function</span> <span class="nx">isString</span><span class="p">(</span><span class="nx">value</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">):</span> <span class="nx">value</span> <span class="k">is</span> <span class="kr">string</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">typeof</span> <span class="nx">value</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span>
<span class="p">}</span>

<span class="k">export</span> <span class="kd">function</span> <span class="nx">isObject</span><span class="p">(</span><span class="nx">value</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">):</span> <span class="nx">value</span> <span class="k">is</span> <span class="p">{</span> <span class="p">[</span><span class="na">key</span><span class="p">:</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="nx">unknown</span> <span class="p">}</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">typeof</span> <span class="nx">value</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">object</span><span class="dl">"</span> <span class="o">&amp;&amp;</span> <span class="nx">value</span> <span class="o">!==</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="nb">Array</span><span class="p">.</span><span class="nx">isArray</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Although this is <em>technically</em> all the basic types, there is one more case that is important to add:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">function</span> <span class="nx">isLiteral</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="kr">any</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">literal</span><span class="p">:</span> <span class="nx">T</span><span class="p">):</span> <span class="p">(</span><span class="nx">value</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">value</span> <span class="k">is</span> <span class="nx">T</span> <span class="p">{</span>
    <span class="k">return</span> <span class="p">(</span><span class="nx">value</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">):</span> <span class="nx">value</span> <span class="k">is</span> <span class="nx">T</span> <span class="o">=&gt;</span> <span class="nx">value</span> <span class="o">===</span> <span class="nx">literal</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This allows for checking for literal types, which is important for checking for enums.</p>

<h2 id="object-typeguards">Object typeguards</h2>

<p>  The simple case of a map type object(<code class="language-plaintext highlighter-rouge">{[key: string]: A}</code>) is relatively simple:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">function</span> <span class="nx">isObjectWithValues</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">typeguard</span><span class="p">:</span> <span class="p">(</span><span class="nx">x</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="k">is</span> <span class="nx">T</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="p">(</span><span class="nx">data</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">):</span> <span class="nx">data</span> <span class="k">is</span> <span class="p">{</span> <span class="p">[</span><span class="na">key</span><span class="p">:</span> <span class="kr">string</span><span class="p">]:</span> <span class="nx">T</span> <span class="p">}</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">isObject</span><span class="p">(</span><span class="nx">data</span><span class="p">))</span> <span class="k">return</span> <span class="kc">false</span>
        <span class="k">return</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">values</span><span class="p">(</span><span class="nx">data</span><span class="p">).</span><span class="nx">every</span><span class="p">(</span><span class="nx">typeguard</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The more common object type you want to cover has set keys however, for example:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">Person</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="na">age</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
    <span class="nx">description</span><span class="p">?:</span> <span class="kr">string</span>
    <span class="na">favoriteNumbers</span><span class="p">:</span> <span class="kr">number</span><span class="p">[]</span>
<span class="p">}</span>
</code></pre></div></div>

<p>  It should be clear that this is not a trivial case to cover. You have to handle required and optional properties, have different typeguards for each property. Therefore I will have to show a could of utility types first. First is a way to filter which properties are optional and which are required:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">ForcedOptionalKey</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="p">{}</span><span class="o">&gt;</span> <span class="o">=</span> <span class="kr">keyof</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">T</span> <span class="k">as</span> <span class="kc">undefined</span> <span class="kd">extends</span> <span class="nx">T</span><span class="p">[</span><span class="nx">K</span><span class="p">]</span> <span class="p">?</span> <span class="nx">K</span> <span class="p">:</span> <span class="nx">never</span><span class="p">]:</span> <span class="nx">K</span> <span class="p">}</span> <span class="o">&amp;</span> <span class="kr">keyof</span> <span class="nx">T</span>
<span class="kd">type</span> <span class="nx">ForcedRequiredKey</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="p">{}</span><span class="o">&gt;</span> <span class="o">=</span> <span class="kr">keyof</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">T</span> <span class="k">as</span> <span class="kc">undefined</span> <span class="kd">extends</span> <span class="nx">T</span><span class="p">[</span><span class="nx">K</span><span class="p">]</span> <span class="p">?</span> <span class="nx">never</span> <span class="p">:</span> <span class="nx">K</span><span class="p">]:</span> <span class="nx">K</span> <span class="p">}</span> <span class="o">&amp;</span> <span class="kr">keyof</span> <span class="nx">T</span>
</code></pre></div></div>

<p>  This works by mapping the keys of an object to <code class="language-plaintext highlighter-rouge">never</code> to remove them iff the value is optional. We check if the value is optional by checking if <code class="language-plaintext highlighter-rouge">undefined</code> is a valid value via <code class="language-plaintext highlighter-rouge">undefined extends T[K]</code>. I then use the <code class="language-plaintext highlighter-rouge">keyof</code> operator to get the keys of the object. Because Typescript can no longer tell that these are keys of T after this operation I then use the <code class="language-plaintext highlighter-rouge">&amp;</code> operator to get the intersection with the keys of the object. These are then used in the following utility functions:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">PropertyKey</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">function</span> <span class="nx">hasOwnProperty</span><span class="o">&lt;</span><span class="nx">X</span> <span class="kd">extends</span> <span class="p">{},</span> <span class="nx">Y</span> <span class="kd">extends</span> <span class="nx">PropertyKey</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">obj</span><span class="p">:</span> <span class="nx">X</span><span class="p">,</span> <span class="nx">prop</span><span class="p">:</span> <span class="nx">Y</span><span class="p">):</span> <span class="nx">obj</span> <span class="k">is</span> <span class="nx">X</span> <span class="o">&amp;</span> <span class="nb">Record</span><span class="o">&lt;</span><span class="nx">Y</span><span class="p">,</span> <span class="nx">unknown</span><span class="o">&gt;</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nx">hasOwnProperty</span><span class="o">&lt;</span><span class="nx">V</span><span class="p">,</span> <span class="nx">X</span> <span class="kd">extends</span> <span class="p">{</span> <span class="p">[</span><span class="na">key</span><span class="p">:</span> <span class="nx">PropertyKey</span><span class="p">]:</span> <span class="nx">V</span> <span class="p">},</span> <span class="nx">Y</span> <span class="kd">extends</span> <span class="nx">PropertyKey</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">obj</span><span class="p">:</span> <span class="nx">X</span><span class="p">,</span> <span class="nx">prop</span><span class="p">:</span> <span class="nx">Y</span><span class="p">):</span> <span class="nx">obj</span> <span class="k">is</span> <span class="nx">X</span> <span class="o">&amp;</span> <span class="nb">Record</span><span class="o">&lt;</span><span class="nx">Y</span><span class="p">,</span> <span class="nx">V</span><span class="o">&gt;</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nx">hasOwnProperty</span><span class="o">&lt;</span><span class="nx">X</span> <span class="kd">extends</span> <span class="p">{},</span> <span class="nx">Y</span> <span class="kd">extends</span> <span class="nx">PropertyKey</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">obj</span><span class="p">:</span> <span class="nx">X</span><span class="p">,</span> <span class="nx">prop</span><span class="p">:</span> <span class="nx">Y</span><span class="p">):</span> <span class="nx">obj</span> <span class="k">is</span> <span class="nx">X</span> <span class="o">&amp;</span> <span class="nb">Record</span><span class="o">&lt;</span><span class="nx">Y</span><span class="p">,</span> <span class="nx">unknown</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">obj</span><span class="p">.</span><span class="nx">hasOwnProperty</span><span class="p">(</span><span class="nx">prop</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">export</span> <span class="kd">function</span> <span class="nx">checkRequiredProperty</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="p">{}</span><span class="o">&gt;</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">return</span> <span class="kd">function</span> <span class="o">&lt;</span><span class="nx">X</span> <span class="kd">extends</span> <span class="p">{},</span> <span class="nx">Y</span> <span class="kd">extends</span> <span class="nx">ForcedRequiredKey</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">,</span> <span class="nx">TG</span> <span class="kd">extends</span> <span class="kc">undefined</span> <span class="o">|</span> <span class="p">((</span><span class="nx">x</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="k">is</span> <span class="nx">T</span><span class="p">[</span><span class="nx">Y</span><span class="p">])</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">obj</span><span class="p">:</span> <span class="nx">X</span><span class="p">,</span> <span class="nx">prop</span><span class="p">:</span> <span class="nx">Y</span><span class="p">,</span> <span class="nx">typeGuard</span><span class="p">?:</span> <span class="nx">TG</span><span class="p">):</span> <span class="nx">obj</span> <span class="k">is</span> <span class="nx">X</span> <span class="o">&amp;</span> <span class="nb">Record</span><span class="o">&lt;</span><span class="nx">Y</span><span class="p">,</span> <span class="nx">TG</span> <span class="kd">extends</span> <span class="kc">undefined</span> <span class="p">?</span> <span class="nx">unknown</span> <span class="p">:</span> <span class="nx">T</span><span class="p">[</span><span class="nx">Y</span><span class="p">]</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">hasOwnProperty</span><span class="p">(</span><span class="nx">obj</span><span class="p">,</span> <span class="nx">prop</span><span class="p">))</span> <span class="k">return</span> <span class="kc">false</span>
        <span class="k">return</span> <span class="nx">typeGuard</span> <span class="p">?</span> <span class="nx">typeGuard</span><span class="p">(</span><span class="nx">obj</span><span class="p">[</span><span class="nx">prop</span><span class="p">])</span> <span class="p">:</span> <span class="kc">true</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">export</span> <span class="kd">function</span> <span class="nx">checkOptionalProperty</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="p">{}</span><span class="o">&gt;</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">return</span> <span class="kd">function</span> <span class="o">&lt;</span><span class="nx">X</span> <span class="kd">extends</span> <span class="p">{},</span> <span class="nx">Y</span> <span class="kd">extends</span> <span class="nx">ForcedOptionalKey</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">,</span> <span class="nx">TG</span> <span class="kd">extends</span> <span class="kc">undefined</span> <span class="o">|</span> <span class="p">((</span><span class="nx">x</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="k">is</span> <span class="nx">T</span><span class="p">[</span><span class="nx">Y</span><span class="p">])</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">obj</span><span class="p">:</span> <span class="nx">X</span><span class="p">,</span> <span class="nx">prop</span><span class="p">:</span> <span class="nx">Y</span><span class="p">,</span> <span class="nx">typeGuard</span><span class="p">?:</span> <span class="nx">TG</span><span class="p">):</span> <span class="nx">obj</span> <span class="k">is</span> <span class="nx">X</span> <span class="o">&amp;</span> <span class="nb">Record</span><span class="o">&lt;</span><span class="nx">Y</span><span class="p">,</span> <span class="nx">TG</span> <span class="kd">extends</span> <span class="kc">undefined</span> <span class="p">?</span> <span class="nx">unknown</span> <span class="p">:</span> <span class="p">(</span><span class="nx">T</span><span class="p">[</span><span class="nx">Y</span><span class="p">]</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">)</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">hasOwnProperty</span><span class="p">(</span><span class="nx">obj</span><span class="p">,</span> <span class="nx">prop</span><span class="p">))</span> <span class="k">return</span> <span class="kc">true</span>
        <span class="k">return</span> <span class="nx">typeGuard</span> <span class="p">?</span> <span class="nx">typeGuard</span><span class="p">(</span><span class="nx">obj</span><span class="p">[</span><span class="nx">prop</span><span class="p">])</span> <span class="p">:</span> <span class="kc">true</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>  <code class="language-plaintext highlighter-rouge">hasOwnProperty</code> is just a version of <code class="language-plaintext highlighter-rouge">obj.hasOwnProperty</code> that typeguards the object so the key can be used for the function implementation. These functions seem unneccessarily complex as they immediately return another function, but this is used to reduce the amount of generic parameters you need to pass in. Using these you can get the following typeguard:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">function</span> <span class="nx">autoTypeguard</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="p">{}</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">typeGuardRequiredKeys</span><span class="p">:</span> <span class="p">{</span> <span class="p">[</span><span class="nx">key</span> <span class="k">in</span> <span class="nx">ForcedRequiredKey</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">]</span><span class="o">-</span><span class="p">?:</span> <span class="p">(</span><span class="nx">d</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">d</span> <span class="k">is</span> <span class="nx">T</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span> <span class="p">},</span> <span class="nx">typeGuardOptionalKeys</span><span class="p">:</span> <span class="p">{</span> <span class="p">[</span><span class="nx">key</span> <span class="k">in</span> <span class="nx">ForcedOptionalKey</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">]</span><span class="o">-</span><span class="p">?:</span> <span class="p">(</span><span class="nx">d</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">d</span> <span class="k">is</span> <span class="nx">T</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span> <span class="p">}):</span> <span class="p">(</span><span class="nx">data</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">data</span> <span class="k">is</span> <span class="nx">CheckNotUnion</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">return</span> <span class="kd">function</span> <span class="p">(</span><span class="na">data</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">):</span> <span class="nx">data</span> <span class="k">is</span> <span class="nx">CheckNotUnion</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">data</span> <span class="o">===</span> <span class="kc">null</span> <span class="o">||</span> <span class="nx">data</span> <span class="o">===</span> <span class="kc">undefined</span> <span class="o">||</span> <span class="k">typeof</span> <span class="nx">data</span> <span class="o">!==</span> <span class="dl">"</span><span class="s2">object</span><span class="dl">"</span> <span class="o">||</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">isArray</span><span class="p">(</span><span class="nx">data</span><span class="p">))</span> <span class="k">return</span> <span class="kc">false</span>
        <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">key</span> <span class="k">in</span> <span class="nx">typeGuardRequiredKeys</span><span class="p">)</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">requiredKey</span> <span class="o">=</span> <span class="nx">key</span> <span class="k">as</span> <span class="nx">ForcedRequiredKey</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span>
            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">checkRequiredProperty</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">()(</span><span class="nx">data</span><span class="p">,</span> <span class="nx">requiredKey</span><span class="p">,</span> <span class="nx">typeGuardRequiredKeys</span><span class="p">[</span><span class="nx">requiredKey</span><span class="p">]))</span> <span class="k">return</span> <span class="kc">false</span>
        <span class="p">}</span>
        <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">key</span> <span class="k">in</span> <span class="nx">typeGuardOptionalKeys</span><span class="p">)</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">optionalKey</span> <span class="o">=</span> <span class="nx">key</span> <span class="k">as</span> <span class="nx">ForcedOptionalKey</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span>
            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">checkOptionalProperty</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">()(</span><span class="nx">data</span><span class="p">,</span> <span class="nx">optionalKey</span><span class="p">,</span> <span class="nx">typeGuardOptionalKeys</span><span class="p">[</span><span class="nx">optionalKey</span><span class="p">]))</span> <span class="k">return</span> <span class="kc">false</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="kc">true</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// Using typeguards directly can start to become unreadable so bringing them into their own function can help</span>
<span class="kd">const</span> <span class="nx">personTypeguard</span><span class="p">:</span> <span class="p">(</span><span class="nx">data</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">data</span> <span class="k">is</span> <span class="nx">Person</span> <span class="o">=</span> <span class="nx">autoTypeguard</span><span class="o">&lt;</span><span class="nx">Person</span><span class="o">&gt;</span><span class="p">({</span>
    <span class="na">name</span><span class="p">:</span> <span class="nx">isString</span><span class="p">,</span>
    <span class="na">age</span><span class="p">:</span> <span class="nx">isNumber</span><span class="p">,</span>
    <span class="na">favoriteNumbers</span><span class="p">:</span> <span class="nx">isArrayMatchingTypeguard</span><span class="p">(</span><span class="nx">isNumber</span><span class="p">)</span>
<span class="p">},</span> <span class="p">{</span>
    <span class="na">description</span><span class="p">:</span> <span class="nx">isString</span>
<span class="p">})</span>
</code></pre></div></div>

<p>  Here you can already start to see how you can chain these typeguards together to create more complex typeguards. You can also notice how this is automatically checked by the compiler. If you were to add a new required property to <code class="language-plaintext highlighter-rouge">Person</code> and forget to add it to the typeguard, or if you were to change the type of a property, the compiler would warn you that the typeguard is out of date. You might notice the <code class="language-plaintext highlighter-rouge">CheckNotUnion</code> type and it may seem unnecessary, but it is used to prevent the developer from accidentally using a union type in the typeguard. The typeguard would not be able to check for the union type correctly, and the entire point of this is to ensure you don’t accidentally mess up your typeguard. <code class="language-plaintext highlighter-rouge">CheckNotUnion</code> is defined as follows:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">CheckNotUnion</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nx">TuplifyUnion</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span> <span class="kd">extends</span> <span class="p">[</span><span class="nx">infer</span> <span class="nx">_</span><span class="p">]</span> <span class="p">?</span> <span class="nx">T</span> <span class="p">:</span> <span class="nx">unknown</span>
</code></pre></div></div>

<p>  This is relatively simple, it simply checks if there is only one type in the union, and if there is it returns that type, otherwise it returns <code class="language-plaintext highlighter-rouge">unknown</code>. But what is <code class="language-plaintext highlighter-rouge">TuplifyUnion</code>?</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">TuplifyUnion</span><span class="o">&lt;</span><span class="nx">T</span><span class="p">,</span> <span class="nx">L</span> <span class="o">=</span> <span class="nx">LastOf</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">,</span> <span class="nx">N</span> <span class="o">=</span> <span class="p">[</span><span class="nx">T</span><span class="p">]</span> <span class="kd">extends</span> <span class="p">[</span><span class="nx">never</span><span class="p">]</span> <span class="p">?</span> <span class="kc">true</span> <span class="p">:</span> <span class="kc">false</span><span class="o">&gt;</span> <span class="o">=</span> <span class="kc">true</span> <span class="kd">extends</span> <span class="nx">N</span> <span class="p">?</span> <span class="p">[]</span> <span class="p">:</span> <span class="nx">Push</span><span class="o">&lt;</span><span class="nx">TuplifyUnion</span><span class="o">&lt;</span><span class="nx">Exclude</span><span class="o">&lt;</span><span class="nx">T</span><span class="p">,</span> <span class="nx">L</span><span class="o">&gt;&gt;</span><span class="p">,</span> <span class="nx">L</span><span class="o">&gt;</span>
</code></pre></div></div>

<p>What?</p>

<p>  This is where the hacky part starts. It uses type parameter default values as pseudo-variables since the type system doesn’t have typical variable definition. Then it uses a recursive type to take out the last case of the union and push it to a tuple. This type of type transformation is not intended by typescript developers and union cases are supposed to be unordered and therefore their order could technically change at any time, although they haven’t while I’ve been using these functions. I’ll show the types it uses:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// This is a built in type</span>
<span class="kd">type</span> <span class="nx">Exclude</span><span class="o">&lt;</span><span class="nx">T</span><span class="p">,</span> <span class="nx">U</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nx">T</span> <span class="kd">extends</span> <span class="nx">U</span> <span class="p">?</span> <span class="nx">never</span> <span class="p">:</span> <span class="nx">T</span><span class="p">;</span>

<span class="kd">type</span> <span class="nx">Push</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="nx">unknown</span><span class="p">[],</span> <span class="nx">V</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">[...</span><span class="nx">T</span><span class="p">,</span> <span class="nx">V</span><span class="p">];</span>
<span class="kd">type</span> <span class="nx">UnionToIntersection</span><span class="o">&lt;</span><span class="nx">U</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">(</span><span class="nx">U</span> <span class="kd">extends</span> <span class="nx">unknown</span> <span class="p">?</span> <span class="p">(</span><span class="nx">k</span><span class="p">:</span> <span class="nx">U</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="k">void</span> <span class="p">:</span> <span class="nx">never</span><span class="p">)</span> <span class="kd">extends</span> <span class="p">((</span><span class="nx">k</span><span class="p">:</span> <span class="nx">infer</span> <span class="nx">I</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">)</span> <span class="p">?</span> <span class="nx">I</span> <span class="p">:</span> <span class="nx">never</span>
<span class="kd">type</span> <span class="nx">LastOf</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nx">UnionToIntersection</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="nx">unknown</span> <span class="p">?</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">T</span> <span class="p">:</span> <span class="nx">never</span><span class="o">&gt;</span> <span class="kd">extends</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">(</span><span class="nx">infer</span> <span class="nx">R</span><span class="p">)</span> <span class="p">?</span> <span class="nx">R</span> <span class="p">:</span> <span class="nx">never</span>
</code></pre></div></div>

<p>What?</p>

<p>  <code class="language-plaintext highlighter-rouge">Exclude</code> and <code class="language-plaintext highlighter-rouge">Push</code> are pretty simple, they are just removing a type from a union and adding a type to a tuple respectively.</p>

<p>  <code class="language-plaintext highlighter-rouge">UnionToIntersection</code> is a bit more complex. It converts a union type to an intersection by putting it as a function parameter and then inferring that same function parameter. This works because the union gets broken up in the first step(<code class="language-plaintext highlighter-rouge">A|B</code> becomes <code class="language-plaintext highlighter-rouge">((k: A) =&gt; void) | ((k: B) =&gt; void)</code>) which is interpretted specially as a function with multiple type definitions. Then the infer tries to get a single type due to it seeing a single function with different definitions and ends up making an intersection. The <code class="language-plaintext highlighter-rouge">extends unknown</code> is used to split up the union when mapping to a function so it doesn’t become <code class="language-plaintext highlighter-rouge">((k: A | B) =&gt; void)</code>.</p>

<p>  Finally <code class="language-plaintext highlighter-rouge">LastOf</code> first maps the union to a function and then creates an intersection. The function mapping is important because <code class="language-plaintext highlighter-rouge">UnionToIntersection&lt;string | number&gt;</code> is <code class="language-plaintext highlighter-rouge">never</code> but <code class="language-plaintext highlighter-rouge">UnionToIntersection&lt;() =&gt; string | () =&gt; number&gt;</code> is <code class="language-plaintext highlighter-rouge">() =&gt; string &amp; () =&gt; number</code>. This doesn’t make sense, but it works ¯_(ツ)_/¯. Then it infers the type parameter of the intersection function and this pulls out the last type.</p>

<h2 id="union-typeguards">Union typeguards</h2>

<p>  The simplest way to create a union typeguard is to just split the union into two parts and check each part. This can be done with the following function:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">function</span> <span class="nx">eitherType</span><span class="o">&lt;</span><span class="nx">A</span><span class="p">,</span> <span class="nx">B</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">aTypeGuard</span><span class="p">:</span> <span class="p">(</span><span class="nx">x</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="k">is</span> <span class="nx">A</span><span class="p">,</span> <span class="nx">bTypeGuard</span><span class="p">:</span> <span class="p">(</span><span class="nx">x</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="k">is</span> <span class="nx">B</span><span class="p">):</span> <span class="p">(</span><span class="nx">x</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="k">is</span> <span class="nx">A</span> <span class="o">|</span> <span class="nx">B</span> <span class="p">{</span>
    <span class="k">return</span> <span class="p">(</span><span class="nx">x</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">):</span> <span class="nx">x</span> <span class="k">is</span> <span class="nx">A</span> <span class="o">|</span> <span class="nx">B</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nx">aTypeGuard</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span> <span class="o">||</span> <span class="nx">bTypeGuard</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>  This doesn’t work so well for a union with more than two types. For that you need a more complex following function:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">TupleToTypeguards</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="nx">unknown</span><span class="p">[]</span><span class="o">&gt;</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">T</span><span class="p">]:</span> <span class="p">(</span><span class="na">data</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">data</span> <span class="k">is</span> <span class="nx">T</span><span class="p">[</span><span class="nx">K</span><span class="p">]</span> <span class="p">}</span>
<span class="kd">type</span> <span class="nx">NoArrayUnionTypeguards</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nx">TupleToTypeguards</span><span class="o">&lt;</span><span class="nx">TuplifyUnion</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;&gt;</span>

<span class="k">export</span> <span class="kd">function</span> <span class="nx">unionTypeguard</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">typeguards</span><span class="p">:</span> <span class="nx">NoArrayUnionTypeguards</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">):</span> <span class="p">(</span><span class="nx">data</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">data</span> <span class="k">is</span> <span class="nx">T</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nx">unionTypeguard</span><span class="o">&lt;</span><span class="nx">T</span><span class="p">,</span> <span class="nx">U</span> <span class="kd">extends</span> <span class="p">(</span><span class="nx">NoArrayUnionTypeguards</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">[</span><span class="kr">number</span><span class="p">][])</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">typeguards</span><span class="p">:</span> <span class="nx">U</span><span class="p">):</span> <span class="p">(</span><span class="nx">data</span><span class="p">:</span> <span class="nx">NoArrayUnionTypeguards</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">[</span><span class="kr">number</span><span class="p">][]</span> <span class="kd">extends</span> <span class="nx">U</span> <span class="p">?</span> <span class="nx">unknown</span> <span class="p">:</span> <span class="nx">T</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">data</span> <span class="k">is</span> <span class="nx">T</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nx">unionTypeguard</span><span class="o">&lt;</span><span class="nx">T</span><span class="p">,</span> <span class="nx">U</span> <span class="kd">extends</span> <span class="p">(</span><span class="nx">NoArrayUnionTypeguards</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">[</span><span class="kr">number</span><span class="p">][])</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">typeguards</span><span class="p">:</span> <span class="nx">U</span><span class="p">):</span> <span class="p">(</span><span class="nx">data</span><span class="p">:</span> <span class="nx">NoArrayUnionTypeguards</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">[</span><span class="kr">number</span><span class="p">][]</span> <span class="kd">extends</span> <span class="nx">U</span> <span class="p">?</span> <span class="nx">unknown</span> <span class="p">:</span> <span class="nx">T</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">data</span> <span class="k">is</span> <span class="nx">T</span> <span class="p">{</span>
    <span class="k">return</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">data</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">):</span> <span class="nx">data</span> <span class="k">is</span> <span class="nx">T</span> <span class="p">{</span>
        <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">typeguard</span> <span class="k">of</span> <span class="nx">typeguards</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="nx">typeguard</span><span class="p">(</span><span class="nx">data</span><span class="p">))</span> <span class="k">return</span> <span class="kc">true</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="kc">false</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>  This uses the previously explained type transformation to convert a union to a tuple of typeguards. This is then used to create a typeguard that checks if the data is of any of the types in the union. The multiple function signitures are used to make completions work if you define the typeguards in order, while still allowing you to pass in the typeguards out of order with some amount of checking.</p>

<h2 id="tuple-typeguards">Tuple typeguards</h2>

<p>  Tuple typeguards are relatively simple at this point. You can just use the following function:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">function</span> <span class="nx">isTupleMatchingTypeguards</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="kr">any</span><span class="p">[]</span><span class="o">&gt;</span><span class="p">(...</span><span class="nx">typeguards</span><span class="p">:</span> <span class="nx">TupleToTypeguards</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">):</span> <span class="p">(</span><span class="nx">x</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="k">is</span> <span class="nx">T</span> <span class="p">{</span>
    <span class="k">return</span> <span class="p">(</span><span class="nx">data</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">):</span> <span class="nx">data</span> <span class="k">is</span> <span class="nx">T</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">Array</span><span class="p">.</span><span class="nx">isArray</span><span class="p">(</span><span class="nx">data</span><span class="p">))</span> <span class="k">return</span> <span class="kc">false</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">length</span> <span class="o">!==</span> <span class="nx">typeguards</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="k">return</span> <span class="kc">false</span>
        <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">typeguards</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">typeguard</span> <span class="o">=</span> <span class="nx">typeguards</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span>
            <span class="k">if</span> <span class="p">(</span><span class="nx">typeguard</span> <span class="o">===</span> <span class="kc">undefined</span><span class="p">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">typeguard is undefined</span><span class="dl">"</span><span class="p">)</span>
            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">typeguard</span><span class="p">(</span><span class="nx">data</span><span class="p">[</span><span class="nx">i</span><span class="p">]))</span> <span class="k">return</span> <span class="kc">false</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="kc">true</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>  This function does not however finish covering all type cases as it misses the case of a tuple with a rest element such as <code class="language-plaintext highlighter-rouge">[A, ...B]</code>.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">function</span> <span class="nx">isSplitTupleMatchingTypeguards</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="kr">any</span><span class="p">[]</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">startTypeguard</span><span class="p">:</span> <span class="p">(</span><span class="nx">d</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">d</span> <span class="k">is</span> <span class="nx">T</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="nx">restTypeguard</span><span class="p">:</span> <span class="p">(</span><span class="nx">d</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">d</span> <span class="k">is</span> <span class="nx">Tail</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">):</span> <span class="p">(</span><span class="nx">x</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="k">is</span> <span class="nx">T</span> <span class="p">{</span>
    <span class="k">return</span> <span class="p">(</span><span class="nx">data</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">):</span> <span class="nx">data</span> <span class="k">is</span> <span class="nx">T</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">Array</span><span class="p">.</span><span class="nx">isArray</span><span class="p">(</span><span class="nx">data</span><span class="p">))</span> <span class="k">return</span> <span class="kc">false</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="k">return</span> <span class="kc">false</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">startTypeguard</span><span class="p">(</span><span class="nx">data</span><span class="p">[</span><span class="mi">0</span><span class="p">]))</span> <span class="k">return</span> <span class="kc">false</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">restTypeguard</span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">1</span><span class="p">)))</span> <span class="k">return</span> <span class="kc">false</span>
        <span class="k">return</span> <span class="kc">true</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="other-typeguards">Other typeguards</h2>

<p>  Another case this type of function can handle is for typeguarding a shared key from a union. Using these can reduce the duplicate code in some cases:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">TupleOmit</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="nx">unknown</span><span class="p">[],</span> <span class="nx">Key</span> <span class="kd">extends</span> <span class="kr">string</span><span class="o">&gt;</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">T</span><span class="p">]:</span> <span class="nx">Omit</span><span class="o">&lt;</span><span class="nx">T</span><span class="p">[</span><span class="nx">K</span><span class="p">],</span> <span class="nx">Key</span><span class="o">&gt;</span> <span class="p">}</span>
<span class="kd">type</span> <span class="nx">BetterOmit</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="p">{},</span> <span class="nx">K</span> <span class="kd">extends</span> <span class="kr">keyof</span> <span class="nx">T</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nx">TupleOmit</span><span class="o">&lt;</span><span class="nx">TuplifyUnion</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">,</span> <span class="nx">K</span> <span class="o">&amp;</span> <span class="kr">string</span><span class="o">&gt;</span><span class="p">[</span><span class="kr">number</span><span class="p">]</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nx">partialTypeguard</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="p">{},</span> <span class="nx">K</span> <span class="kd">extends</span> <span class="nx">ForcedRequiredKey</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;&gt;</span><span class="p">(</span><span class="nx">key</span><span class="p">:</span> <span class="nx">K</span><span class="p">,</span> <span class="nx">partialTypeguard</span><span class="p">:</span> <span class="p">(</span><span class="nx">data</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">data</span> <span class="k">is</span> <span class="nx">T</span><span class="p">[</span><span class="nx">K</span><span class="p">],</span> <span class="nx">restTypeguard</span><span class="p">:</span> <span class="p">(</span><span class="nx">data</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">data</span> <span class="k">is</span> <span class="nx">BetterOmit</span><span class="o">&lt;</span><span class="nx">T</span><span class="p">,</span> <span class="nx">K</span><span class="o">&gt;</span><span class="p">):</span> <span class="p">(</span><span class="nx">data</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">data</span> <span class="k">is</span> <span class="nx">T</span> <span class="p">{</span>
    <span class="k">return</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">data</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">):</span> <span class="nx">data</span> <span class="k">is</span> <span class="nx">T</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">data</span> <span class="o">===</span> <span class="kc">null</span> <span class="o">||</span> <span class="k">typeof</span> <span class="nx">data</span> <span class="o">!==</span> <span class="dl">"</span><span class="s2">object</span><span class="dl">"</span> <span class="o">||</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">isArray</span><span class="p">(</span><span class="nx">data</span><span class="p">))</span> <span class="k">return</span> <span class="kc">false</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">hasOwnProperty</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="nx">key</span><span class="p">))</span> <span class="k">return</span> <span class="kc">false</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">partialTypeguard</span><span class="p">(</span><span class="nx">data</span><span class="p">[</span><span class="nx">key</span><span class="p">]))</span> <span class="k">return</span> <span class="kc">false</span>
        <span class="kd">let</span> <span class="nx">datacopy</span> <span class="o">=</span> <span class="nx">structuredClone</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span>
        <span class="k">delete</span> <span class="nx">datacopy</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span>
        <span class="k">return</span> <span class="nx">restTypeguard</span><span class="p">(</span><span class="nx">datacopy</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">export</span> <span class="kd">function</span> <span class="nx">partialTypeguardOptional</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="p">{},</span> <span class="nx">K</span> <span class="kd">extends</span> <span class="nx">ForcedOptionalKey</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;&gt;</span><span class="p">(</span><span class="nx">key</span><span class="p">:</span> <span class="nx">K</span><span class="p">,</span> <span class="nx">partialTypeguard</span><span class="p">:</span> <span class="p">(</span><span class="nx">data</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">data</span> <span class="k">is</span> <span class="nx">T</span><span class="p">[</span><span class="nx">K</span><span class="p">],</span> <span class="nx">restTypeguard</span><span class="p">:</span> <span class="p">(</span><span class="nx">data</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">data</span> <span class="k">is</span> <span class="nx">BetterOmit</span><span class="o">&lt;</span><span class="nx">T</span><span class="p">,</span> <span class="nx">K</span><span class="o">&gt;</span><span class="p">):</span> <span class="p">(</span><span class="nx">data</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">data</span> <span class="k">is</span> <span class="nx">T</span> <span class="p">{</span>
    <span class="k">return</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">data</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">):</span> <span class="nx">data</span> <span class="k">is</span> <span class="nx">T</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">data</span> <span class="o">===</span> <span class="kc">null</span> <span class="o">||</span> <span class="k">typeof</span> <span class="nx">data</span> <span class="o">!==</span> <span class="dl">"</span><span class="s2">object</span><span class="dl">"</span> <span class="o">||</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">isArray</span><span class="p">(</span><span class="nx">data</span><span class="p">))</span> <span class="k">return</span> <span class="kc">false</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">hasOwnProperty</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="nx">key</span><span class="p">))</span> <span class="k">return</span> <span class="kc">true</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">partialTypeguard</span><span class="p">(</span><span class="nx">data</span><span class="p">[</span><span class="nx">key</span><span class="p">]))</span> <span class="k">return</span> <span class="kc">false</span>
        <span class="kd">let</span> <span class="nx">datacopy</span> <span class="o">=</span> <span class="nx">structuredClone</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span>
        <span class="k">delete</span> <span class="nx">datacopy</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span>
        <span class="k">return</span> <span class="nx">restTypeguard</span><span class="p">(</span><span class="nx">datacopy</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>An example of when these would be useful is when you have a type like this:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">Request</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">GET</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">url</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">token</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">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">POST</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">url</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">token</span><span class="p">?:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="na">body</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">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">DELETE</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">url</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">token</span><span class="p">?:</span> <span class="kr">string</span>
<span class="p">}</span>
</code></pre></div></div>

<p>By using the above functions you can avoid duplicating the <code class="language-plaintext highlighter-rouge">url</code> and <code class="language-plaintext highlighter-rouge">token</code> typeguards like so:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">longRequestTypeguard</span><span class="p">:</span> <span class="p">(</span><span class="nx">data</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">data</span> <span class="k">is</span> <span class="nx">Request</span> <span class="o">=</span> <span class="nx">unionTypeguard</span><span class="o">&lt;</span><span class="nx">Request</span><span class="o">&gt;</span><span class="p">([</span>
    <span class="nx">autoTypeguard</span><span class="o">&lt;</span><span class="nx">TuplifyUnion</span><span class="o">&lt;</span><span class="nx">Request</span><span class="o">&gt;</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">&gt;</span><span class="p">({</span>
        <span class="na">type</span><span class="p">:</span> <span class="nx">isLiteral</span><span class="p">(</span><span class="dl">"</span><span class="s2">GET</span><span class="dl">"</span><span class="p">),</span>
        <span class="na">url</span><span class="p">:</span> <span class="nx">isString</span><span class="p">,</span>
    <span class="p">},</span> <span class="p">{</span>
        <span class="na">token</span><span class="p">:</span> <span class="nx">isString</span><span class="p">,</span>
    <span class="p">}),</span>
    <span class="nx">autoTypeguard</span><span class="o">&lt;</span><span class="nx">TuplifyUnion</span><span class="o">&lt;</span><span class="nx">Request</span><span class="o">&gt;</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">&gt;</span><span class="p">({</span>
        <span class="na">type</span><span class="p">:</span> <span class="nx">isLiteral</span><span class="p">(</span><span class="dl">"</span><span class="s2">POST</span><span class="dl">"</span><span class="p">),</span>
        <span class="na">url</span><span class="p">:</span> <span class="nx">isString</span><span class="p">,</span>
        <span class="na">body</span><span class="p">:</span> <span class="nx">isString</span><span class="p">,</span>
    <span class="p">},</span> <span class="p">{</span>
        <span class="na">token</span><span class="p">:</span> <span class="nx">isString</span><span class="p">,</span>
    <span class="p">}),</span>
    <span class="nx">autoTypeguard</span><span class="o">&lt;</span><span class="nx">TuplifyUnion</span><span class="o">&lt;</span><span class="nx">Request</span><span class="o">&gt;</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="o">&gt;</span><span class="p">({</span>
        <span class="na">type</span><span class="p">:</span> <span class="nx">isLiteral</span><span class="p">(</span><span class="dl">"</span><span class="s2">DELETE</span><span class="dl">"</span><span class="p">),</span>
        <span class="na">url</span><span class="p">:</span> <span class="nx">isString</span><span class="p">,</span>
    <span class="p">},</span> <span class="p">{</span>
        <span class="na">token</span><span class="p">:</span> <span class="nx">isString</span><span class="p">,</span>
    <span class="p">}),</span>
<span class="p">])</span>

<span class="kd">type</span> <span class="nx">PartialRequest</span> <span class="o">=</span> <span class="nx">BetterOmit</span><span class="o">&lt;</span><span class="nx">BetterOmit</span><span class="o">&lt;</span><span class="nx">Request</span><span class="p">,</span> <span class="dl">"</span><span class="s2">url</span><span class="dl">"</span><span class="o">&gt;</span><span class="p">,</span> <span class="dl">"</span><span class="s2">token</span><span class="dl">"</span><span class="o">&gt;</span>
<span class="kd">const</span> <span class="nx">shortRequestTypeguard</span><span class="p">:</span> <span class="p">(</span><span class="nx">data</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">data</span> <span class="k">is</span> <span class="nx">Request</span> <span class="o">=</span> <span class="nx">partialTypeguard</span><span class="p">(</span><span class="dl">"</span><span class="s2">url</span><span class="dl">"</span><span class="p">,</span> <span class="nx">isString</span><span class="p">,</span>
    <span class="nx">partialTypeguardOptional</span><span class="p">(</span><span class="dl">"</span><span class="s2">token</span><span class="dl">"</span><span class="p">,</span> <span class="nx">isString</span><span class="p">,</span> <span class="nx">unionTypeguard</span><span class="p">([</span>
        <span class="nx">autoTypeguard</span><span class="o">&lt;</span><span class="nx">TuplifyUnion</span><span class="o">&lt;</span><span class="nx">PartialRequest</span><span class="o">&gt;</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">&gt;</span><span class="p">({</span>
            <span class="na">type</span><span class="p">:</span> <span class="nx">isLiteral</span><span class="p">(</span><span class="dl">"</span><span class="s2">GET</span><span class="dl">"</span><span class="p">),</span>
        <span class="p">},</span> <span class="p">{}),</span>
        <span class="nx">autoTypeguard</span><span class="o">&lt;</span><span class="nx">TuplifyUnion</span><span class="o">&lt;</span><span class="nx">PartialRequest</span><span class="o">&gt;</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">&gt;</span><span class="p">({</span>
            <span class="na">type</span><span class="p">:</span> <span class="nx">isLiteral</span><span class="p">(</span><span class="dl">"</span><span class="s2">POST</span><span class="dl">"</span><span class="p">),</span>
            <span class="na">body</span><span class="p">:</span> <span class="nx">isString</span><span class="p">,</span>
        <span class="p">},</span> <span class="p">{}),</span>
        <span class="nx">autoTypeguard</span><span class="o">&lt;</span><span class="nx">TuplifyUnion</span><span class="o">&lt;</span><span class="nx">PartialRequest</span><span class="o">&gt;</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="o">&gt;</span><span class="p">({</span>
            <span class="na">type</span><span class="p">:</span> <span class="nx">isLiteral</span><span class="p">(</span><span class="dl">"</span><span class="s2">DELETE</span><span class="dl">"</span><span class="p">),</span>
        <span class="p">},</span> <span class="p">{}),</span>
    <span class="p">])))</span>
</code></pre></div></div>

<p>As you can see, the <code class="language-plaintext highlighter-rouge">shortRequestTypeguard</code> is much more readable and shorter than the <code class="language-plaintext highlighter-rouge">longRequestTypeguard</code>.</p>

<h2 id="conclusion">Conclusion</h2>

<p>  This shows it is possible to generate verified typeguards in vanilla typescript. However, not only does it use abuse niche features of the type system that could change, but it also still requires a lot of boilerplate to be written. It has been somewhat useful for me, but I wouldn’t neccessarily recommend it for others. If you want to see a project that uses this, you can check out my <a href="https://github.com/Devon7925/overwatch_patch_compare">Overwatch patch compare tool</a>.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Neural Networks for Rating Systems</title><link href="https://devon7925.github.io/2024/07/06/neural_rating_system.html" rel="alternate" type="text/html" title="Neural Networks for Rating Systems" /><published>2024-07-06T22:46:20+00:00</published><updated>2024-07-06T22:46:20+00:00</updated><id>https://devon7925.github.io/2024/07/06/neural_rating_system</id><content type="html" xml:base="https://devon7925.github.io/2024/07/06/neural_rating_system.html"><![CDATA[<style type="text/css">
   /* Indent Formatting */
   ol {list-style-type: decimal;}
   ol ol { list-style-type: lower-alpha;}
   ol ol ol { list-style-type: lower-roman;}
   ol ol ol ol { list-style-type: upper-alpha;}
   ol ol ol ol ol { list-style-type: decimal;}
   ol ol ol ol ol ol { list-style-type: upper-roman;}
   /* https://www.w3schools.com/cssref/pr_list-style-type.asp */
   /* https://stackoverflow.com/questions/11445453/css-set-li-indent */
   /* https://stackoverflow.com/questions/13366820/how-do-you-make-lettered-lists-using-markdown */
</style>

<h2 id="problem-statement">Problem Statement</h2>

<p> Conventional rating systems such as Elo, Glicko, and Trueskill are fundementally designed for games with low input randomness. By that I mean that they decrease in accuracy for games in which the game starts out differently each time it is played. However, many modern video games using these types of systems have very high input randomness due to their inclusion of factors such as random gamemodes, random maps, and chosen characters for themselves, their teammates or their enemies. Although the impact of these factors can be somewhat mitigated by factors such as a pick/ban system, or <a href="#enabling-disabling-character-swapping-from-a-game-design-perspective">allowing swapping characters</a>, these have their own downsides such as lower match variety and <a href="#the-effects-of-swapping">counterswapping</a>.</p>

<h2 id="idea">Idea</h2>

<p> Neural networks have increasingly been shown to be capable of solving almost arbitrarily complex problems given sufficent data. The fundemental idea proposed here is to use a neural network to predict match outcomes based on all data possibly available at matchmaking time, such as player skill and character picks, map, and side that goes first in the case of an asymmetrical gamemode. This predictor can then be used as a heuristic to find well balanced matchups.</p>

<h2 id="design">Design</h2>

<h3 id="evaluator">Evaluator</h3>

<p>I will not go too deep into the design of how the neural network would work because:</p>

<ol>
  <li>I am not an expert in the machine learning space</li>
  <li>The precise design would depend on the game it would be used for</li>
  <li>I have not put in the work to test this theory and do not know where I could get the public data neccessary to test it</li>
</ol>

<p>However the layers I would propose are:</p>

<ol>
  <li>A layer for inputs where character picks and maps are encoded as a one-hot vector</li>
  <li>An encoding layer for characters and maps
    <ul>
      <li>Ideally for new characters and maps this would be the only layer that would be needed to train.</li>
    </ul>
  </li>
  <li>A layer combining character and skill rating information to account for characters that play differently at different skill levels. This can also be used if using <a href="#multiple-mmr">multiple mmr</a> to encode play styles.</li>
  <li>Some layers using an attention like mechanism so that character map information can be combined to account for synergies and counters.</li>
  <li>A fully connected dense layer connected to an output neuron with a sigmoid(or similar) activation function.</li>
</ol>

<h3 id="other-factors">Other factors</h3>

<p> When using this system you probably want to use character specific mmr instead of a role based or global mmr. The neural network may interpret character mmrs differently, and using character specific mmr should lead to better matchmaking. This also has other potential positive impacts on player experience (see <a href="#opening-up-character-design">Opening up character design</a>).</p>

<h2 id="using-backpropagation">Using backpropagation</h2>

<p> One benefit of this system is that via backpropagation the impact of changing inputs on the matchup can be measured. This has an additional cost of approximately double that of the evaluation function but has a number of potential benefits.</p>

<h3 id="using-backpropagation-to-measure-player-impact">Using backpropagation to measure player impact</h3>

<p> One way backpropagation can be used is to measure the impact of changing a player’s input mmr on the expected match result. This can be used as a heuristic for measuring a player’s impact.</p>

<h4 id="matchmaking-uses">Matchmaking uses</h4>

<p> It could be used for matchmaking to prevent matches where a player would have a very small impact, as these matches can be frustrating for the player. Similarly it could also be used to prevent matches where a single player has too high of an impact, as these matches can be both frustrating for the high impact player as it becomes carry or lose, as well as frustrating for other players as their impact can only be seen if the high impact player neither carries nor underperforms.</p>

<h4 id="rating-update-uses">Rating update uses</h4>

<p> It could also be used to update player mmr after the match. If a player had a small impact on a match it makes sense that that game is less good data for updating mmr compared to a game where a player had a large impact on a match. This should allow players to find their correct mmr faster.</p>

<p> This also has the benefit of being able to correctly separate the mmr of players who play together due to a grouping system. Currently, as far as I know the only system that helps with this is the rating deviation system, which can help adjust stabalized mmr less when they are grouped with players with unstabalized mmr. It does not however account for new players of different skill playing together, which can lead to unbalanced matches if these players ever play without eachother.</p>

<h3 id="using-backpropagation-to-find-fair-matches-faster">Using backpropagation to find fair matches faster</h3>

<p> Backpropagation can also be used to evaluate the impact of character pick or map on the matchup. This can be used in the case where a guess occurs for a possible match that is not balanced. By reading the gradients of the one-hot vectors, maps or character picks that would lead to a better matchup can be determined. In the case of maps, the matchup can be immediately reevaluated with the more fair map. In the case of character picks, the player can be swapped out for another player of the changed character pick. Alternatively, if the player is allowed to queue for multiple characters at a time, you can swap only their character, which may be nessecary in the case of groups when you can’t just swap a player out.</p>

<h2 id="enabling-disabling-character-swapping-from-a-game-design-perspective">Enabling disabling character swapping from a game design perspective</h2>

<p>Some character based games allow the player to swap characters mid game, whereas others don’t. They both have upsides and downsides I’ll list here for reference.</p>

<h3 id="the-effects-of-swapping">The Effects of Swapping</h3>

<ol>
  <li>Players can change up gameplay if they realize they aren’t enjoying current gameplay</li>
  <li>Allows for more unique character design as players can swap to avoid bad matchups</li>
  <li>Competitive players will swap to good matchups
    <ol>
      <li>Notably good matchups are not the same as fun matchups or characters they enjoy playing, likely decreasing net enjoyment</li>
      <li>Their enemies are likely to also swap to get a better matchup in response, likely leading to even more swaps</li>
      <li>Players recognize bad situational character picks in their teammates
        <ol>
          <li>This increases toxicity as people complain about their teammates character picks</li>
          <li>This makes people feel like they don’t have an impact as they feel they are losing due to their teammate’s character pick</li>
          <li>This makes people who just want to play a character have less fun as they have to deal with their teammate’s complaints</li>
        </ol>
      </li>
      <li>Good matchups are more common for “meta” characters leading to players feeling like they have to play “meta” characters instead of characters they enjoy or lose.</li>
      <li>Even players who simply want to avoid bad matchups then end up playing characters that have no extremely bad matchups, which are often either less unique, more “meta” or tanky characters that tend to be less fun</li>
    </ol>
  </li>
  <li>Unique characters can cause extreme power in certain situations leading to characters being pick or lose in certain situations. (ex. players swapping to a character to get out of spawn faster, certain maps)
    <ol>
      <li>This can lead to misleading statistics as characters are only played in the situations they are strong</li>
      <li>This makes characters oppressive in some circumstances while being almost impossible to play in others</li>
    </ol>
  </li>
  <li>Character based games rely on character fantasies, they enjoyment of becoming another character. This is disrupted any time they swap. This is likely part of why many players in games where swapping is allowed, either choose not to or maintain a small amount of characters played even at the cost of losing more games. These players with a small amount of characters played disrupt fair matchmaking under a conventional rating system, as their preferences cannot be accounted for.</li>
</ol>

<h3 id="the-effect-of-neural-rating-on-no-swapping-downsides">The effect of neural rating on no swapping downsides</h3>

<p>I would argue that neural rating fixes most of the downsides of changing to a no swapping system. In regards to the above:</p>

<ol>
  <li>This is perhaps the most valid point for swapping, however much of why people don’t enjoy gameplay has to do with losing/ feeling like you cannot win. Knowing that a matchup is fair helps with feelings of a game not being winnable</li>
  <li>When uneven matchups are prevented through matchmaking, unique heros no longer break fair matchmaking when you can’t swap.</li>
  <li>Although some players enjoy the tactical element of swapping, preventing swapping all these issues of swapping.</li>
  <li>Although some players enjoy the tactical element of swapping, preventing swapping all these issues of swapping.</li>
  <li>Without swapping, character fantasies are disrupted less, and players who don’t swap don’t screw up matchmaking.</li>
</ol>

<p>Another advantage is that because you can determine character picks at matchmaking time, you can prevent putting players who want to play the same character on the same team, thereby allowing players to play the characters they like more often.</p>

<h3 id="adapting-previous-data-for-training">Adapting previous data for training</h3>

<p> One problem of adapting no character swapping neural matchmaking to character swapping games is that the dataset of games available include swapping, and are therefore not ideal data for no swapping matchmaking. You can take data from those chance few games that did not have swaps, but depending on the complexity of the game and number of games available to train on, this data alone may not be enough.</p>

<h4 id="individual-fight-data">Individual fight data</h4>

<p> One source of data you may be able to use to enhance your dataset is individual fights. By training on simplified game states such as only including score, time, and ultimate economy data to predict the overall outcome of the game, including only gamestates where there were no swaps for the rest of the game, you can train on valid data, and then evaluate at the game’s start state when matchmaking. As a bonus, you can give players this tool to help identify their mistakes based on significant decreases to their probability of winning after losing a fight.</p>

<h4 id="synthetic-data">Synthetic data</h4>

<p> Another way you can increase your dataset is by altering your current data in ways that should not affect outcome. For example you can change the order players are inputted, swap both the teams and the results, and increase the mmrs of the winning team, as this should not affect the result.</p>

<h3 id="combatting-toxicity">Combatting toxicity</h3>

<p> Using character mmrs and neural ratings should decrease player’s variance in performance compared to expected performance, thereby decreasing cases where a player lashes out due to feeling like a game is unwinnable due to a teammate’s underperformance.</p>

<h3 id="queue-time-impact-analysis">Queue time impact analysis</h3>

<p> One response I’ve gotten when suggesting this idea is that because you’re increasing the burden of quality on the matchmaker, the amount of time it takes a player to find a game must go up. In general I don’t entirely disagree with this sentiment, although I believe that between players queueing for multiple characters to get shorter queue times, and using map picks to compensate for bad matchups the impact should be minimal. I also think that avoiding bad matchups also helps even out other factors that have previously been a problem for matchmaking. For example, normally the impact of high rating disparities causes the result of a match to be unpredictable at matchmaking time, leading to lower match quality. However, if such impacts could be predicted by the matchmaker, you could include much wider rating disparities without a guaranteed decrease in match fairness. Another factor is that a wider variety of fair matches are possible, for example when overwatch implemented role queue queue times went up significantly, but many of the player’s complaints with open queue had to do with the increased impact of matchups, that could potentially be accounted for by a neural rating system.</p>

<h3 id="opening-up-character-design">Opening up character design</h3>

<p> Although character swapping allows for more unique character design than normal no swapping, it also limits character design as unique characters leading to unfair matchups continue to cause issues through counterswapping and characters being oppressively powerful in certain situations but unplayable in others. Accounting for matchups properly through matchmaking could allow designers to create more unique and fun characters. In addition, effects such as character overall power can be accounted for via the matchmaker, either by putting them in a mirror, against better opponents or against counters. This allows designers to make characters that are unbalanced in either direction without messing up fair matches independent of character pick. With character specific mmr, all that matters is how good you are relative to other players of your character, meaning players don’t have to play meta to climb leaderboards, and variety of play can come from player choice rather than balance.</p>

<h3 id="having-better-data">Having better data</h3>

<p> Having this system also allows you to get better data about the balance of a character. Raw winrates can paint a misleading picture of overall character power as players only use characters in the situations they are powerful. This can either lead to an unwarranted belief of high power, as players who use them in their niches outperform those who don’t. Or the opposite, where if a character is, for example, used to get out of spawn faster to defend in a losing fight, they might have a lower winrate statistic than what they would have if played more universally as they are only played in losing situations. By evaluating the rating system on hypothetical examples, designers can get a better sense of when a character is strong.</p>

<h2 id="adapting-for-game-changes">Adapting for game changes</h2>

<p> In the general sense, changes to a game affect matchmaking quality. If a character is made more powerful, the players of that character will win an unexpected amount of games. Meanwhile other players playing against them will lose an unexpected amnount of games. Still others will swap to playing the character because they are powerful, lose games because they don’t know how to play them, and then start winning as they learn the character’s basics enough to get up to their normal skill. These factors decrease the fairness of matches and therefore enjoyment of players, and are difficult to mitigate with a traditional rating system. With a neural rating system, designers can predict the impact of a change on winrates and attempt to adjust the system to account for this. This should lead to improved early matchmaking, and as the system learns on new data, it will continue to improve, such that players who join late after these changes are not affected by a decrease in match quality.</p>

<h2 id="matchmaking-for-fun">Matchmaking for fun</h2>

<blockquote>
  <p>Note this is optional extension to the idea, and not a critical part of it.</p>
</blockquote>

<h3 id="fun-matchmaking-problem-statement">Fun Matchmaking Problem Statement</h3>

<p> Just because a match is fair, doesn’t mean it is guaranteed to be fun for players. I have previously talked about accounting for impact in matchmaking to mitigate this, but there are more examples. In some matchups characters might have abilities with very little power which often decreases their fun, even if the match is fair.</p>

<h3 id="proposed-idea">Proposed idea</h3>

<p> If you ask players after each match to rate how much fun they had, you could not only get good data on situations where the game isn’t fun for designers, but also train your neural rating system to output a player expected enjoyment value as well as its match prediction result. This can then be used in matchmaking to reduce the prevalence of matchups that aren’t fun. There are some interesting improvements you would likely want to make for this idea, like normalizing the player fun ratings for different characters and match results. Ultimately the impact of this would have to be tested to find problems with it, and whether it would work overall.</p>

<h2 id="multiple-mmr">Multiple MMR</h2>

<blockquote>
  <p>Note this is optional extension to the idea, and not a critical part of it.</p>
</blockquote>

<p> Character picks and overall skill level are not the only thing that can affect a player’s performance in a game. Other factors include playstyle and situational strengths. For example a player might have very good mechanics, but relativly bad game sense, which would impact their performance in matchups that require different amounts of the different skills. By using a neural rating system, multiple ratings for different skills can be put in to evaluate a matchup, and they can be adjusted differently based on the differing result of backpropagation between them. This could lead to better matchmaking accuracy.</p>

<p> One difficulty with this strategy is getting the neural network to learn these seperate ratings in the first place. To do this you would initialize the training data with either random mmrs, or a random variation on mmr, and apply the gradients of the mmr to the training data. This has its own costs however, as it would likely make it more prone to overfitting, and might make applying old mmr to the new system difficult.</p>

<h2 id="potential-downsides">Potential Downsides</h2>

<h3 id="manipulation">Manipulation</h3>

<p> By intentionally losing certain matchups on alt accounts, players could provide invalid training data that would then allow them to come back and win those matchups on their main, thereby abusing the system to gain an unfairly high rating. There are ways to detect this, and it’s unclear why a player would do this as opposed to other cheating methods such as hacking, win trading, or smurfing.</p>

<h3 id="effect-on-professional-scenes">Effect on professional scenes</h3>

<p> If this system causes designers to come up with more unique characters, and make changes for fun rather than balance, then the pro scene could be incredibly unbalanced and therefore less fun to watch and play in. You could try doing tournaments based on a swiss system, with varying points depending on matchup, but this would still likely be worse.</p>

<h3 id="factors-this-does-not-account-for">Factors this does not account for</h3>

<p> This does not account for player mental state such as tilted players, tired players, players being toxic or distracting to make their teammates play worse, optimistic players, energized players, or players that give good communication to make their team play better. These will still have a cost to match fairness and are hard to account for.</p>

<h3 id="computation-cost">Computation cost</h3>

<p> As this system is more complex than current matchup evaluation systems, it neccessarily comes with an increased compute cost in both training and matchmaking, this may have an affect to increase queue times, or increase matchmaking expenses to make it not worth it economically.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[]]></summary></entry></feed>