<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shun Ueda</title>
    <description>The latest articles on DEV Community by Shun Ueda (@hasundue).</description>
    <link>https://dev.to/hasundue</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F887213%2F6b8521b6-0025-4788-b8f6-a764198336a1.jpeg</url>
      <title>DEV Community: Shun Ueda</title>
      <link>https://dev.to/hasundue</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hasundue"/>
    <language>en</language>
    <item>
      <title>Type inference in mapped types: a rabbit hole and a technique to jump out</title>
      <dc:creator>Shun Ueda</dc:creator>
      <pubDate>Tue, 05 Jul 2022 03:49:26 +0000</pubDate>
      <link>https://dev.to/hasundue/type-inference-in-mapped-types-a-rabbit-hole-and-a-technique-to-jump-out-5ci8</link>
      <guid>https://dev.to/hasundue/type-inference-in-mapped-types-a-rabbit-hole-and-a-technique-to-jump-out-5ci8</guid>
      <description>&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;Suppose you are dealing with objects like these:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;foobar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;foo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;boo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;boo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;boo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;boo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;boo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How would you type them? They seem to be characterized as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The type of keys is &lt;code&gt;string&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Numbers of keys are unknown.&lt;/li&gt;
&lt;li&gt;Values are arrays of keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And as always, we want to impose the strongest restriction and inference on the type of arguments as possible.&lt;/p&gt;




&lt;h2&gt;
  
  
  Discussion
&lt;/h2&gt;

&lt;p&gt;Let's think with the following &lt;code&gt;identity&lt;/code&gt; function for the sake of simplicity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;foo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// =&amp;gt; result = {&lt;/span&gt;
&lt;span class="c1"&gt;//      foo: ["foo"],&lt;/span&gt;
&lt;span class="c1"&gt;//      bar: ["bar", "bar"],&lt;/span&gt;
&lt;span class="c1"&gt;//    }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can see how typescript infers the type of the argument by checking the type of &lt;code&gt;result&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Case 1: Record
&lt;/h3&gt;

&lt;p&gt;Let's start with a simple &lt;code&gt;Record&lt;/code&gt; type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;identity1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, we just get an object of &lt;code&gt;Record&amp;lt;string, string[]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6CbSW-5c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://storage.googleapis.com/zenn-user-upload/c45923b4e82d-20220704.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6CbSW-5c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://storage.googleapis.com/zenn-user-upload/c45923b4e82d-20220704.png" alt="result1" width="413" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this way. we allow an object in which "foo" and "bar" are exchanged, which we don't like to:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PYOjTNrC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://storage.googleapis.com/zenn-user-upload/527348dda913-20220705.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PYOjTNrC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://storage.googleapis.com/zenn-user-upload/527348dda913-20220705.png" alt="counter1" width="423" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is because that we have no restriction on the relationship between the type of keys and that of values in &lt;code&gt;Record&lt;/code&gt; types.&lt;/p&gt;

&lt;h3&gt;
  
  
  Case 2: Mapped Type
&lt;/h3&gt;

&lt;p&gt;Next we try a mapped type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;identity2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;　&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seems good, huh? But we get this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UOKQhVq---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://storage.googleapis.com/zenn-user-upload/0c770431a8b4-20220705.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UOKQhVq---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://storage.googleapis.com/zenn-user-upload/0c770431a8b4-20220705.png" alt="result2" width="307" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This type is essentially the same as the last one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7JLksOWf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://storage.googleapis.com/zenn-user-upload/2d8ef5544e05-20220705.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7JLksOWf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://storage.googleapis.com/zenn-user-upload/2d8ef5544e05-20220705.png" alt="counter2" width="305" height="157"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We did specify the relationship between the types of keys and values as &lt;code&gt;[T in string]: T[]&lt;/code&gt;! Where did it go? What should we do instead?&lt;/p&gt;




&lt;h2&gt;
  
  
  Answer
&lt;/h2&gt;

&lt;p&gt;Here is the solution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;identity3&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;U&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;U&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way typescript can infer the type of an argument very specifically:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Nwd1J_wq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://storage.googleapis.com/zenn-user-upload/9c628ad1beb9-20220705.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Nwd1J_wq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://storage.googleapis.com/zenn-user-upload/9c628ad1beb9-20220705.png" alt="answer" width="236" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And doesn't allow illegal objects:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2DtiGx5O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://storage.googleapis.com/zenn-user-upload/f98c26a01e1e-20220705.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2DtiGx5O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://storage.googleapis.com/zenn-user-upload/f98c26a01e1e-20220705.png" alt="error" width="490" height="148"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Commentary
&lt;/h2&gt;

&lt;p&gt;So what's the difference between &lt;code&gt;identity2&lt;/code&gt; and &lt;code&gt;identity3&lt;/code&gt;? Let's recall what mapped types are first:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.typescriptlang.org/docs/handbook/2/mapped-types.html"&gt;https://www.typescriptlang.org/docs/handbook/2/mapped-types.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A mapped type is a generic type which uses a &lt;strong&gt;union of PropertyKeys&lt;/strong&gt; (frequently created via a keyof) to iterate through keys to create a type:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It means that if we write &lt;code&gt;[T in X]&lt;/code&gt;, &lt;code&gt;X&lt;/code&gt; should be an union, or something like &lt;code&gt;"foo" | "bar" | "boo"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now let's recall how we defined &lt;code&gt;identity2&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;identity2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;　&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We put &lt;code&gt;string&lt;/code&gt; into &lt;code&gt;X&lt;/code&gt; and typescript allowed it. That should means that &lt;code&gt;string&lt;/code&gt; is a kind of union.&lt;/p&gt;

&lt;p&gt;But what is &lt;code&gt;string&lt;/code&gt; as an union? We may consider it as an union of every string literal we could make, or a collection of an unlimited number of strings.&lt;/p&gt;

&lt;p&gt;Therefore, &lt;code&gt;identity2&lt;/code&gt; is defined to map every element &lt;code&gt;T&lt;/code&gt; in such a collection onto &lt;code&gt;T[]&lt;/code&gt;, but typescript does not think it possible for us. Then the type falls back to something like an ordinary record.&lt;/p&gt;

&lt;p&gt;How about &lt;code&gt;identity3&lt;/code&gt; then, which is the solution here?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;identity3&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;U&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;U&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It uses &lt;code&gt;U extends string&lt;/code&gt; instead of &lt;code&gt;string&lt;/code&gt;. This way &lt;code&gt;U&lt;/code&gt; may be considered as a subset of &lt;code&gt;string&lt;/code&gt; which has a limited number of string literals, and the type is regarded as a valid mapped type!&lt;/p&gt;

&lt;p&gt;In the example where we pass &lt;code&gt;{ foo: ["foo"], bar: ["bar"] }&lt;/code&gt; to the function, &lt;code&gt;"foo" | "bar"&lt;/code&gt; matches with &lt;code&gt;U&lt;/code&gt; and all of the entries are confirmed to mapped to &lt;code&gt;T[]&lt;/code&gt; as defined.&lt;/p&gt;




&lt;h2&gt;
  
  
  Afterword
&lt;/h2&gt;

&lt;p&gt;If you are interested in how the technique is used in real world scenarios, see the source code of my ongoing work, which is a framework for building web APIs with Deno + Cloudflare Workers:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hasundue/flash"&gt;https://github.com/hasundue/flash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for your kind reading!&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The original version in Japanese: &lt;a href="https://zenn.dev/articles/a8b36dafffc067"&gt;https://zenn.dev/articles/a8b36dafffc067&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>typescript</category>
    </item>
  </channel>
</rss>
