Planet Factor
John Benediktsson: Reverse Vowels
2024-02-12T19:00:00.000000Z
<p>Our task today is to “reverse vowels of a string”. This sounds like (and
probably is) a <a href="https://www.codinginterview.com">coding interview question</a>
as well as a <a href="https://leetcode.com/problems/reverse-vowels-of-a-string/description/">LeetCode
problem</a>,
a <a href="https://www.codewars.com/kata/585db3e8eec141ce9a00008f">Codewars kata</a>,
and the second task in the <a href="https://theweeklychallenge.org/blog/perl-weekly-challenge-254/">Perl Weekly Challenge
#254</a>.</p>
<p><em>If you don’t want spoilers, maybe stop reading here!</em></p>
<hr>
<p>We are going to use <a href="https://factorcode.org">Factor</a> to solve this problem as
well as a variant that is a bit more challenging.</p>
<h3 id="lets-reverse-the-vowels">Let’s Reverse The Vowels</h3>
<p>One of the benefits of the <a href="https://en.wikipedia.org/wiki/Monorepo">monorepo
approach</a> that we have taken to
building the extensive <a href="https://docs.factorcode.org/content/article-vocab-index.html">Factor standard
library</a> is
developing higher-level words that solve specific kind of tasks.</p>
<p>One of those is
<a href="https://docs.factorcode.org/content/word-arg-where,sequences.extras.html">arg-where</a>
– currently in the miscellaneous <a href="https://docs.factorcode.org/content/vocab-sequences.extras.html">sequences.extras
vocabulary</a> –
which we can use to find all the indices in a string that contain a
<a href="https://docs.factorcode.org/content/word-vowel__que__,english.html">vowel?</a>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> <span class="s">"hello"</span> [ vowel? ] arg-where <span class="m">.
</span></span></span><span class="line"><span class="cl"><span class="m"></span>V{ <span class="m">1 4 </span>}
</span></span></code></pre></div><p>We’ll want to group the beginning and ending indices, ignoring the middle index
if the number of indices is odd since it would not change:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">split-indices</span> <span class="nf">( </span><span class="nv">indices</span> <span class="nf">-- </span><span class="nv">head</span> <span class="nv">tail</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> <span class="nb">dup length 2/ </span>[ <span class="nb">head-slice </span>] [ <span class="nb">tail-slice* </span>] <span class="nb">2bi </span><span class="k">;
</span></span></span></code></pre></div><p>We can then build a word to reverse specified indices:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">reverse-indices</span> <span class="nf">( </span><span class="nv">str</span> <span class="nv">indices</span> <span class="nf">-- </span><span class="nv">str</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> split-indices <span class="nb"><reversed> </span>[ <span class="nb">pick exchange </span>] <span class="nb">2each </span><span class="k">;
</span></span></span></code></pre></div><p>And then use it to reverse the vowels:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">reverse-vowels</span> <span class="nf">( </span><span class="nv">str</span> <span class="nf">-- </span><span class="nv">str</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> <span class="nb">dup </span>>lower [ vowel? ] arg-where reverse-indices <span class="k">;
</span></span></span></code></pre></div><p>And see how it works:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> <span class="s">"factor"</span> reverse-vowels <span class="m">.
</span></span></span><span class="line"><span class="cl"><span class="m"></span><span class="s">"foctar"</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> <span class="s">"concatenative"</span> reverse-vowels <span class="m">.
</span></span></span><span class="line"><span class="cl"><span class="m"></span><span class="s">"cencitanetavo"</span>
</span></span></code></pre></div><p>Pretty cool!</p>
<h3 id="lets-reverse-the-vowels-maintain-the-case">Let’s Reverse The Vowels, Maintain The Case</h3>
<p>A somewhat more challenging task is to reverse the vowels, and to swap their
<a href="https://en.wikipedia.org/wiki/Letter_case">letter case</a>.</p>
<p>Let’s start by building a word to swap the case of two letters:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">swap-case</span> <span class="nf">( </span><span class="nv">a</span> <span class="nv">b</span> <span class="nf">-- </span><span class="nv">a'</span> <span class="nv">b'</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> <span class="nb">2dup </span>[ letter? ] <span class="nb">bi@ 2array </span>{
</span></span><span class="line"><span class="cl"> { { <span class="no">t f </span>} [ [ ch>upper ] [ ch>lower ] <span class="nb">bi* </span>] }
</span></span><span class="line"><span class="cl"> { { <span class="no">f t </span>} [ [ ch>lower ] [ ch>upper ] <span class="nb">bi* </span>] }
</span></span><span class="line"><span class="cl"> [ <span class="nb">drop </span>]
</span></span><span class="line"><span class="cl"> } <span class="nb">case </span><span class="k">;
</span></span></span></code></pre></div><p>And then another word to
<a href="https://docs.factorcode.org/content/word-exchange,sequences.html">exchange</a>
two indices, but also swap their case:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">exchange-case</span> <span class="nf">( </span><span class="nv">i</span> <span class="nv">j</span> <span class="nv">seq</span> <span class="nf">-- )
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> [ '[ _ <span class="nb">nth </span>] <span class="nb">bi@ </span>swap-case ]
</span></span><span class="line"><span class="cl"> [ '[ _ <span class="nb">set-nth </span>] <span class="nb">bi@ </span>] <span class="nb">3bi </span><span class="k">; inline
</span></span></span></code></pre></div><p>A word to reverse the indices, but also swap their case:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">reverse-indices-case</span> <span class="nf">( </span><span class="nv">str</span> <span class="nv">indices</span> <span class="nf">-- </span><span class="nv">str</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> split-indices <span class="nb"><reversed> </span>[ <span class="nb">pick </span>exchange-case ] <span class="nb">2each </span><span class="k">;
</span></span></span></code></pre></div><p>And, finally, a word to reverse the vowels, but also swap their case:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">reverse-vowels-case</span> <span class="nf">( </span><span class="nv">str</span> <span class="nf">-- </span><span class="nv">str</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> <span class="nb">dup </span>>lower [ vowel? ] arg-where reverse-indices-case <span class="k">;
</span></span></span></code></pre></div><p>And then see how it works:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> <span class="s">"FActor"</span> reverse-vowels-case <span class="m">.
</span></span></span><span class="line"><span class="cl"><span class="m"></span><span class="s">"FOctar"</span>
</span></span></code></pre></div><p>A pretty fun problem!</p>
John Benediktsson: Dragonbox
2024-02-12T02:30:00.000000Z
<p>One of the challenging problems in <a href="https://en.wikipedia.org/wiki/Computer_science">computer
science</a> is to efficiently take
a binary representation of a floating-point number and convert it to the
“shortest decimal representation” that will roundtrip back to the same
floating-point number when it is parsed.</p>
<p>A few days ago, one of the members of <a href="https://discord.gg/QxJYZx3QDf">the Factor Discord
server</a> posted about an issue they were
having where three separate floating-point numbers printed as the same
decimal value:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> 0x1.1ffffffffffffp7 <span class="m">.
</span></span></span><span class="line"><span class="cl"><span class="m">144.0
</span></span></span><span class="line"><span class="cl"><span class="m"></span>
</span></span><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> 0x1.2p7 <span class="m">.
</span></span></span><span class="line"><span class="cl"><span class="m">144.0
</span></span></span><span class="line"><span class="cl"><span class="m"></span>
</span></span><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> 0x1.2000000000001p7 <span class="m">.
</span></span></span><span class="line"><span class="cl"><span class="m">144.0
</span></span></span></code></pre></div><p>Well, that’s not ideal!</p>
<p>And you can see that in other languages like <a href="https://python.org">Python</a>, they
parse properly into three distinct values:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">>>></span> <span class="nb">float</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="s1">'0x1.1ffffffffffffp7'</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="mf">143.99999999999997</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">>>></span> <span class="nb">float</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="s1">'0x1.2p7'</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="mf">144.0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">>>></span> <span class="nb">float</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="s1">'0x1.2000000000001p7'</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="mf">144.00000000000003</span>
</span></span></code></pre></div><p>In the process of investigating this issue, I re-discovered a few algorithms
that have been developed to do this. There is a neat project called
<a href="https://github.com/abolz/Drachennest">Drachennest</a> that investigates the
relative performance of several of these algorithms and claims:</p>
<blockquote>
<p>Grisu3, Ryu, Schubfach, and Dragonbox are optimal, i.e. the output string</p>
<ol>
<li>rounds back to the input number when read in,</li>
<li>is as short as possible,</li>
<li>is as close to the input number as possible.</li>
</ol>
</blockquote>
<p>Well, it turns out that the “Dragonbox” algorithm is one of the current best
and is described in a paper called <a href="https://github.com/jk-jeon/dragonbox/blob/master/other_files/Dragonbox.pdf">A New Floating-Point Binary-to-Decimal
Conversion
Algorithm</a>
as well as a fantastic <a href="https://github.com/jk-jeon/dragonbox">reference implementation of Dragonbox in
C++</a>.</p>
<p>I was able to quickly fix the bug by temporarily using a “modern formatting
library” called <a href="https://fmt.dev">{fmt}</a> that works in C++11 and provides a
version of the C++20 function
<a href="https://en.cppreference.com/w/cpp/utility/format/format">std::format</a>, but
thought it would be a good idea to implement the Dragonbox algorithm someday
in pure Factor code and filed an issue to track that idea.</p>
<p>Well, one of our awesome contributors,
<a href="https://github.com/gifti258">Giftpflanze</a>, jumped in and <a href="https://github.com/factor/factor/pull/2943">implemented
Dragonbox in Factor</a> – providing a
very readable and understandable and <a href="https://re.factorcode.org/2011/07/concatenative-thinking.html">nicely
concatenative</a> version – and it was
merged today!</p>
<p>Not only does this solve the issue of decimal representation of floats, but it
provides quite a large speedup to our <a href="https://github.com/factor/factor/blob/master/extra/benchmark/parse-float/parse-float.factor">float-parsing
benchmark</a>:</p>
<p>Currently in <a href="https://re.factorcode.org/2023/08/factor-0-99-now-available.html">Factor 0.99</a>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> gc [ parse-float-benchmark ] time
</span></span><span class="line"><span class="cl">Running time: <span class="m">3.181906583 </span>seconds
</span></span></code></pre></div><p>And now after the patch:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> gc [ parse-float-benchmark ] time
</span></span><span class="line"><span class="cl">Running time: <span class="m">0.378132792 </span>seconds
</span></span></code></pre></div><p>Very impressive!</p>
<p>I’m excited to say that this is now available in the <a href="https://github.com/factor/factor">development version of
Factor</a>.</p>
John Benediktsson: Divmods
2024-02-02T15:00:00.000000Z
<p>There’s a discussion on <a href="https://discuss.python.org/t/support-multiple-divisors-in-divmod/33109">support multiple divisors in
divmod()</a>
on the Python.</p>
<blockquote>
<p>So instead of</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">minutes</span><span class="p">,</span> <span class="n">seconds</span> <span class="o">=</span> <span class="nb">divmod</span><span class="p">(</span><span class="n">t</span><span class="p">,</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">hours</span><span class="p">,</span> <span class="n">minutes</span> <span class="o">=</span> <span class="nb">divmod</span><span class="p">(</span><span class="n">minutes</span><span class="p">,</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">days</span><span class="p">,</span> <span class="n">hours</span> <span class="o">=</span> <span class="nb">divmod</span><span class="p">(</span><span class="n">hours</span><span class="p">,</span> <span class="mi">24</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">weeks</span><span class="p">,</span> <span class="n">days</span> <span class="o">=</span> <span class="nb">divmod</span><span class="p">(</span><span class="n">days</span><span class="p">,</span> <span class="mi">7</span><span class="p">)</span>
</span></span></code></pre></div><p>you could write:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">weeks</span><span class="p">,</span> <span class="n">days</span><span class="p">,</span> <span class="n">hours</span><span class="p">,</span> <span class="n">minutes</span><span class="p">,</span> <span class="n">seconds</span> <span class="o">=</span> <span class="nb">divmod</span><span class="p">(</span><span class="n">t</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">24</span><span class="p">,</span> <span class="mi">60</span><span class="p">,</span> <span class="mi">60</span><span class="p">)</span>
</span></span></code></pre></div><p>Sample implementation:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">new_divmod</span><span class="p">(</span><span class="n">dividend</span><span class="p">,</span> <span class="o">*</span><span class="n">divisors</span><span class="p">):</span>
</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="ow">not</span> <span class="n">divisors</span><span class="p">:</span>
</span></span><span class="line"><span class="cl"> <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span><span class="s1">'required at least one divisor'</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="n">remainders</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="n">divisor</span> <span class="ow">in</span> <span class="nb">reversed</span><span class="p">(</span><span class="n">divisors</span><span class="p">):</span>
</span></span><span class="line"><span class="cl"> <span class="n">dividend</span><span class="p">,</span> <span class="n">remainder</span> <span class="o">=</span> <span class="n">old_divmod</span><span class="p">(</span><span class="n">dividend</span><span class="p">,</span> <span class="n">divisor</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="n">remainders</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">remainder</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="p">(</span><span class="n">dividend</span><span class="p">,</span> <span class="o">*</span><span class="n">remainders</span><span class="p">[::</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span>
</span></span></code></pre></div></blockquote>
<p>Along with the sample implementation in <a href="https://python.org">Python</a> above, the
original author provides some thoughts on whether the order of arguments should
be reversed or not, and some of the comments in the thread discuss various
implementation details and some other use-cases for this approach.</p>
<p>You can see how it might work by trying the code:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">>>></span> <span class="n">new_divmod</span><span class="p">(</span><span class="mi">1234567</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">24</span><span class="p">,</span> <span class="mi">60</span><span class="p">,</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">56</span><span class="p">,</span> <span class="mi">7</span><span class="p">)</span>
</span></span></code></pre></div><p>Okay, so how might we do this in <a href="https://factorcode.org">Factor</a>?</p>
<p>Well, our version of <code>divmod</code> is
<a href="https://docs.factorcode.org/content/word-__slash__mod,math.html">/mod</a> and we
could just run it a few times to get the result:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> <span class="m">1234567 60 </span><span class="nb">/mod swap </span><span class="m">60 </span><span class="nb">/mod swap </span><span class="m">24 </span><span class="nb">/mod swap </span><span class="m">7 </span><span class="nb">/mod swap
</span></span></span><span class="line"><span class="cl"><span class="nb"></span>
</span></span><span class="line"><span class="cl">--- Data stack:
</span></span><span class="line"><span class="cl"><span class="m">7
</span></span></span><span class="line"><span class="cl"><span class="m">56
</span></span></span><span class="line"><span class="cl"><span class="m">6
</span></span></span><span class="line"><span class="cl"><span class="m">0
</span></span></span><span class="line"><span class="cl"><span class="m">2
</span></span></span></code></pre></div><p>Alternatively, we could pass the arguments as a sequence and return the result
as a sequence:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> <span class="m">1234567 </span>{ <span class="m">60 60 24 7 </span>} [ <span class="nb">/mod </span>] <span class="nb">map swap suffix
</span></span></span><span class="line"><span class="cl"><span class="nb"></span>
</span></span><span class="line"><span class="cl">--- Data stack:
</span></span><span class="line"><span class="cl">{ <span class="m">7 56 6 0 2 </span>}
</span></span></code></pre></div><p>Or, perhaps, we could make a
<a href="https://docs.factorcode.org/content/article-macros.html">macro</a>, taking the
input argument as a sequence, but generating code to put the result onto the
stack:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">MACRO:</span> <span class="nf">/mods</span> <span class="nf">( </span><span class="nv">seq</span> <span class="nf">-- </span><span class="nv">quot</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> [ '[ _ <span class="nb">/mod swap </span>] ] <span class="nb">map concat </span><span class="k">;
</span></span></span></code></pre></div><p>And then use it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> <span class="m">1234567 </span>{ <span class="m">60 60 24 7 </span>} /mods
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">--- Data stack:
</span></span><span class="line"><span class="cl"><span class="m">7
</span></span></span><span class="line"><span class="cl"><span class="m">56
</span></span></span><span class="line"><span class="cl"><span class="m">6
</span></span></span><span class="line"><span class="cl"><span class="m">0
</span></span></span><span class="line"><span class="cl"><span class="m">2
</span></span></span></code></pre></div><p>Kind of an interesting idea!</p>
John Benediktsson: Crontab
2024-01-31T15:00:00.000000Z
<p><a href="https://cron.com">Cron</a> might be the latest, greatest, and coolest
“<em>next-generation calendar</em>” as well as now a product called <a href="https://www.notion.so/product/calendar">Notion
Calendar</a>. But in the good ol’ days,
<a href="https://en.wikipedia.org/wiki/Cron">cron</a> was instead known as:</p>
<blockquote>
<p>The <code>cron</code> command-line utility is a <a href="https://en.wikipedia.org/wiki/Job_scheduler">job
scheduler</a> on <a href="https://en.wikipedia.org/wiki/Operating_system">Unix-like
operating
systems</a>. Users who set up and
maintain software environments use cron to
schedule jobs (commands or <a href="https://en.wikipedia.org/wiki/Shell_script">shell
scripts</a>), also known as <strong>cron
jobs</strong>, to run periodically at fixed times, dates, or intervals. It typically
automates system maintenance or administration—though its general-purpose
nature makes it useful for things like downloading files from the Internet
and downloading email at regular intervals.</p>
</blockquote>
<p>There are implementations of <code>crond</code> – the cron daemon – on most operating
systems. Many of them have standardized on a
<a href="https://linux.die.net/man/5/crontab">crontab</a> format that looks something like
this:</p>
<pre tabindex="0"><code># ┌───────────── minute (0–59)
# │ ┌───────────── hour (0–23)
# │ │ ┌───────────── day of the month (1–31)
# │ │ │ ┌───────────── month (1–12)
# │ │ │ │ ┌───────────── day of the week (0–6) (Sunday to Saturday;
# │ │ │ │ │ 7 is also Sunday on some systems)
# │ │ │ │ │
# │ │ │ │ │
# * * * * * <command to execute>
</code></pre><p>At first (and sometimes second and third and fourth) glance, this looks a bit
inscrutable, and so websites such as <a href="https://crontab.guru">crontab guru</a> pop
up to help you unpack and explain when a <strong>cronentry</strong> is expected to be run.</p>
<p>I thought it would be fun to build a parser for these cronentries in
<a href="https://factorcode.org">Factor</a>.</p>
<p>Let’s start by defining a <code>cronentry</code> type:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">TUPLE:</span> <span class="nc">cronentry</span> <span class="nv">minutes</span> <span class="nv">hours</span> <span class="nv">days</span> <span class="nv">months</span> <span class="nv">days-of-week</span> <span class="nv">command</span> <span class="k">;
</span></span></span></code></pre></div><p>For each component, there is a variety of allowed inputs:</p>
<ul>
<li>all values in the range: <code>*</code></li>
<li>list of values: <code>3,5,7</code></li>
<li>range of values: <code>10-15</code></li>
<li>step values: <code>1-20/5</code></li>
<li>random value in range: <code>10~30</code></li>
</ul>
<p>We build a <code>parse-value</code> word that will take an <code>input</code> string, a <code>quot</code>
to parse the input, and a <code>seq</code> of possible values, as well as a
<code>parse-range</code> word to help with optional starting and ending input values.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">::</span> <span class="nf">parse-range</span> <span class="nf">( </span><span class="nv">from/f</span> <span class="nv">to/f</span> <span class="nv">quot:</span> <span class="nf">( </span><span class="nv">input</span> <span class="nf">-- </span><span class="nv">value</span> <span class="nf">) </span><span class="nv">seq</span> <span class="nf">-- </span><span class="nv">from</span> <span class="nv">to</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> from/f [ seq <span class="nb">first </span>] quot <span class="nb">if-empty
</span></span></span><span class="line"><span class="cl"><span class="nb"></span> to/f [ seq <span class="nb">last </span>] quot <span class="nb">if-empty </span><span class="k">; inline
</span></span></span><span class="line"><span class="cl"><span class="k"></span>
</span></span><span class="line"><span class="cl"><span class="k">::</span> <span class="nf">parse-value</span> <span class="nf">( </span><span class="nv">input</span> <span class="nv">quot:</span> <span class="nf">( </span><span class="nv">input</span> <span class="nf">-- </span><span class="nv">value</span> <span class="nf">) </span><span class="nv">seq</span> <span class="nf">-- </span><span class="nv">value</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> input {
</span></span><span class="line"><span class="cl"> { [ <span class="nb">dup </span><span class="s">"*"</span> <span class="nb">= </span>] [ <span class="nb">drop </span>seq ] }
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> { [ <span class="sc">CHAR: , </span><span class="nb">over member? </span>] [
</span></span><span class="line"><span class="cl"> <span class="s">","</span> split [ quot seq parse-value ] <span class="nb">map concat </span>] }
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> { [ <span class="sc">CHAR: / </span><span class="nb">over member? </span>] [
</span></span><span class="line"><span class="cl"> <span class="s">"/"</span> split1 [
</span></span><span class="line"><span class="cl"> quot seq parse-value <span class="nb">dup length </span><span class="m">1 </span><span class="nb">=
</span></span></span><span class="line"><span class="cl"><span class="nb"></span> [ seq <span class="nb">swap first </span>seq <span class="nb">index </span>seq <span class="nb">length </span>]
</span></span><span class="line"><span class="cl"> [ <span class="m">0 </span><span class="nb">over length </span>] <span class="nb">if </span><span class="m">1 </span><span class="nb">-
</span></span></span><span class="line"><span class="cl"><span class="nb"></span> ] <span class="nb">dip </span>string>number <range> <span class="nb">swap nths </span>] }
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> { [ <span class="sc">CHAR: - </span><span class="nb">over member? </span>] [
</span></span><span class="line"><span class="cl"> <span class="s">"-"</span> split1 quot seq parse-range [a..b] ] }
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> { [ <span class="sc">CHAR: ~ </span><span class="nb">over member? </span>] [
</span></span><span class="line"><span class="cl"> <span class="s">"~"</span> split1 quot seq parse-range [a..b] random <span class="nb">1array </span>] }
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> [ quot <span class="nb">call 1array </span>]
</span></span><span class="line"><span class="cl"> } <span class="nb">cond </span>members sort <span class="k">; inline recursive
</span></span></span></code></pre></div><p>We can then make <code>parse-cronentry</code> to parse the entry description, handling
days and months differently to allow their abbreviations to be passed as input
(e.g., <code>sun</code> for Sunday or <code>jan</code> for January).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">parse-day</span> <span class="nf">( </span><span class="nv">str</span> <span class="nf">-- </span><span class="nv">n</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> [ string>number <span class="nb">dup </span><span class="m">7 </span><span class="nb">= </span>[ <span class="nb">drop </span><span class="m">0 </span>] <span class="nb">when </span>] [
</span></span><span class="line"><span class="cl"> >lower $[ day-abbreviations3 [ >lower ] <span class="nb">map </span>] <span class="nb">index
</span></span></span><span class="line"><span class="cl"><span class="nb"></span> ] ?unless <span class="k">;
</span></span></span><span class="line"><span class="cl"><span class="k"></span>
</span></span><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">parse-month</span> <span class="nf">( </span><span class="nv">str</span> <span class="nf">-- </span><span class="nv">n</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> [ string>number ] [
</span></span><span class="line"><span class="cl"> >lower $[ month-abbreviations [ >lower ] <span class="nb">map </span>] <span class="nb">index
</span></span></span><span class="line"><span class="cl"><span class="nb"></span> ] ?unless <span class="k">;
</span></span></span><span class="line"><span class="cl"><span class="k"></span>
</span></span><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">parse-cronentry</span> <span class="nf">( </span><span class="nv">entry</span> <span class="nf">-- </span><span class="nv">cronentry</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> <span class="s">" "</span> split1 <span class="s">" "</span> split1 <span class="s">" "</span> split1 <span class="s">" "</span> split1 <span class="s">" "</span> split1 {
</span></span><span class="line"><span class="cl"> [ [ string>number ] T{ range <span class="no">f </span><span class="m">0 60 1 </span>} parse-value ]
</span></span><span class="line"><span class="cl"> [ [ string>number ] T{ range <span class="no">f </span><span class="m">0 24 1 </span>} parse-value ]
</span></span><span class="line"><span class="cl"> [ [ string>number ] T{ range <span class="no">f </span><span class="m">1 31 1 </span>} parse-value ]
</span></span><span class="line"><span class="cl"> [ [ parse-month ] T{ range <span class="no">f </span><span class="m">1 12 1 </span>} parse-value ]
</span></span><span class="line"><span class="cl"> [ [ parse-day ] T{ circular <span class="no">f </span>T{ range <span class="no">f </span><span class="m">0 7 1 </span>} <span class="m">1 </span>} parse-value ]
</span></span><span class="line"><span class="cl"> [ ]
</span></span><span class="line"><span class="cl"> } <span class="nb">spread </span>cronentry <span class="nb">boa </span><span class="k">;
</span></span></span></code></pre></div><p>We can try using it to see what a parsed cronentry looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> <span class="s">"20-30/5 5 */5 * * /path/to/command"</span> parse-cronentry <span class="m">.
</span></span></span><span class="line"><span class="cl"><span class="m"></span>T{ cronentry
</span></span><span class="line"><span class="cl"> { minutes { <span class="m">20 25 30 </span>} }
</span></span><span class="line"><span class="cl"> { hours { <span class="m">5 </span>} }
</span></span><span class="line"><span class="cl"> { days { <span class="m">1 6 11 16 21 26 31 </span>} }
</span></span><span class="line"><span class="cl"> { months { <span class="m">1 2 3 4 5 6 7 8 9 10 11 12 </span>} }
</span></span><span class="line"><span class="cl"> { days-of-week { <span class="m">0 1 2 3 4 5 6 </span>} }
</span></span><span class="line"><span class="cl"> { command <span class="s">"/path/to/command"</span> }
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></div><p>Now that we have that working, we can use it to calculate the
<code>next-time-after</code> a given
<a href="https://docs.factorcode.org/content/word-timestamp,calendar.html">timestamp</a>
that the cronentry will trigger, applying a waterfall to rollover the timestamp
until a valid one is found:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">::</span> <span class="nf">(next-time-after)</span> <span class="nf">( </span><span class="nv">cronentry</span> <span class="nv">timestamp</span> <span class="nf">-- )
</span></span></span><span class="line"><span class="cl"><span class="nf"></span>
</span></span><span class="line"><span class="cl"> <span class="no">f </span><span class="c">! should we keep searching for a matching time</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> timestamp month>> :> month
</span></span><span class="line"><span class="cl"> cronentry months>> [ month <span class="nb">>= </span>] <span class="nb">find nip
</span></span></span><span class="line"><span class="cl"><span class="nb"></span> <span class="nb">dup </span>month <span class="nb">= </span>[ <span class="nb">drop </span>] [
</span></span><span class="line"><span class="cl"> [ cronentry months>> <span class="nb">first </span>timestamp <span class="m">1 </span>+year <span class="nb">drop </span>] <span class="nb">unless*
</span></span></span><span class="line"><span class="cl"><span class="nb"></span> timestamp <span class="m">1 </span>>>day <span class="m">0 </span>>>hour <span class="m">0 </span>>>minute month<< <span class="nb">drop </span><span class="no">t
</span></span></span><span class="line"><span class="cl"><span class="no"></span> ] <span class="nb">if
</span></span></span><span class="line"><span class="cl"><span class="nb"></span>
</span></span><span class="line"><span class="cl"> timestamp day-of-week :> weekday
</span></span><span class="line"><span class="cl"> cronentry days-of-week>> [ weekday <span class="nb">>= </span>] <span class="nb">find nip </span>[
</span></span><span class="line"><span class="cl"> cronentry days-of-week>> <span class="nb">first </span><span class="m">7 </span><span class="nb">+
</span></span></span><span class="line"><span class="cl"><span class="nb"></span> ] <span class="nb">unless* </span>weekday <span class="nb">- </span>:> days-to-weekday
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> timestamp day>> :> day
</span></span><span class="line"><span class="cl"> cronentry days>> [ day <span class="nb">>= </span>] <span class="nb">find nip </span>[
</span></span><span class="line"><span class="cl"> cronentry days>> <span class="nb">first </span>timestamp days-in-month <span class="nb">+
</span></span></span><span class="line"><span class="cl"><span class="nb"></span> ] <span class="nb">unless* </span>day <span class="nb">- </span>:> days-to-day
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> cronentry days-of-week>> <span class="nb">length </span><span class="m">7 </span><span class="nb">=
</span></span></span><span class="line"><span class="cl"><span class="nb"></span> cronentry days>> <span class="nb">length </span><span class="m">31 </span><span class="nb">= 2array
</span></span></span><span class="line"><span class="cl"><span class="nb"></span> {
</span></span><span class="line"><span class="cl"> { { <span class="no">f t </span>} [ days-to-weekday ] }
</span></span><span class="line"><span class="cl"> { { <span class="no">t f </span>} [ days-to-day ] }
</span></span><span class="line"><span class="cl"> [ <span class="nb">drop </span>days-to-weekday days-to-day min ]
</span></span><span class="line"><span class="cl"> } <span class="nb">case </span>[
</span></span><span class="line"><span class="cl"> timestamp <span class="m">0 </span>>>hour <span class="m">0 </span>>>minute <span class="nb">swap </span>+day <span class="nb">2drop </span><span class="no">t
</span></span></span><span class="line"><span class="cl"><span class="no"></span> ] <span class="nb">unless-zero
</span></span></span><span class="line"><span class="cl"><span class="nb"></span>
</span></span><span class="line"><span class="cl"> timestamp hour>> :> hour
</span></span><span class="line"><span class="cl"> cronentry hours>> [ hour <span class="nb">>= </span>] <span class="nb">find nip
</span></span></span><span class="line"><span class="cl"><span class="nb"></span> <span class="nb">dup </span>hour <span class="nb">= </span>[ <span class="nb">drop </span>] [
</span></span><span class="line"><span class="cl"> [ cronentry hours>> <span class="nb">first </span>timestamp <span class="m">1 </span>+day <span class="nb">drop </span>] <span class="nb">unless*
</span></span></span><span class="line"><span class="cl"><span class="nb"></span> timestamp <span class="m">0 </span>>>minute hour<< <span class="nb">drop </span><span class="no">t
</span></span></span><span class="line"><span class="cl"><span class="no"></span> ] <span class="nb">if
</span></span></span><span class="line"><span class="cl"><span class="nb"></span>
</span></span><span class="line"><span class="cl"> timestamp minute>> :> minute
</span></span><span class="line"><span class="cl"> cronentry minutes>> [ minute <span class="nb">>= </span>] <span class="nb">find nip
</span></span></span><span class="line"><span class="cl"><span class="nb"></span> <span class="nb">dup </span>minute <span class="nb">= </span>[ <span class="nb">drop </span>] [
</span></span><span class="line"><span class="cl"> [ cronentry minutes>> <span class="nb">first </span>timestamp <span class="m">1 </span>+hour <span class="nb">drop </span>] <span class="nb">unless*
</span></span></span><span class="line"><span class="cl"><span class="nb"></span> timestamp minute<< <span class="nb">drop </span><span class="no">t
</span></span></span><span class="line"><span class="cl"><span class="no"></span> ] <span class="nb">if
</span></span></span><span class="line"><span class="cl"><span class="nb"></span>
</span></span><span class="line"><span class="cl"> [ cronentry timestamp (next-time-after) ] <span class="nb">when </span><span class="k">;
</span></span></span><span class="line"><span class="cl"><span class="k"></span>
</span></span><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">next-time-after</span> <span class="nf">( </span><span class="nv">cronentry</span> <span class="nv">timestamp</span> <span class="nf">-- </span><span class="nv">timestamp</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> [ <span class="nb">dup </span>cronentry? [ parse-cronentry ] <span class="nb">unless </span>]
</span></span><span class="line"><span class="cl"> [ <span class="m">1 </span>minutes time+ <span class="m">0 </span>>>second ] <span class="nb">bi*
</span></span></span><span class="line"><span class="cl"><span class="nb"></span> [ (next-time-after) ] <span class="nb">keep </span><span class="k">;
</span></span></span></code></pre></div><p>This is great, because we can find the next time that a cronentry will trigger.
For example, if we wanted to specify something to trigger at midnight on every
leap day:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> <span class="s">"0 0 29 2 *"</span> now next-time-after timestamp>rfc822 <span class="m">.
</span></span></span><span class="line"><span class="cl"><span class="m"></span><span class="s">"Thu, 29 Feb 2024 00:00:00 -0800"</span>
</span></span></code></pre></div><p>Or even, the next several times that the cronentry will trigger:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> <span class="s">"0 0 29 2 *"</span> now <span class="m">5 </span>[
</span></span><span class="line"><span class="cl"> <span class="nb">dupd </span>next-time-after [ timestamp>rfc822 <span class="m">. </span>] <span class="nb">keep
</span></span></span><span class="line"><span class="cl"><span class="nb"></span> ] <span class="nb">times 2drop
</span></span></span><span class="line"><span class="cl"><span class="nb"></span><span class="s">"Thu, 29 Feb 2024 00:00:00 -0800"</span>
</span></span><span class="line"><span class="cl"><span class="s">"Tue, 29 Feb 2028 00:00:00 -0800"</span>
</span></span><span class="line"><span class="cl"><span class="s">"Sun, 29 Feb 2032 00:00:00 -0800"</span>
</span></span><span class="line"><span class="cl"><span class="s">"Fri, 29 Feb 2036 00:00:00 -0800"</span>
</span></span><span class="line"><span class="cl"><span class="s">"Wed, 29 Feb 2040 00:00:00 -0800"</span>
</span></span></code></pre></div><p>This is available in the <a href="https://github.com/factor/factor/blob/master/extra/crontab/crontab.factor">crontab
vocabulary</a>
including some features such as support for aliases (e.g., <code>@daily</code> and
<code>@weekly</code>) and some higher-level words for working with crontabs and
cronentries.</p>
John Benediktsson: Codewars
2024-01-29T15:00:00.000000Z
<p><a href="https://codewars.com">Codewars</a> is an online platform for learning programming
languages by solving small programming exercises called “kata” and subsequently
increasing your degree of proficiency via levels of “kyu”. It has useful
features such as extensive unit tests, leaderboards, allies for allowing
friendly competition, and discussion boards.</p>
<p>It supports an incredible number of programming languages – albeit some of these
are in “beta” status – including <a href="https://factorcode.org">Factor</a>!</p>
<p>
<img src="https://re.factorcode.org/images/2024-01-29-codewars.png" alt="" width="902" height="540" />
</p>
<p>I wanted to draw attention to the <a href="https://codewars.com">Codewars</a> website and
point out that it has newly released support for <a href="https://re.factorcode.org/2023/08/factor-0-99-now-available.html">Factor
0.99</a> due to great community support
and some work on the <a href="https://github.com/codewars/testest">Codewars test
vocabulary</a> that was developed
specifically for use with the Codewars system.</p>
<p>It’s pretty fun to complete the katas and then see the solutions that other
users have contributed.</p>
<p>Give it a try!</p>
John Benediktsson: Special Numbers
2024-01-15T15:00:00.000000Z
<p>Lots of <a href="https://www.skillsyouneed.com/num/special-numbers-concepts.html">numbers are
special</a> in
various definitions of specialness. This often forms the basis of different
programming challenges. In the case of the most recent <a href="https://theweeklychallenge.org/blog/perl-weekly-challenge-252/">Perl Weekly Challenge
#252</a>, the
problem statement declares that a number is “special” in this way:</p>
<blockquote>
<p>You are given an array of integers, <code>@ints</code>.</p>
<p>Write a script to find the sum of the squares of all special elements of the
given array.</p>
<p>An element <code>$int[i]</code> of <code>@ints</code> is called special if <code>i</code> divides <code>n</code>,
i.e. <code>n % i == 0</code>, where <code>n</code> is the length of the given array. Also the
array is 1-indexed for the task.</p>
</blockquote>
<p>And it gives two examples, which we can use as test cases later when we solve
this in <a href="https://factorcode.org">Factor</a>.</p>
<p><em><strong>Spoiler Alert</strong>: This weekly challenge deadline is due in a few days from now
(on January 21, 2024 at 23:59). This blog post provides some solutions to this
challenge. Please don’t read on if you intend to complete the challenge on your
own.</em></p>
<h2 id="solution">Solution</h2>
<p>Let’s find the special indices – which are just the
<a href="https://docs.factorcode.org/content/word-divisors,math.primes.factors.html">divisors</a>
of the length of the input sequence – and then take the elements at those
special indices:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">special-numbers</span> <span class="nf">( </span><span class="nv">ints</span> <span class="nf">-- </span><span class="nv">ints'</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> [ <span class="nb">length </span>divisors <span class="m">1 </span>v-n ] [ <span class="nb">nths </span>] <span class="nb">bi </span><span class="k">;
</span></span></span></code></pre></div><p>And so, we can solve this problem for both provided examples:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl">{ <span class="m">21 </span>} [ { <span class="m">1 2 3 4 </span>} special-numbers sum-of-squares ] unit-test
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">{ <span class="m">63 </span>} [ { <span class="m">2 7 1 19 18 3 </span>} special-numbers sum-of-squares ] unit-test
</span></span></code></pre></div><p>And a “script”, if we wanted to take input from the command-line, as requested:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">MAIN:</span> <span class="nf">[</span>
</span></span><span class="line"><span class="cl"> [ <span class="nb">readln </span>] [
</span></span><span class="line"><span class="cl"> split-words <span class="nb">harvest </span>[ string>number ] <span class="nb">map
</span></span></span><span class="line"><span class="cl"><span class="nb"></span> <span class="nb">dup </span>special-numbers sum-of-squares
</span></span><span class="line"><span class="cl"> <span class="s">"%u => %u\n"</span> printf
</span></span><span class="line"><span class="cl"> ] while*
</span></span><span class="line"><span class="cl">]
</span></span></code></pre></div>
John Benediktsson: Building Hangman
2023-12-26T15:00:00.000000Z
<p>Recently, <a href="https://realpython.com/team/jfincher/">Jon Fincher</a> published an
interesting <a href="https://python.org">Python</a> tutorial describing steps to <a href="https://realpython.com/python-hangman/">build a
hangman game for the command line in
Python</a>. It provides for a nice demo of
different programming language features including taking user input, printing
to the screen, storing game state, and performing some logic until the game is
completed.</p>
<p>A game in progress might look like this – with <em>hangman</em> as the word being
guessed:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> ┌───┐
</span></span><span class="line"><span class="cl"> │ │
</span></span><span class="line"><span class="cl"> O │
</span></span><span class="line"><span class="cl"> ─┼─ │
</span></span><span class="line"><span class="cl"> <span class="nb">/ </span>│ │
</span></span><span class="line"><span class="cl"> │ │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> └──────┘
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Your word is: _ a n _ _ a n
</span></span><span class="line"><span class="cl">Your guesses: a e r s n
</span></span></code></pre></div><p>I thought it would be fun to show how to build a similar <a href="https://en.wikipedia.org/wiki/Hangman_(game)">hangman
game</a> in
<a href="https://factorcode.org">Factor</a>, using similar steps.</p>
<h2 id="step-1-set-up-the-hangman-project">Step 1: Set Up the Hangman Project</h2>
<p>We need to create the <code>hangman</code> vocabulary to store our work. We can use the
<code>scaffold-vocab</code> word to create a new vocabulary. It will prompt for which
vocab-root to place the new vocabulary into.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> <span class="kn">USE:</span> <span class="nn">tools.scaffold</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> <span class="s">"hangman"</span> scaffold-vocab
</span></span></code></pre></div><p>And then open the vocab in your favorite text editor:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> <span class="s">"hangman"</span> edit-vocab
</span></span></code></pre></div><p>We can start the file with all these imports, which we will be using in the
implementation below and two symbols that we will use to hold the state of
our game.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="kn">USING:</span> <span class="nn">combinators.short-circuit</span> <span class="nn">io</span> <span class="nn">io.encodings.utf8</span> <span class="nn">io.files</span>
</span></span><span class="line"><span class="cl"><span class="nn">kernel</span> <span class="nn">make</span> <span class="nn">math</span> <span class="nn">multiline</span> <span class="nn">namespaces</span> <span class="nn">random</span> <span class="nn">sequences</span>
</span></span><span class="line"><span class="cl"><span class="nn">sequences.interleaved</span> <span class="nn">sets</span> <span class="nn">sorting</span> <span class="nn">unicode</span> <span class="k">;
</span></span></span><span class="line"><span class="cl"><span class="k"></span>
</span></span><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">hangman</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">SYMBOL:</span> <span class="nf">target-word</span> <span class="c">! the word being guessed</span>
</span></span><span class="line"><span class="cl"><span class="k">SYMBOL:</span> <span class="nf">guesses</span> <span class="c">! all of the guessed letters</span>
</span></span></code></pre></div><h2 id="step-2-select-a-word-to-guess">Step 2: Select a Word to Guess</h2>
<p>Let’s create a <code>vocab:hangman/words.txt</code> file containing all of the possible
word choices. The original tutorial had a list of words that you can reference
– you are welcome to copy my file or create your own:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> <span class="kn">USE:</span> <span class="nn">http.client</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> <span class="s">"https://raw.githubusercontent.com/mrjbq7/re-factor/master/hangman/words.txt"</span>
</span></span><span class="line"><span class="cl"> <span class="s">"vocab:hangman/words.txt"</span> download-to
</span></span></code></pre></div><p>Now we can add this word to read the file into memory and then choose a random
line.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">random-word</span> <span class="nf">( -- </span><span class="nv">word</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> <span class="s">"vocab:hangman/words.txt"</span> utf8 file-lines random <span class="k">;
</span></span></span></code></pre></div><h2 id="step-3-get-and-validate-the-players-input">Step 3: Get and Validate the Player’s Input</h2>
<p>The user will use the
<a href="https://docs.factorcode.org/content/word-readln,io.html">readln</a> word to
provide input, and we will validate it by making sure the line contains a
single character that is not already in our <code>guesses</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">valid-guess?</span> <span class="nf">( </span><span class="nv">input</span> <span class="nf">-- </span><span class="nv">?</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> {
</span></span><span class="line"><span class="cl"> [ <span class="nb">length </span><span class="m">1 </span><span class="nb">= </span>]
</span></span><span class="line"><span class="cl"> [ lower? ]
</span></span><span class="line"><span class="cl"> [ <span class="nb">first </span>guesses <span class="nb">get </span>?adjoin ]
</span></span><span class="line"><span class="cl"> } 1&& <span class="k">;
</span></span></span></code></pre></div><p>Reading the player input is then just looping until we get a valid guess:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">player-guess</span> <span class="nf">( -- </span><span class="nv">ch</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> <span class="no">f </span>[ <span class="nb">dup </span>valid-guess? ] [ <span class="nb">drop readln </span>] <span class="nb">do until first </span><span class="k">;
</span></span></span></code></pre></div><h2 id="step-4-display-the-guessed-letters-and-word">Step 4: Display the Guessed Letters and Word</h2>
<p>We can display the guessed letters as a sorted, space-separated list:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">spaces</span> <span class="nf">( </span><span class="nv">str</span> <span class="nf">-- </span><span class="nv">str'</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> <span class="sc">CHAR: \s </span><interleaved> <span class="k">;
</span></span></span><span class="line"><span class="cl"><span class="k"></span>
</span></span><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">guessed-letters</span> <span class="nf">( -- </span><span class="nv">str</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> guesses <span class="nb">get </span>members sort spaces <span class="k">;
</span></span></span></code></pre></div><p>And the target word is also space-separated with blanks for letters we have not
guessed, or the actual letters if they have been guessed successfully:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">guessed-word</span> <span class="nf">( -- </span><span class="nv">str</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> target-word <span class="nb">get </span>guesses <span class="nb">get </span>'[
</span></span><span class="line"><span class="cl"> <span class="nb">dup </span>_ in? [ <span class="nb">drop </span><span class="sc">CHAR: _ </span>] <span class="nb">unless
</span></span></span><span class="line"><span class="cl"><span class="nb"></span> ] <span class="nb">map </span>spaces <span class="k">;
</span></span></span></code></pre></div><h2 id="step-5-draw-the-hanged-man">Step 5: Draw the Hanged Man</h2>
<p>We first calculate the number of wrong guesses, by <a href="https://docs.factorcode.org/content/word-diff,sets.html">set
difference</a> between
the guesses and our target word:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">#wrong-guesses</span> <span class="nf">( -- </span><span class="nv">n</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> guesses <span class="nb">get </span>target-word <span class="nb">get </span>diff cardinality <span class="k">;
</span></span></span></code></pre></div><p>Displaying the “hanged man” requires a bit more lines of code that the rest of
the program, using the number of wrong guesses to pick which to output:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">CONSTANT:</span> <span class="nf">HANGED-MAN</span> {
</span></span><span class="line"><span class="cl">[[
</span></span><span class="line"><span class="cl"> ┌───┐
</span></span><span class="line"><span class="cl"> │ │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> └──────┘
</span></span><span class="line"><span class="cl">]] [[
</span></span><span class="line"><span class="cl"> ┌───┐
</span></span><span class="line"><span class="cl"> │ │
</span></span><span class="line"><span class="cl"> O │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> └──────┘
</span></span><span class="line"><span class="cl">]] [[
</span></span><span class="line"><span class="cl"> ┌───┐
</span></span><span class="line"><span class="cl"> │ │
</span></span><span class="line"><span class="cl"> O │
</span></span><span class="line"><span class="cl"> ─┼─ │
</span></span><span class="line"><span class="cl"> │ │
</span></span><span class="line"><span class="cl"> │ │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> └──────┘
</span></span><span class="line"><span class="cl">]] [[
</span></span><span class="line"><span class="cl"> ┌───┐
</span></span><span class="line"><span class="cl"> │ │
</span></span><span class="line"><span class="cl"> O │
</span></span><span class="line"><span class="cl"> ─┼─ │
</span></span><span class="line"><span class="cl"> <span class="nb">/ </span>│ │
</span></span><span class="line"><span class="cl"> │ │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> └──────┘
</span></span><span class="line"><span class="cl">]] [[
</span></span><span class="line"><span class="cl"> ┌───┐
</span></span><span class="line"><span class="cl"> │ │
</span></span><span class="line"><span class="cl"> O │
</span></span><span class="line"><span class="cl"> ─┼─ │
</span></span><span class="line"><span class="cl"> <span class="nb">/ </span>│ <span class="no">\ │</span>
</span></span><span class="line"><span class="cl"> │ │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> └──────┘
</span></span><span class="line"><span class="cl">]] [[
</span></span><span class="line"><span class="cl"> ┌───┐
</span></span><span class="line"><span class="cl"> │ │
</span></span><span class="line"><span class="cl"> O │
</span></span><span class="line"><span class="cl"> ─┼─ │
</span></span><span class="line"><span class="cl"> <span class="nb">/ </span>│ <span class="no">\ │</span>
</span></span><span class="line"><span class="cl"> │ │
</span></span><span class="line"><span class="cl"> ─┴─ │
</span></span><span class="line"><span class="cl"> <span class="nb">/ </span> │
</span></span><span class="line"><span class="cl"> │ │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> └──────┘
</span></span><span class="line"><span class="cl">]] [[
</span></span><span class="line"><span class="cl"> ┌───┐
</span></span><span class="line"><span class="cl"> │ │
</span></span><span class="line"><span class="cl"> O │
</span></span><span class="line"><span class="cl"> ─┼─ │
</span></span><span class="line"><span class="cl"> <span class="nb">/ </span>│ <span class="no">\ │</span>
</span></span><span class="line"><span class="cl"> │ │
</span></span><span class="line"><span class="cl"> ─┴─ │
</span></span><span class="line"><span class="cl"> <span class="nb">/ </span> <span class="no">\ │</span>
</span></span><span class="line"><span class="cl"> │ │ │
</span></span><span class="line"><span class="cl"> │
</span></span><span class="line"><span class="cl"> └──────┘
</span></span><span class="line"><span class="cl">]]
</span></span><span class="line"><span class="cl">}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">hanged-man.</span> <span class="nf">( -- )
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> #wrong-guesses HANGED-MAN <span class="nb">nth print </span><span class="k">;
</span></span></span></code></pre></div><h2 id="step-6-figure-out-when-the-game-is-over">Step 6: Figure Out When the Game Is Over</h2>
<p>The game is lost when the player has too many wrong guesses:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">lose?</span> <span class="nf">( -- </span><span class="nv">?</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> #wrong-guesses HANGED-MAN <span class="nb">length </span><span class="m">1 </span><span class="nb">- >= </span><span class="k">;
</span></span></span></code></pre></div><p>The game is won when the word has no unknown letters:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">win?</span> <span class="nf">( -- </span><span class="nv">?</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> target-word <span class="nb">get </span>guesses <span class="nb">get </span>diff null? <span class="k">;
</span></span></span></code></pre></div><p>And the game is over when it is won or lost:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">game-over?</span> <span class="nf">( -- </span><span class="nv">?</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> { [ win? ] [ lose? ] } 0|| <span class="k">;
</span></span></span></code></pre></div><h2 id="step-7-run-the-game-loop">Step 7: Run the Game Loop</h2>
<p>It is frequently useful in <a href="https://factorcode.org">Factor</a> to build helper
words that, for example, set up some of the state that our program will use and
then run a provided
<a href="https://docs.factorcode.org/content/article-quotations.html">quotation</a>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">with-hangman</span> <span class="nf">( </span><span class="nv">quot</span> <span class="nf">-- )
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> [
</span></span><span class="line"><span class="cl"> random-word target-word ,,
</span></span><span class="line"><span class="cl"> HS{ } <span class="nb">clone </span>guesses ,,
</span></span><span class="line"><span class="cl"> ] H{ } make <span class="nb">swap with-variables </span><span class="k">; inline
</span></span></span></code></pre></div><p>And then we can use that to build and run the game:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">:</span> <span class="nf">play-hangman</span> <span class="nf">( -- )
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> [
</span></span><span class="line"><span class="cl"> <span class="s">"Welcome to Hangman!"</span> <span class="nb">print
</span></span></span><span class="line"><span class="cl"><span class="nb"></span>
</span></span><span class="line"><span class="cl"> [ game-over? ] [
</span></span><span class="line"><span class="cl"> hanged-man.
</span></span><span class="line"><span class="cl"> <span class="s">"Your word is: "</span> <span class="nb">write </span>guessed-word <span class="nb">print
</span></span></span><span class="line"><span class="cl"><span class="nb"></span> <span class="s">"Your guesses: "</span> <span class="nb">write </span>guessed-letters <span class="nb">print
</span></span></span><span class="line"><span class="cl"><span class="nb"></span>
</span></span><span class="line"><span class="cl"> <span class="nb">nl </span><span class="s">"What is your guess? "</span> <span class="nb">write flush
</span></span></span><span class="line"><span class="cl"><span class="nb"></span>
</span></span><span class="line"><span class="cl"> player-guess target-word <span class="nb">get </span>in?
</span></span><span class="line"><span class="cl"> <span class="s">"Great guess!"</span> <span class="s">"Sorry, it's not there."</span> <span class="nb">? print
</span></span></span><span class="line"><span class="cl"><span class="nb"></span> ] <span class="nb">until
</span></span></span><span class="line"><span class="cl"><span class="nb"></span>
</span></span><span class="line"><span class="cl"> hanged-man.
</span></span><span class="line"><span class="cl"> lose? <span class="s">"Sorry, you lost!"</span> <span class="s">"Congrats! You did it!"</span> <span class="nb">? print
</span></span></span><span class="line"><span class="cl"><span class="nb"></span> <span class="s">"Your word was: "</span> <span class="nb">write </span>target-word <span class="nb">get print
</span></span></span><span class="line"><span class="cl"><span class="nb"></span> ] with-hangman <span class="k">;
</span></span></span></code></pre></div><p>One last thing we can do is set this word as the <a href="https://docs.factorcode.org/content/word-MAIN__colon__,syntax.html">main entry
point</a> of
our vocabulary:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">MAIN:</span> <span class="nf">play-hangman</span>
</span></span></code></pre></div><h2 id="next-steps">Next Steps</h2>
<p>Well, that’s kind of fun. You can run this in the listener:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> <span class="s">"hangman"</span> run
</span></span></code></pre></div><p>Or at the command-line:</p>
<pre tabindex="0"><code>$ ./factor -run=hangman
</code></pre><p>The source code is available on my
<a href="https://github.com/mrjbq7/re-factor/tree/master/hangman">GitHub</a>.</p>
John Benediktsson: JavaScript Arrays
2023-11-22T15:00:00.000000Z
<p><a href="https://en.wikipedia.org/wiki/JSON">JSON</a> or “JavaScript Object Notation”
is widely used as a data format for storing, transmitting, and retrieving
data objects. It is language-independent and has parsers in most modern
programming languages. <a href="https://factorcode.org">Factor</a> is no exception,
containing the <a href="https://docs.factorcode.org/content/article-json.html">json
vocabulary</a>.</p>
<p>I wanted to go over some <a href="https://wtfjs.com">wtfjs</a> that relates to JavaScript
Arrays and JavaScript Objects, and then see how something similar might work in
<a href="https://factorcode.org">Factor</a>!</p>
<h3 id="in-javascript">In JavaScript</h3>
<p>You can define an array:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">person</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"John"</span><span class="p">,</span> <span class="s2">"Doe"</span><span class="p">,</span> <span class="mi">46</span><span class="p">];</span>
</span></span></code></pre></div><p>Or you can define an object:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">person</span> <span class="o">=</span> <span class="p">{</span><span class="nx">firstName</span><span class="o">:</span> <span class="s2">"John"</span><span class="p">,</span> <span class="nx">lastName</span><span class="o">:</span> <span class="s2">"Doe"</span><span class="p">,</span> <span class="nx">age</span><span class="o">:</span> <span class="mi">46</span><span class="p">};</span>
</span></span></code></pre></div><p>You can start with an array and set it’s values by index:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">person</span> <span class="o">=</span> <span class="p">[];</span>
</span></span><span class="line"><span class="cl"><span class="nx">person</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"John"</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nx">person</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"Doe"</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nx">person</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="mi">46</span><span class="p">;</span>
</span></span></code></pre></div><p>You can start with an object and set it’s values by key:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">person</span> <span class="o">=</span> <span class="p">{};</span>
</span></span><span class="line"><span class="cl"><span class="nx">person</span><span class="p">[</span><span class="s2">"firstName"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"John"</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nx">person</span><span class="p">[</span><span class="s2">"lastName"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"Doe"</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nx">person</span><span class="p">[</span><span class="s2">"age"</span><span class="p">]</span> <span class="o">=</span> <span class="mi">46</span><span class="p">;</span>
</span></span></code></pre></div><p>Or, using “dot notation” on an object:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">person</span> <span class="o">=</span> <span class="p">{};</span>
</span></span><span class="line"><span class="cl"><span class="nx">person</span><span class="p">.</span><span class="nx">firstName</span> <span class="o">=</span> <span class="s2">"John"</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nx">person</span><span class="p">.</span><span class="nx">lastName</span> <span class="o">=</span> <span class="s2">"Doe"</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nx">person</span><span class="p">.</span><span class="nx">age</span> <span class="o">=</span> <span class="mi">46</span><span class="p">;</span>
</span></span></code></pre></div><p>In JavaScript, arrays are indexed by number, and objects are indexed by
name. But, you can mix these and create <em>association arrays</em> that can be…
both?</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="o">></span> <span class="kr">const</span> <span class="nx">list</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"A"</span><span class="p">,</span> <span class="s2">"B"</span><span class="p">,</span> <span class="s2">"C"</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="o">></span> <span class="nx">list</span><span class="p">.</span><span class="nx">length</span>
</span></span><span class="line"><span class="cl"><span class="mi">3</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">></span> <span class="nx">list</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="s2">"A"</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">></span> <span class="nx">list</span><span class="p">[</span><span class="s2">"0"</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="s2">"A"</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">></span> <span class="nx">list</span><span class="p">.</span><span class="nx">key</span> <span class="o">=</span> <span class="s2">"value"</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="o">></span> <span class="nx">list</span><span class="p">.</span><span class="nx">length</span>
</span></span><span class="line"><span class="cl"><span class="mi">3</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">></span> <span class="nx">list</span><span class="p">.</span><span class="nx">key</span>
</span></span><span class="line"><span class="cl"><span class="s2">"value"</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">></span> <span class="nb">Object</span><span class="p">.</span><span class="nx">assign</span><span class="p">({},</span> <span class="nx">list</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span><span class="mi">0</span><span class="o">:</span> <span class="s2">"A"</span><span class="p">,</span> <span class="mi">1</span><span class="o">:</span> <span class="s2">"B"</span><span class="p">,</span> <span class="mi">2</span><span class="o">:</span> <span class="s2">"C"</span><span class="p">,</span> <span class="nx">key</span><span class="o">:</span> <span class="s2">"value"</span><span class="p">}</span>
</span></span></code></pre></div><p>That’s kinda weird.</p>
<h3 id="in-factor">In Factor</h3>
<p>Maybe <a href="https://factorcode.org">Factor</a> needs something like that? What if we
define a type that has both a sequence and an assoc, and supports both the
<a href="https://docs.factorcode.org/content/article-sequence-protocol.html">sequence
protocol</a>
and the <a href="https://docs.factorcode.org/content/article-assocs-protocol.html">assoc
protocol</a>.
How might that look?</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="k">TUPLE:</span> <span class="nc">js-array</span> <span class="nv">seq</span> <span class="nv">assoc</span> <span class="k">;
</span></span></span><span class="line"><span class="cl"><span class="k"></span>
</span></span><span class="line"><span class="cl"><span class="k">:</span> <span class="nf"><js-array></span> <span class="nf">( -- </span><span class="nv">js-array</span> <span class="nf">)
</span></span></span><span class="line"><span class="cl"><span class="nf"></span> V{ } <span class="nb">clone </span>H{ } <span class="nb">clone </span>js-array <span class="nb">boa </span><span class="k">;
</span></span></span><span class="line"><span class="cl"><span class="k"></span>
</span></span><span class="line"><span class="cl"><span class="k">INSTANCE:</span> <span class="nc">js-array</span> <span class="nc">sequence</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">CONSULT: sequence-protocol js-array seq>> <span class="k">;
</span></span></span><span class="line"><span class="cl"><span class="k"></span>
</span></span><span class="line"><span class="cl"><span class="k">INSTANCE:</span> <span class="nc">js-array</span> <span class="nc">assoc</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">CONSULT: assoc-protocol js-array assoc>> <span class="k">;
</span></span></span></code></pre></div><p>And now we can do something kinda similar:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-factor" data-lang="factor"><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> <js-array>
</span></span><span class="line"><span class="cl"> { <span class="s">"A"</span> <span class="s">"B"</span> <span class="s">"C"</span> } [ <span class="nb">suffix! </span>] <span class="nb">each
</span></span></span><span class="line"><span class="cl"><span class="nb"></span> <span class="s">"value"</span> <span class="s">"key"</span> <span class="nb">pick set-at
</span></span></span><span class="line"><span class="cl"><span class="nb"></span>
</span></span><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> <span class="nb">dup first </span><span class="m">.
</span></span></span><span class="line"><span class="cl"><span class="m"></span><span class="s">"A"</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> <span class="nb">dup length </span><span class="m">.
</span></span></span><span class="line"><span class="cl"><span class="m">3
</span></span></span><span class="line"><span class="cl"><span class="m"></span>
</span></span><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> <span class="nb">dup </span>members <span class="m">.
</span></span></span><span class="line"><span class="cl"><span class="m"></span>V{ <span class="s">"A"</span> <span class="s">"B"</span> <span class="s">"C"</span> }
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> <span class="nb">dup >alist </span><span class="m">.
</span></span></span><span class="line"><span class="cl"><span class="m"></span>{ { <span class="s">"key"</span> <span class="s">"value"</span> } }
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">IN:</span> <span class="nn">scratchpad</span> <span class="s">"key"</span> <span class="nb">of </span><span class="m">.
</span></span></span><span class="line"><span class="cl"><span class="m"></span><span class="s">"value"</span>
</span></span></code></pre></div><p>Well, it doesn’t handle converting
<a href="https://docs.factorcode.org/content/article-strings.html">string</a> keys so that
<code>"0" of</code> would return the same value as <code>first</code>. And it doesn’t handle
combining all the number indexed keys and name indexed keys to an <code>alist</code>
output like the <code>Object.assign({}, ...)</code> call above. And probably a few
other idiosyncrasies of the JavaScript association array that I’m not familiar
with…</p>
<p>But do we like this? I dunno yet.</p>