Jekyll2023-02-16T15:41:00+01:00http://writeonly.pl/feed.xml⚡WriteOnly.plBlog o programowaniu w językach Haskell, Scala Native i Scala.js oraz generatorze stron Jekyll, systemie kontroli wersji Git i użyciu wiersza poleceń CLIKamil AdamZależności Funkcyjne w Haskellu2021-06-02T00:00:00+02:002021-06-02T00:00:00+02:00http://writeonly.pl/haskell-eta/functional-dependency<p>Spytano mnie raz,
co jest trudnego w <strong><a href="/langs/haskell">Haskellu</a></strong>,
co jednocześnie jest łatwe w OOP.
Np. stworzenie interfejsu kolekcji i danie możliwości implementowania go klientom-użytkownikom.
W tym celu potrzebujemy <a href="/tags/type-class">Klasę Typu</a> od dwóch parametrów.
Ale żeby mieć dobry interfejs, to nie wystarczy.
O czym się przekonaliśmy w artykule <a href="/pattern-matching">Abstrakcja i dopasowanie do wzorców</a>.</p>
<p>Okazuje się,
że tego,
czego nam brakowało to <a href="/tags/functional-dependency">Zależności Funkcyjne</a> (ang. <em>Functional Dependency</em>).</p>
<h2 id="składnia-zależności-funkcyjnych">Składnia Zależności Funkcyjnych</h2>
<p>Poszukując przykładu interfejsu dla kolekcji można trafić na taki przykład <a href="/tags/functional-dependency">Zależności Funkcyjnych</a>:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">class</span> <span class="kt">Collects</span> <span class="n">e</span> <span class="n">ce</span> <span class="o">|</span> <span class="n">ce</span> <span class="o">-></span> <span class="n">e</span> <span class="kr">where</span>
<span class="n">empty</span> <span class="o">::</span> <span class="n">ce</span>
<span class="n">insert</span> <span class="o">::</span> <span class="n">e</span> <span class="o">-></span> <span class="n">ce</span> <span class="o">-></span> <span class="n">ce</span>
<span class="n">member</span> <span class="o">::</span> <span class="n">e</span> <span class="o">-></span> <span class="n">ce</span> <span class="o">-></span> <span class="kt">Bool</span>
<span class="n">toList</span> <span class="o">::</span> <span class="n">ce</span> <span class="o">-></span> <span class="p">[</span><span class="n">e</span><span class="p">]</span>
</code></pre></div></div>
<p>Czyli jedyna nowość to <code class="language-plaintext highlighter-rouge">| ce -> e </code>.
Zapis ten pozwala na uzależnienie jednego typu od drugiego.</p>
<p>Implementacja wygląda jak implementacja <a href="/tags/type-class">Klasy Typu</a> dla dwóch parametrów:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">instance</span> <span class="kt">Eq</span> <span class="n">e</span> <span class="o">=></span> <span class="kt">Collects</span> <span class="n">e</span> <span class="p">[</span><span class="n">e</span><span class="p">]</span> <span class="kr">where</span>
<span class="n">empty</span> <span class="o">=</span> <span class="kt">[]</span>
<span class="n">insert</span> <span class="n">e</span> <span class="n">l</span> <span class="o">=</span> <span class="p">(</span><span class="n">e</span><span class="o">:</span><span class="n">l</span><span class="p">)</span>
<span class="n">member</span> <span class="n">e</span> <span class="kt">[]</span> <span class="o">=</span> <span class="kt">False</span>
<span class="n">member</span> <span class="n">e</span> <span class="p">(</span><span class="n">x</span><span class="o">:</span><span class="n">xs</span><span class="p">)</span>
<span class="o">|</span> <span class="n">e</span> <span class="o">==</span> <span class="n">x</span> <span class="o">=</span> <span class="kt">True</span>
<span class="o">|</span> <span class="n">otherwise</span> <span class="o">=</span> <span class="n">member</span> <span class="n">e</span> <span class="n">xs</span>
<span class="n">toList</span> <span class="n">l</span> <span class="o">=</span> <span class="n">l</span>
</code></pre></div></div>
<h2 id="zależności-funkcyjne-w-helma">Zależności Funkcyjne w HelMA</h2>
<p>Główna zmiana polegała na dodaniu <code class="language-plaintext highlighter-rouge">| c -> e</code> do <a href="https://github.com/helvm/helma/blob/v0.6.6.0/hs/src/HelVM/HelMA/Common/Memories/Stack.hs">Klasy Typu</a>.</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">class</span> <span class="p">(</span><span class="kt">Semigroup</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">Show</span> <span class="n">c</span><span class="p">)</span> <span class="o">=></span> <span class="kt">Stack</span> <span class="n">e</span> <span class="n">c</span> <span class="o">|</span> <span class="n">c</span> <span class="o">-></span> <span class="n">e</span> <span class="kr">where</span>
<span class="n">fromList</span> <span class="o">::</span> <span class="p">[</span><span class="n">e</span><span class="p">]</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">empty</span> <span class="o">::</span> <span class="n">c</span>
<span class="n">indexMaybe</span> <span class="o">::</span> <span class="n">c</span> <span class="o">-></span> <span class="kt">Index</span> <span class="o">-></span> <span class="kt">Maybe</span> <span class="n">e</span>
<span class="n">lookup</span> <span class="o">::</span> <span class="kt">Index</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="kt">Maybe</span> <span class="n">e</span>
<span class="n">splitAt</span> <span class="o">::</span> <span class="kt">Index</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="p">(</span><span class="n">c</span> <span class="p">,</span> <span class="n">c</span><span class="p">)</span>
<span class="n">drop</span> <span class="o">::</span> <span class="kt">Index</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">pop1</span> <span class="o">::</span> <span class="n">c</span> <span class="o">-></span> <span class="p">(</span><span class="n">e</span> <span class="p">,</span> <span class="n">c</span><span class="p">)</span>
<span class="n">pop2</span> <span class="o">::</span> <span class="n">c</span> <span class="o">-></span> <span class="p">(</span><span class="n">e</span> <span class="p">,</span> <span class="n">e</span> <span class="p">,</span> <span class="n">c</span><span class="p">)</span>
<span class="n">indexMaybe</span> <span class="o">=</span> <span class="n">flip</span> <span class="n">lookup</span>
<span class="n">lookup</span> <span class="o">=</span> <span class="n">flip</span> <span class="n">indexMaybe</span>
</code></pre></div></div>
<p>Przy okazji poprawiłem trochę sygnatury metod.</p>
<p>Musimy też poprawić nasze implementacje (instancje):</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">instance</span> <span class="kt">Show</span> <span class="n">e</span> <span class="o">=></span> <span class="kt">Stack</span> <span class="n">e</span> <span class="p">[</span><span class="n">e</span><span class="p">]</span> <span class="kr">where</span>
<span class="n">fromList</span> <span class="o">=</span> <span class="n">id</span>
<span class="n">empty</span> <span class="o">=</span> <span class="kt">[]</span>
<span class="n">lookup</span> <span class="n">i</span> <span class="n">c</span> <span class="o">=</span> <span class="n">c</span> <span class="o">!!?</span> <span class="n">i</span>
<span class="n">splitAt</span> <span class="n">i</span> <span class="n">c</span> <span class="o">=</span> <span class="kt">List</span><span class="o">.</span><span class="n">splitAt</span> <span class="n">i</span> <span class="n">c</span>
<span class="n">drop</span> <span class="n">i</span> <span class="n">c</span> <span class="o">=</span> <span class="kt">List</span><span class="o">.</span><span class="n">drop</span> <span class="n">i</span> <span class="n">c</span>
<span class="n">pop1</span> <span class="p">(</span><span class="n">e</span> <span class="o">:</span> <span class="n">c</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="n">e</span> <span class="p">,</span> <span class="n">c</span><span class="p">)</span>
<span class="n">pop1</span> <span class="n">c</span> <span class="o">=</span> <span class="n">error</span> <span class="o">$</span> <span class="s">"Empty c "</span> <span class="o"><></span> <span class="n">show</span> <span class="n">c</span>
<span class="n">pop2</span> <span class="p">(</span><span class="n">e</span> <span class="o">:</span> <span class="n">e'</span> <span class="o">:</span> <span class="n">c</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="n">e</span> <span class="p">,</span> <span class="n">e'</span><span class="p">,</span> <span class="n">c</span><span class="p">)</span>
<span class="n">pop2</span> <span class="n">c</span> <span class="o">=</span> <span class="n">error</span> <span class="o">$</span> <span class="s">"Empty c "</span> <span class="o"><></span> <span class="n">show</span> <span class="n">c</span>
<span class="kr">instance</span> <span class="kt">Show</span> <span class="n">e</span> <span class="o">=></span> <span class="kt">Stack</span> <span class="n">e</span> <span class="p">(</span><span class="kt">Seq</span> <span class="n">e</span><span class="p">)</span> <span class="kr">where</span>
<span class="n">fromList</span> <span class="o">=</span> <span class="kt">Seq</span><span class="o">.</span><span class="n">fromList</span>
<span class="n">empty</span> <span class="o">=</span> <span class="kt">Seq</span><span class="o">.</span><span class="n">empty</span>
<span class="n">lookup</span> <span class="n">i</span> <span class="n">c</span> <span class="o">=</span> <span class="kt">Seq</span><span class="o">.</span><span class="n">lookup</span> <span class="n">i</span> <span class="n">c</span>
<span class="n">splitAt</span> <span class="n">i</span> <span class="n">c</span> <span class="o">=</span> <span class="kt">Seq</span><span class="o">.</span><span class="n">splitAt</span> <span class="n">i</span> <span class="n">c</span>
<span class="n">drop</span> <span class="n">i</span> <span class="n">c</span> <span class="o">=</span> <span class="kt">Seq</span><span class="o">.</span><span class="n">drop</span> <span class="n">i</span> <span class="n">c</span>
<span class="n">pop1</span> <span class="p">(</span><span class="n">e</span> <span class="o">:<|</span> <span class="n">c</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="n">e</span> <span class="p">,</span> <span class="n">c</span><span class="p">)</span>
<span class="n">pop1</span> <span class="n">c</span> <span class="o">=</span> <span class="n">error</span> <span class="o">$</span> <span class="s">"Empty c "</span> <span class="o"><></span> <span class="n">show</span> <span class="n">c</span>
<span class="n">pop2</span> <span class="p">(</span><span class="n">e</span> <span class="o">:<|</span> <span class="n">e'</span> <span class="o">:<|</span> <span class="n">c</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="n">e</span> <span class="p">,</span> <span class="n">e'</span><span class="p">,</span> <span class="n">c</span><span class="p">)</span>
<span class="n">pop2</span> <span class="n">c</span> <span class="o">=</span> <span class="n">error</span> <span class="o">$</span> <span class="s">"Empty c "</span> <span class="o"><></span> <span class="n">show</span> <span class="n">c</span>
</code></pre></div></div>
<p>Od tej pory nie potrzebujemy już rzutować elementu stosu na <code class="language-plaintext highlighter-rouge">Symbol</code>,
a więc wiele metod pomocniczych do manipulacji stosem możemy przenieść bezpośrednio do modułu <a href="https://github.com/helvm/helma/blob/v0.6.6.0/hs/src/HelVM/HelMA/Common/Memories/Stack.hs">Stack</a>:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- Stack instructions</span>
<span class="n">halibut</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Integral</span> <span class="n">e</span> <span class="p">,</span> <span class="kt">Stack</span> <span class="n">e</span> <span class="n">c</span><span class="p">)</span> <span class="o">=></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">halibut</span> <span class="n">c</span>
<span class="o">|</span> <span class="n">i</span> <span class="o"><=</span> <span class="mi">0</span> <span class="o">=</span> <span class="n">copy</span> <span class="p">(</span><span class="n">negate</span> <span class="n">i</span><span class="p">)</span> <span class="n">c'</span>
<span class="o">|</span> <span class="n">otherwise</span> <span class="o">=</span> <span class="n">move</span> <span class="n">i</span> <span class="n">c'</span>
<span class="kr">where</span>
<span class="n">i</span> <span class="o">=</span> <span class="n">fromIntegral</span> <span class="n">e</span>
<span class="p">(</span><span class="n">e</span> <span class="p">,</span> <span class="n">c'</span><span class="p">)</span> <span class="o">=</span> <span class="n">pop1</span> <span class="n">c</span>
<span class="n">move</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="n">e</span> <span class="n">c</span> <span class="o">=></span> <span class="kt">Index</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">move</span> <span class="n">i</span> <span class="n">c</span> <span class="o">=</span> <span class="n">c1</span> <span class="o"><></span> <span class="n">c2</span> <span class="o"><></span> <span class="n">c3</span> <span class="kr">where</span>
<span class="p">(</span><span class="n">c1</span> <span class="p">,</span> <span class="n">c3</span><span class="p">)</span> <span class="o">=</span> <span class="n">splitAt</span> <span class="mi">1</span> <span class="n">c'</span>
<span class="p">(</span><span class="n">c2</span> <span class="p">,</span> <span class="n">c'</span><span class="p">)</span> <span class="o">=</span> <span class="n">splitAt</span> <span class="n">i</span> <span class="n">c</span>
<span class="n">swap</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="n">e</span> <span class="n">c</span> <span class="o">=></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">swap</span> <span class="n">c</span> <span class="o">=</span> <span class="n">push2</span> <span class="n">e'</span> <span class="n">e</span> <span class="n">c'</span> <span class="kr">where</span> <span class="p">(</span><span class="n">e</span> <span class="p">,</span> <span class="n">e'</span><span class="p">,</span> <span class="n">c'</span><span class="p">)</span> <span class="o">=</span> <span class="n">pop2</span> <span class="n">c</span>
<span class="n">discard</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="n">e</span> <span class="n">c</span> <span class="o">=></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">discard</span> <span class="o">=</span> <span class="n">drop</span> <span class="mi">1</span>
<span class="n">slide</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="n">e</span> <span class="n">c</span> <span class="o">=></span> <span class="kt">Index</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">slide</span> <span class="n">i</span> <span class="n">c</span> <span class="o">=</span> <span class="n">push1</span> <span class="n">e</span> <span class="p">(</span><span class="n">drop</span> <span class="n">i</span> <span class="n">c'</span><span class="p">)</span> <span class="kr">where</span> <span class="p">(</span><span class="n">e</span> <span class="p">,</span> <span class="n">c'</span><span class="p">)</span> <span class="o">=</span> <span class="n">pop1</span> <span class="n">c</span>
<span class="n">dup</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="n">e</span> <span class="n">c</span> <span class="o">=></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">dup</span> <span class="o">=</span> <span class="n">copy</span> <span class="mi">0</span>
<span class="n">copy</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="n">e</span> <span class="n">c</span> <span class="o">=></span> <span class="kt">Index</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">copy</span> <span class="n">i</span> <span class="n">c</span> <span class="o">=</span> <span class="n">push1</span> <span class="p">(</span><span class="n">c</span> <span class="p">`</span><span class="n">index</span><span class="p">`</span> <span class="n">i</span><span class="p">)</span> <span class="n">c</span>
<span class="c1">-- Push instructions</span>
<span class="n">pushChar1</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Num</span> <span class="n">e</span> <span class="p">,</span> <span class="kt">Stack</span> <span class="n">e</span> <span class="n">c</span><span class="p">)</span> <span class="o">=></span> <span class="kt">Char</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">pushChar1</span> <span class="o">=</span> <span class="n">genericPush1</span> <span class="o">.</span> <span class="n">ord</span>
<span class="n">genericPush1</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Integral</span> <span class="n">v</span> <span class="p">,</span> <span class="kt">Num</span> <span class="n">e</span> <span class="p">,</span> <span class="kt">Stack</span> <span class="n">e</span> <span class="n">c</span><span class="p">)</span> <span class="o">=></span> <span class="n">v</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">genericPush1</span> <span class="o">=</span> <span class="n">push1</span> <span class="o">.</span> <span class="n">fromIntegral</span>
<span class="n">push1</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="n">e</span> <span class="n">c</span> <span class="o">=></span> <span class="n">e</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">push1</span> <span class="n">e</span> <span class="o">=</span> <span class="n">pushList</span> <span class="p">[</span><span class="n">e</span><span class="p">]</span>
<span class="n">push2</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="n">e</span> <span class="n">c</span> <span class="o">=></span> <span class="n">e</span> <span class="o">-></span> <span class="n">e</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">push2</span> <span class="n">e</span> <span class="n">e'</span> <span class="o">=</span> <span class="n">pushList</span> <span class="p">[</span><span class="n">e</span> <span class="p">,</span> <span class="n">e'</span><span class="p">]</span>
<span class="n">pushList</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="n">e</span> <span class="n">c</span> <span class="o">=></span> <span class="p">[</span><span class="n">e</span><span class="p">]</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">pushList</span> <span class="n">es</span> <span class="n">c</span> <span class="o">=</span> <span class="n">fromList</span> <span class="n">es</span> <span class="o"><></span> <span class="n">c</span>
<span class="c1">----</span>
<span class="n">index</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Stack</span> <span class="n">e</span> <span class="n">c</span><span class="p">)</span> <span class="o">=></span> <span class="n">c</span> <span class="o">-></span> <span class="kt">Int</span> <span class="o">-></span> <span class="n">e</span>
<span class="n">index</span> <span class="n">c</span> <span class="n">i</span> <span class="o">=</span> <span class="n">check</span> <span class="p">(</span><span class="n">c</span> <span class="p">`</span><span class="n">indexMaybe</span><span class="p">`</span> <span class="n">i</span><span class="p">)</span> <span class="kr">where</span>
<span class="n">check</span> <span class="p">(</span><span class="kt">Just</span> <span class="n">e</span><span class="p">)</span> <span class="o">=</span> <span class="n">e</span>
<span class="n">check</span> <span class="kt">Nothing</span> <span class="o">=</span> <span class="n">error</span> <span class="o">$</span> <span class="s">"Empty stack "</span> <span class="o"><></span> <span class="n">show</span> <span class="n">c</span> <span class="o"><></span> <span class="s">" index "</span> <span class="o"><></span> <span class="n">show</span> <span class="n">i</span>
</code></pre></div></div>
<p>Możemy też zdefiniować operacje arytmetyczne:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- Arithmetic</span>
<span class="n">divMod</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Integral</span> <span class="n">e</span> <span class="p">,</span> <span class="kt">Stack</span> <span class="n">e</span> <span class="n">c</span><span class="p">)</span> <span class="o">=></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">divMod</span> <span class="o">=</span> <span class="n">binaryOps</span> <span class="p">[</span><span class="kt">Mod</span> <span class="p">,</span> <span class="kt">Div</span><span class="p">]</span>
<span class="n">sub</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Integral</span> <span class="n">e</span> <span class="p">,</span> <span class="kt">Stack</span> <span class="n">e</span> <span class="n">c</span><span class="p">)</span> <span class="o">=></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">sub</span> <span class="o">=</span> <span class="n">binaryOp</span> <span class="kt">Sub</span>
<span class="n">binaryOp</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Integral</span> <span class="n">e</span> <span class="p">,</span> <span class="kt">Stack</span> <span class="n">e</span> <span class="n">c</span><span class="p">)</span> <span class="o">=></span> <span class="kt">BinaryOperator</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">binaryOp</span> <span class="n">op</span> <span class="o">=</span> <span class="n">binaryOps</span> <span class="p">[</span><span class="n">op</span><span class="p">]</span>
<span class="n">binaryOps</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Integral</span> <span class="n">e</span> <span class="p">,</span> <span class="kt">Stack</span> <span class="n">e</span> <span class="n">c</span><span class="p">)</span> <span class="o">=></span> <span class="p">[</span><span class="kt">BinaryOperator</span><span class="p">]</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">binaryOps</span> <span class="n">ops</span> <span class="n">c</span> <span class="o">=</span> <span class="n">pushList</span> <span class="p">(</span><span class="n">calculateOps</span> <span class="n">e</span> <span class="n">e'</span> <span class="n">ops</span><span class="p">)</span> <span class="n">c'</span> <span class="kr">where</span> <span class="p">(</span><span class="n">e</span> <span class="p">,</span> <span class="n">e'</span><span class="p">,</span> <span class="n">c'</span><span class="p">)</span> <span class="o">=</span> <span class="n">pop2</span> <span class="n">c</span>
</code></pre></div></div>
<p>W tym celu wydzielimy moduł <a href="https://github.com/helvm/helma/blob/v0.6.6.0/hs/src/HelVM/HelMA/Common/BinaryOperator.hs">BinaryOperator</a>:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">module</span> <span class="nn">HelVM.HelMA.Common.BinaryOperator</span> <span class="kr">where</span>
<span class="n">calculateOps</span> <span class="o">::</span> <span class="kt">Integral</span> <span class="n">a</span> <span class="o">=></span> <span class="n">a</span> <span class="o">-></span> <span class="n">a</span> <span class="o">-></span> <span class="p">[</span><span class="kt">BinaryOperator</span><span class="p">]</span> <span class="o">-></span> <span class="p">[</span><span class="n">a</span><span class="p">]</span>
<span class="n">calculateOps</span> <span class="n">operand</span> <span class="n">operand'</span> <span class="o">=</span> <span class="n">map</span> <span class="p">(</span><span class="n">calculateOp</span> <span class="n">operand</span> <span class="n">operand'</span><span class="p">)</span>
<span class="n">calculateOp</span> <span class="o">::</span> <span class="kt">Integral</span> <span class="n">a</span> <span class="o">=></span> <span class="n">a</span> <span class="o">-></span> <span class="n">a</span> <span class="o">-></span> <span class="kt">BinaryOperator</span> <span class="o">-></span> <span class="n">a</span>
<span class="n">calculateOp</span> <span class="n">operand</span> <span class="n">operand'</span> <span class="n">operation</span> <span class="o">=</span> <span class="n">doBinary</span> <span class="n">operation</span> <span class="n">operand'</span> <span class="n">operand</span>
<span class="n">doBinary</span> <span class="o">::</span> <span class="kt">Integral</span> <span class="n">a</span> <span class="o">=></span> <span class="kt">BinaryOperator</span> <span class="o">-></span> <span class="n">a</span> <span class="o">-></span> <span class="n">a</span> <span class="o">-></span> <span class="n">a</span>
<span class="n">doBinary</span> <span class="kt">Add</span> <span class="o">=</span> <span class="p">(</span><span class="o">+</span><span class="p">)</span>
<span class="n">doBinary</span> <span class="kt">Sub</span> <span class="o">=</span> <span class="p">(</span><span class="o">-</span><span class="p">)</span>
<span class="n">doBinary</span> <span class="kt">Mul</span> <span class="o">=</span> <span class="p">(</span><span class="o">*</span><span class="p">)</span>
<span class="n">doBinary</span> <span class="kt">Div</span> <span class="o">=</span> <span class="n">div</span>
<span class="n">doBinary</span> <span class="kt">Mod</span> <span class="o">=</span> <span class="n">mod</span>
<span class="kr">data</span> <span class="kt">BinaryOperator</span> <span class="o">=</span> <span class="kt">Add</span> <span class="o">|</span> <span class="kt">Sub</span> <span class="o">|</span> <span class="kt">Mul</span> <span class="o">|</span> <span class="kt">Div</span> <span class="o">|</span> <span class="kt">Mod</span>
<span class="kr">deriving</span> <span class="p">(</span><span class="kt">Eq</span> <span class="p">,</span> <span class="kt">Show</span> <span class="p">,</span> <span class="kt">Read</span><span class="p">)</span>
</code></pre></div></div>
<h2 id="dużo-małych-klas-typów">Dużo małych Klas Typów</h2>
<p>Mam wrażenie,
że ten kod dalej nie jest odpowiednio polimorficzny.
Stworzyliśmy jeden wielki interfejs.
Co,
jednak gdybyśmy chcieli stworzyć klasyczny stos tylko z funkcjami <code class="language-plaintext highlighter-rouge">push</code> i <code class="language-plaintext highlighter-rouge">pop</code>?
Musielibyśmy wszystko pisać od początku.
Programowanie byłoby o wiele łatwiejsze,
gdybyśmy mieli polimorficzne funkcje dla kolekcji podobnie,
tak jak mamy polimorficzną metodę <code class="language-plaintext highlighter-rouge">fmap</code> zdefiniowaną w <a href="/tags/functor">Funktorze</a>.</p>
<p>Dzięki polimorfizmowi nie musimy pisać w kodzie <code class="language-plaintext highlighter-rouge">List.fmap</code>, <code class="language-plaintext highlighter-rouge">Seq.fmap</code> czy <code class="language-plaintext highlighter-rouge">IntMap.fmap</code>,
tylko wybór odpowiedniej implementacji jest ustalana na podstawie parametru.
Miło by było mieć tak samo polimorficzne metody <code class="language-plaintext highlighter-rouge">drop</code>, <code class="language-plaintext highlighter-rouge">empty</code>, <code class="language-plaintext highlighter-rouge">fromList</code>, <code class="language-plaintext highlighter-rouge">index</code>, <code class="language-plaintext highlighter-rouge">insert</code>, <code class="language-plaintext highlighter-rouge">lookup</code> i <code class="language-plaintext highlighter-rouge">splitAt</code>.</p>
<p>W tym celu tworzymy folder <code class="language-plaintext highlighter-rouge">HelMA.Common.Collections</code> i umieszczamy w nim następujące <a href="/tags/type-class">Klasy Typów</a>:</p>
<p><a href="https://github.com/helvm/helma/blob/v0.6.6.0/hs/src/HelVM/HelMA/Common/Collections/FromList.hs">FromList</a> z metodami <code class="language-plaintext highlighter-rouge">fromList</code> i <code class="language-plaintext highlighter-rouge">empty</code>,
żeby tworzyć kolekcję na podstawie listy:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">module</span> <span class="nn">HelVM.HelMA.Common.Collections.FromList</span> <span class="kr">where</span>
<span class="kr">import</span> <span class="nn">Prelude</span> <span class="k">hiding</span> <span class="p">(</span><span class="nf">fromList</span><span class="p">)</span>
<span class="kr">import</span> <span class="k">qualified</span> <span class="nn">Data.List.Index</span> <span class="k">as</span> <span class="n">List</span>
<span class="kr">import</span> <span class="k">qualified</span> <span class="nn">Data.IntMap</span> <span class="k">as</span> <span class="n">IntMap</span>
<span class="kr">import</span> <span class="k">qualified</span> <span class="nn">Data.Sequence</span> <span class="k">as</span> <span class="n">Seq</span>
<span class="n">intMapFromList</span> <span class="o">::</span> <span class="p">[</span><span class="n">e</span><span class="p">]</span> <span class="o">-></span> <span class="kt">IntMap</span> <span class="n">e</span>
<span class="n">intMapFromList</span> <span class="o">=</span> <span class="kt">IntMap</span><span class="o">.</span><span class="n">fromList</span> <span class="o">.</span> <span class="kt">List</span><span class="o">.</span><span class="n">indexed</span>
<span class="kr">class</span> <span class="kt">FromList</span> <span class="n">e</span> <span class="n">c</span> <span class="o">|</span> <span class="n">c</span> <span class="o">-></span> <span class="n">e</span> <span class="kr">where</span>
<span class="n">fromList</span> <span class="o">::</span> <span class="p">[</span><span class="n">e</span><span class="p">]</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">empty</span> <span class="o">::</span> <span class="n">c</span>
<span class="n">empty</span> <span class="o">=</span> <span class="n">fromList</span> <span class="kt">[]</span>
<span class="kr">instance</span> <span class="kt">FromList</span> <span class="n">e</span> <span class="p">[</span><span class="n">e</span><span class="p">]</span> <span class="kr">where</span>
<span class="n">fromList</span> <span class="o">=</span> <span class="n">id</span>
<span class="n">empty</span> <span class="o">=</span> <span class="kt">[]</span>
<span class="kr">instance</span> <span class="kt">FromList</span> <span class="n">e</span> <span class="p">(</span><span class="kt">Seq</span> <span class="n">e</span><span class="p">)</span> <span class="kr">where</span>
<span class="n">fromList</span> <span class="o">=</span> <span class="kt">Seq</span><span class="o">.</span><span class="n">fromList</span>
<span class="n">empty</span> <span class="o">=</span> <span class="kt">Seq</span><span class="o">.</span><span class="n">empty</span>
<span class="kr">instance</span> <span class="kt">FromList</span> <span class="n">e</span> <span class="p">(</span><span class="kt">IntMap</span> <span class="n">e</span><span class="p">)</span> <span class="kr">where</span>
<span class="n">fromList</span> <span class="o">=</span> <span class="n">intMapFromList</span>
<span class="n">empty</span> <span class="o">=</span> <span class="kt">IntMap</span><span class="o">.</span><span class="n">empty</span>
</code></pre></div></div>
<p><a href="https://github.com/helvm/helma/blob/v0.6.6.0/hs/src/HelVM/HelMA/Common/Collections/Lookup.hs">Lookup</a> z metodą <code class="language-plaintext highlighter-rouge">lookup</code>,
żeby wyszukiwać elementy w kolekcji po indeksie:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">module</span> <span class="nn">HelVM.HelMA.Common.Collections.Lookup</span> <span class="kr">where</span>
<span class="kr">import</span> <span class="k">qualified</span> <span class="nn">Data.IntMap</span> <span class="k">as</span> <span class="n">IntMap</span>
<span class="kr">import</span> <span class="k">qualified</span> <span class="nn">Data.Sequence</span> <span class="k">as</span> <span class="n">Seq</span>
<span class="n">index</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Show</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">Lookup</span> <span class="n">e</span> <span class="n">c</span><span class="p">)</span> <span class="o">=></span> <span class="n">c</span> <span class="o">-></span> <span class="kt">Int</span> <span class="o">-></span> <span class="n">e</span>
<span class="n">index</span> <span class="n">c</span> <span class="n">i</span> <span class="o">=</span> <span class="n">check</span> <span class="p">(</span><span class="n">c</span> <span class="p">`</span><span class="n">indexMaybe</span><span class="p">`</span> <span class="n">i</span><span class="p">)</span> <span class="kr">where</span>
<span class="n">check</span> <span class="p">(</span><span class="kt">Just</span> <span class="n">e</span><span class="p">)</span> <span class="o">=</span> <span class="n">e</span>
<span class="n">check</span> <span class="kt">Nothing</span> <span class="o">=</span> <span class="n">error</span> <span class="o">$</span> <span class="s">"Empty stack "</span> <span class="o"><></span> <span class="n">show</span> <span class="n">c</span> <span class="o"><></span> <span class="s">" index "</span> <span class="o"><></span> <span class="n">show</span> <span class="n">i</span>
<span class="n">indexMaybe</span> <span class="o">::</span> <span class="kt">Lookup</span> <span class="n">e</span> <span class="n">c</span> <span class="o">=></span> <span class="n">c</span> <span class="o">-></span> <span class="kt">Int</span> <span class="o">-></span> <span class="kt">Maybe</span> <span class="n">e</span>
<span class="n">indexMaybe</span> <span class="o">=</span> <span class="n">flip</span> <span class="n">lookup</span>
<span class="kr">class</span> <span class="kt">Lookup</span> <span class="n">e</span> <span class="n">c</span> <span class="o">|</span> <span class="n">c</span> <span class="o">-></span> <span class="n">e</span> <span class="kr">where</span>
<span class="n">lookup</span><span class="o">::</span> <span class="kt">Int</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="kt">Maybe</span> <span class="n">e</span>
<span class="kr">instance</span> <span class="kt">Lookup</span> <span class="n">e</span> <span class="p">[</span><span class="n">e</span><span class="p">]</span> <span class="kr">where</span>
<span class="n">lookup</span> <span class="o">=</span> <span class="n">flip</span> <span class="p">(</span><span class="o">!!?</span><span class="p">)</span>
<span class="kr">instance</span> <span class="kt">Lookup</span> <span class="n">e</span> <span class="p">(</span><span class="kt">Seq</span> <span class="n">e</span><span class="p">)</span> <span class="kr">where</span>
<span class="n">lookup</span> <span class="o">=</span> <span class="kt">Seq</span><span class="o">.</span><span class="n">lookup</span>
<span class="kr">instance</span> <span class="kt">Lookup</span> <span class="n">e</span> <span class="p">(</span><span class="kt">IntMap</span> <span class="n">e</span><span class="p">)</span> <span class="kr">where</span>
<span class="n">lookup</span> <span class="o">=</span> <span class="kt">IntMap</span><span class="o">.</span><span class="n">lookup</span>
</code></pre></div></div>
<p><a href="https://github.com/helvm/helma/blob/v0.6.6.0/hs/src/HelVM/HelMA/Common/Collections/Insert.hs">Insert</a> z metodą <code class="language-plaintext highlighter-rouge">insert</code>,
żeby wstawiać elementy do kolekcji:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">module</span> <span class="nn">HelVM.HelMA.Common.Collections.Insert</span> <span class="kr">where</span>
<span class="kr">import</span> <span class="nn">Data.Default</span>
<span class="kr">import</span> <span class="nn">Data.Sequence</span> <span class="p">((</span><span class="o">|></span><span class="p">))</span>
<span class="kr">import</span> <span class="k">qualified</span> <span class="nn">Data.IntMap</span> <span class="k">as</span> <span class="n">IntMap</span>
<span class="kr">import</span> <span class="k">qualified</span> <span class="nn">Data.Sequence</span> <span class="k">as</span> <span class="n">Seq</span>
<span class="kr">class</span> <span class="kt">Insert</span> <span class="n">e</span> <span class="n">c</span> <span class="o">|</span> <span class="n">c</span> <span class="o">-></span> <span class="n">e</span> <span class="kr">where</span>
<span class="n">insert</span> <span class="o">::</span> <span class="kt">Int</span> <span class="o">-></span> <span class="n">e</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="kr">instance</span> <span class="kt">Default</span> <span class="n">e</span> <span class="o">=></span> <span class="kt">Insert</span> <span class="n">e</span> <span class="p">[</span><span class="n">e</span><span class="p">]</span> <span class="kr">where</span>
<span class="n">insert</span> <span class="mi">0</span> <span class="n">e</span> <span class="kt">[]</span> <span class="o">=</span> <span class="p">[</span><span class="n">e</span><span class="p">]</span>
<span class="n">insert</span> <span class="mi">0</span> <span class="n">e</span> <span class="p">(</span><span class="kr">_</span><span class="o">:</span><span class="n">xs</span><span class="p">)</span> <span class="o">=</span> <span class="n">e</span> <span class="o">:</span> <span class="n">xs</span>
<span class="n">insert</span> <span class="n">i</span> <span class="n">e</span> <span class="kt">[]</span> <span class="o">=</span> <span class="n">def</span> <span class="o">:</span> <span class="n">insert</span> <span class="p">(</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="n">e</span> <span class="kt">[]</span>
<span class="n">insert</span> <span class="n">i</span> <span class="n">e</span> <span class="p">(</span><span class="n">x</span><span class="o">:</span><span class="n">xs</span><span class="p">)</span> <span class="o">=</span> <span class="n">x</span> <span class="o">:</span> <span class="n">insert</span> <span class="p">(</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="n">e</span> <span class="n">xs</span>
<span class="kr">instance</span> <span class="kt">Default</span> <span class="n">e</span> <span class="o">=></span> <span class="kt">Insert</span> <span class="n">e</span> <span class="p">(</span><span class="kt">Seq</span> <span class="n">e</span><span class="p">)</span> <span class="kr">where</span>
<span class="n">insert</span> <span class="n">i</span> <span class="n">e</span> <span class="n">c</span> <span class="o">=</span> <span class="n">insert'</span> <span class="o">$</span> <span class="kt">Seq</span><span class="o">.</span><span class="n">length</span> <span class="n">c</span> <span class="kr">where</span>
<span class="n">insert'</span> <span class="n">l</span>
<span class="o">|</span> <span class="n">i</span> <span class="o"><</span> <span class="n">l</span> <span class="o">=</span> <span class="kt">Seq</span><span class="o">.</span><span class="n">update</span> <span class="n">i</span> <span class="n">e</span> <span class="n">c</span>
<span class="o">|</span> <span class="n">otherwise</span> <span class="o">=</span> <span class="n">c</span> <span class="o"><></span> <span class="kt">Seq</span><span class="o">.</span><span class="n">replicate</span> <span class="p">(</span><span class="n">i</span> <span class="o">-</span> <span class="n">l</span><span class="p">)</span> <span class="n">def</span> <span class="o">|></span> <span class="n">e</span>
<span class="kr">instance</span> <span class="kt">Insert</span> <span class="n">e</span> <span class="p">(</span><span class="kt">IntMap</span> <span class="n">e</span><span class="p">)</span> <span class="kr">where</span>
<span class="n">insert</span> <span class="o">=</span> <span class="kt">IntMap</span><span class="o">.</span><span class="n">insert</span>
</code></pre></div></div>
<p><a href="https://github.com/helvm/helma/blob/v0.6.6.0/hs/src/HelVM/HelMA/Common/Collections/Pop.hs">Pop</a> z metodą <code class="language-plaintext highlighter-rouge">pop1</code> i <code class="language-plaintext highlighter-rouge">pop2</code>,
żeby pobierać ze szczytu stosu:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">module</span> <span class="nn">HelVM.HelMA.Common.Collections.Pop</span> <span class="kr">where</span>
<span class="kr">import</span> <span class="nn">Data.Sequence</span> <span class="p">(</span><span class="kt">Seq</span><span class="p">(</span><span class="o">..</span><span class="p">))</span>
<span class="kr">class</span> <span class="kt">Pop1</span> <span class="n">e</span> <span class="n">c</span> <span class="o">|</span> <span class="n">c</span> <span class="o">-></span> <span class="n">e</span> <span class="kr">where</span>
<span class="n">pop1</span> <span class="o">::</span> <span class="n">c</span> <span class="o">-></span> <span class="p">(</span><span class="n">e</span> <span class="p">,</span> <span class="n">c</span><span class="p">)</span>
<span class="kr">instance</span> <span class="kt">Show</span> <span class="n">e</span> <span class="o">=></span> <span class="kt">Pop1</span> <span class="n">e</span> <span class="p">[</span><span class="n">e</span><span class="p">]</span> <span class="kr">where</span>
<span class="n">pop1</span> <span class="p">(</span><span class="n">e</span> <span class="o">:</span> <span class="n">c</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="n">e</span> <span class="p">,</span> <span class="n">c</span><span class="p">)</span>
<span class="n">pop1</span> <span class="n">c</span> <span class="o">=</span> <span class="n">error</span> <span class="o">$</span> <span class="s">"Empty "</span> <span class="o"><></span> <span class="n">show</span> <span class="n">c</span>
<span class="kr">instance</span> <span class="kt">Show</span> <span class="n">e</span> <span class="o">=></span> <span class="kt">Pop1</span> <span class="n">e</span> <span class="p">(</span><span class="kt">Seq</span> <span class="n">e</span><span class="p">)</span> <span class="kr">where</span>
<span class="n">pop1</span> <span class="p">(</span><span class="n">e</span> <span class="o">:<|</span> <span class="n">c</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="n">e</span> <span class="p">,</span> <span class="n">c</span><span class="p">)</span>
<span class="n">pop1</span> <span class="n">c</span> <span class="o">=</span> <span class="n">error</span> <span class="o">$</span> <span class="s">"Empty "</span> <span class="o"><></span> <span class="n">show</span> <span class="n">c</span>
<span class="kr">class</span> <span class="kt">Pop2</span> <span class="n">e</span> <span class="n">c</span> <span class="o">|</span> <span class="n">c</span> <span class="o">-></span> <span class="n">e</span> <span class="kr">where</span>
<span class="n">pop2</span> <span class="o">::</span> <span class="n">c</span> <span class="o">-></span> <span class="p">(</span><span class="n">e</span> <span class="p">,</span> <span class="n">e</span> <span class="p">,</span> <span class="n">c</span><span class="p">)</span>
<span class="kr">instance</span> <span class="kt">Show</span> <span class="n">e</span> <span class="o">=></span> <span class="kt">Pop2</span> <span class="n">e</span> <span class="p">[</span><span class="n">e</span><span class="p">]</span> <span class="kr">where</span>
<span class="n">pop2</span> <span class="p">(</span><span class="n">e</span> <span class="o">:</span> <span class="n">e'</span> <span class="o">:</span> <span class="n">c</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="n">e</span> <span class="p">,</span> <span class="n">e'</span><span class="p">,</span> <span class="n">c</span><span class="p">)</span>
<span class="n">pop2</span> <span class="n">c</span> <span class="o">=</span> <span class="n">error</span> <span class="o">$</span> <span class="s">"Empty "</span> <span class="o"><></span> <span class="n">show</span> <span class="n">c</span>
<span class="kr">instance</span> <span class="kt">Show</span> <span class="n">e</span> <span class="o">=></span> <span class="kt">Pop2</span> <span class="n">e</span> <span class="p">(</span><span class="kt">Seq</span> <span class="n">e</span><span class="p">)</span> <span class="kr">where</span>
<span class="n">pop2</span> <span class="p">(</span><span class="n">e</span> <span class="o">:<|</span> <span class="n">e'</span> <span class="o">:<|</span> <span class="n">c</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="n">e</span> <span class="p">,</span> <span class="n">e'</span><span class="p">,</span> <span class="n">c</span><span class="p">)</span>
<span class="n">pop2</span> <span class="n">c</span> <span class="o">=</span> <span class="n">error</span> <span class="o">$</span> <span class="s">"Empty "</span> <span class="o"><></span> <span class="n">show</span> <span class="n">c</span>
</code></pre></div></div>
<p>Plus jeszcze <a href="https://github.com/helvm/helma/blob/v0.6.6.0/hs/src/HelVM/HelMA/Common/Collections/Drop.hs">Drop</a> i <a href="https://github.com/helvm/helma/blob/v0.6.6.0/hs/src/HelVM/HelMA/Common/Collections/Pop.hs">Pop</a> z nudną implementacją.</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">module</span> <span class="nn">HelVM.HelMA.Common.Collections.Drop</span> <span class="kr">where</span>
<span class="kr">import</span> <span class="nn">Prelude</span> <span class="k">hiding</span> <span class="p">(</span><span class="nf">drop</span><span class="p">)</span>
<span class="kr">import</span> <span class="k">qualified</span> <span class="nn">Data.Sequence</span> <span class="k">as</span> <span class="n">Seq</span>
<span class="kr">import</span> <span class="k">qualified</span> <span class="nn">Prelude</span> <span class="k">as</span> <span class="n">List</span> <span class="p">(</span><span class="n">drop</span><span class="p">)</span>
<span class="kr">class</span> <span class="kt">Drop</span> <span class="n">e</span> <span class="n">c</span> <span class="o">|</span> <span class="n">c</span> <span class="o">-></span> <span class="n">e</span> <span class="kr">where</span>
<span class="n">drop</span> <span class="o">::</span> <span class="kt">Int</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="kr">instance</span> <span class="kt">Drop</span> <span class="n">e</span> <span class="p">[</span><span class="n">e</span><span class="p">]</span> <span class="kr">where</span>
<span class="n">drop</span> <span class="n">i</span> <span class="n">c</span> <span class="o">=</span> <span class="kt">List</span><span class="o">.</span><span class="n">drop</span> <span class="n">i</span> <span class="n">c</span>
<span class="kr">instance</span> <span class="kt">Drop</span> <span class="n">e</span> <span class="p">(</span><span class="kt">Seq</span> <span class="n">e</span><span class="p">)</span> <span class="kr">where</span>
<span class="n">drop</span> <span class="n">i</span> <span class="n">c</span> <span class="o">=</span> <span class="kt">Seq</span><span class="o">.</span><span class="n">drop</span> <span class="n">i</span> <span class="n">c</span>
</code></pre></div></div>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">module</span> <span class="nn">HelVM.HelMA.Common.Collections.SplitAt</span> <span class="kr">where</span>
<span class="kr">import</span> <span class="nn">Prelude</span> <span class="k">hiding</span> <span class="p">(</span><span class="nf">splitAt</span><span class="p">)</span>
<span class="kr">import</span> <span class="k">qualified</span> <span class="nn">Data.Sequence</span> <span class="k">as</span> <span class="n">Seq</span>
<span class="kr">import</span> <span class="k">qualified</span> <span class="nn">Prelude</span> <span class="k">as</span> <span class="n">List</span> <span class="p">(</span><span class="n">splitAt</span><span class="p">)</span>
<span class="kr">class</span> <span class="kt">SplitAt</span> <span class="n">e</span> <span class="n">c</span> <span class="o">|</span> <span class="n">c</span> <span class="o">-></span> <span class="n">e</span> <span class="kr">where</span>
<span class="n">splitAt</span> <span class="o">::</span> <span class="kt">Int</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="p">(</span><span class="n">c</span> <span class="p">,</span> <span class="n">c</span><span class="p">)</span>
<span class="kr">instance</span> <span class="kt">SplitAt</span> <span class="n">e</span> <span class="p">[</span><span class="n">e</span><span class="p">]</span> <span class="kr">where</span>
<span class="n">splitAt</span> <span class="n">i</span> <span class="n">c</span> <span class="o">=</span> <span class="kt">List</span><span class="o">.</span><span class="n">splitAt</span> <span class="n">i</span> <span class="n">c</span>
<span class="kr">instance</span> <span class="kt">SplitAt</span> <span class="n">e</span> <span class="p">(</span><span class="kt">Seq</span> <span class="n">e</span><span class="p">)</span> <span class="kr">where</span>
<span class="n">splitAt</span> <span class="n">i</span> <span class="n">c</span> <span class="o">=</span> <span class="kt">Seq</span><span class="o">.</span><span class="n">splitAt</span> <span class="n">i</span> <span class="n">c</span>
</code></pre></div></div>
<p>Teraz importujemy wszystkie potrzebne metody do modułu <code class="language-plaintext highlighter-rouge">Stack</code>:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">import</span> <span class="nn">HelVM.HelMA.Common.Collections.Drop</span>
<span class="kr">import</span> <span class="nn">HelVM.HelMA.Common.Collections.FromList</span>
<span class="kr">import</span> <span class="nn">HelVM.HelMA.Common.Collections.Lookup</span>
<span class="kr">import</span> <span class="nn">HelVM.HelMA.Common.Collections.Pop</span>
<span class="kr">import</span> <span class="nn">HelVM.HelMA.Common.Collections.SplitAt</span>
</code></pre></div></div>
<p>Musimy jeszcze ukryć przeszkadzające nam funkcje:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">import</span> <span class="nn">Prelude</span> <span class="k">hiding</span> <span class="p">(</span><span class="nf">divMod</span> <span class="p">,</span> <span class="nf">drop</span> <span class="p">,</span> <span class="nf">empty</span> <span class="p">,</span> <span class="nf">fromList</span> <span class="p">,</span> <span class="nf">splitAt</span> <span class="p">,</span> <span class="nf">swap</span><span class="p">)</span>
</code></pre></div></div>
<p>Niby osiągnęliśmy cel,
jednak pisanie wysokopoziomowych metod to jakaś tragedia:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- Stack instructions</span>
<span class="n">halibut</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Show</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">Semigroup</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">Integral</span> <span class="n">e</span> <span class="p">,</span> <span class="kt">FromList</span> <span class="n">e</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">Lookup</span> <span class="n">e</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">SplitAt</span> <span class="n">e</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">Pop1</span> <span class="n">e</span> <span class="n">c</span><span class="p">)</span> <span class="o">=></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">halibut</span> <span class="n">c</span>
<span class="o">|</span> <span class="n">i</span> <span class="o"><=</span> <span class="mi">0</span> <span class="o">=</span> <span class="n">copy</span> <span class="p">(</span><span class="n">negate</span> <span class="n">i</span><span class="p">)</span> <span class="n">c'</span>
<span class="o">|</span> <span class="n">otherwise</span> <span class="o">=</span> <span class="n">move</span> <span class="n">i</span> <span class="n">c'</span>
<span class="kr">where</span>
<span class="n">i</span> <span class="o">=</span> <span class="n">fromIntegral</span> <span class="n">e</span>
<span class="p">(</span><span class="n">e</span> <span class="p">,</span> <span class="n">c'</span><span class="p">)</span> <span class="o">=</span> <span class="n">pop1</span> <span class="n">c</span>
<span class="n">move</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Semigroup</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">SplitAt</span> <span class="n">e</span> <span class="n">c</span><span class="p">)</span> <span class="o">=></span> <span class="kt">Index</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">move</span> <span class="n">i</span> <span class="n">c</span> <span class="o">=</span> <span class="n">c1</span> <span class="o"><></span> <span class="n">c2</span> <span class="o"><></span> <span class="n">c3</span> <span class="kr">where</span>
<span class="p">(</span><span class="n">c1</span> <span class="p">,</span> <span class="n">c3</span><span class="p">)</span> <span class="o">=</span> <span class="n">splitAt</span> <span class="mi">1</span> <span class="n">c'</span>
<span class="p">(</span><span class="n">c2</span> <span class="p">,</span> <span class="n">c'</span><span class="p">)</span> <span class="o">=</span> <span class="n">splitAt</span> <span class="n">i</span> <span class="n">c</span>
<span class="n">swap</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Semigroup</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">FromList</span> <span class="n">e</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">Pop2</span> <span class="n">e</span> <span class="n">c</span><span class="p">)</span> <span class="o">=></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">swap</span> <span class="n">c</span> <span class="o">=</span> <span class="n">push2</span> <span class="n">e'</span> <span class="n">e</span> <span class="n">c'</span> <span class="kr">where</span> <span class="p">(</span><span class="n">e</span> <span class="p">,</span> <span class="n">e'</span> <span class="p">,</span> <span class="n">c'</span><span class="p">)</span> <span class="o">=</span> <span class="n">pop2</span> <span class="n">c</span>
<span class="n">discard</span> <span class="o">::</span> <span class="kt">Drop</span> <span class="n">e</span> <span class="n">c</span> <span class="o">=></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">discard</span> <span class="o">=</span> <span class="n">drop</span> <span class="mi">1</span>
<span class="n">slide</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Semigroup</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">Drop</span> <span class="n">e</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">FromList</span> <span class="n">e</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">Pop1</span> <span class="n">e</span> <span class="n">c</span><span class="p">)</span> <span class="o">=></span> <span class="kt">Index</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">slide</span> <span class="n">i</span> <span class="n">c</span> <span class="o">=</span> <span class="n">push1</span> <span class="n">e</span> <span class="p">(</span><span class="n">drop</span> <span class="n">i</span> <span class="n">c'</span><span class="p">)</span> <span class="kr">where</span> <span class="p">(</span><span class="n">e</span> <span class="p">,</span> <span class="n">c'</span><span class="p">)</span> <span class="o">=</span> <span class="n">pop1</span> <span class="n">c</span>
<span class="n">dup</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Show</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">Semigroup</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">FromList</span> <span class="n">e</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">Lookup</span> <span class="n">e</span> <span class="n">c</span><span class="p">)</span> <span class="o">=></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">dup</span> <span class="o">=</span> <span class="n">copy</span> <span class="mi">0</span>
<span class="n">copy</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Show</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">Semigroup</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">FromList</span> <span class="n">e</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">Lookup</span> <span class="n">e</span> <span class="n">c</span><span class="p">)</span> <span class="o">=></span> <span class="kt">Index</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">copy</span> <span class="n">i</span> <span class="n">c</span> <span class="o">=</span> <span class="n">push1</span> <span class="p">(</span><span class="n">c</span> <span class="p">`</span><span class="n">index</span><span class="p">`</span> <span class="n">i</span><span class="p">)</span> <span class="n">c</span>
<span class="c1">-- Push instructions</span>
<span class="n">pushChar1</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Num</span> <span class="n">e</span> <span class="p">,</span> <span class="kt">Semigroup</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">FromList</span> <span class="n">e</span> <span class="n">c</span><span class="p">)</span> <span class="o">=></span> <span class="kt">Char</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">pushChar1</span> <span class="o">=</span> <span class="n">genericPush1</span> <span class="o">.</span> <span class="n">ord</span>
<span class="n">genericPush1</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Integral</span> <span class="n">v</span> <span class="p">,</span> <span class="kt">Num</span> <span class="n">e</span> <span class="p">,</span> <span class="kt">Semigroup</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">FromList</span> <span class="n">e</span> <span class="n">c</span><span class="p">)</span> <span class="o">=></span> <span class="n">v</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">genericPush1</span> <span class="o">=</span> <span class="n">push1</span> <span class="o">.</span> <span class="n">fromIntegral</span>
<span class="n">push1</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Semigroup</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">FromList</span> <span class="n">e</span> <span class="n">c</span><span class="p">)</span> <span class="o">=></span> <span class="n">e</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">push1</span> <span class="n">e</span> <span class="o">=</span> <span class="n">pushList</span> <span class="p">[</span><span class="n">e</span><span class="p">]</span>
<span class="n">push2</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Semigroup</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">FromList</span> <span class="n">e</span> <span class="n">c</span><span class="p">)</span> <span class="o">=></span> <span class="n">e</span> <span class="o">-></span> <span class="n">e</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">push2</span> <span class="n">e</span> <span class="n">e'</span> <span class="o">=</span> <span class="n">pushList</span> <span class="p">[</span><span class="n">e</span> <span class="p">,</span> <span class="n">e'</span><span class="p">]</span>
<span class="n">pushList</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Semigroup</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">FromList</span> <span class="n">e</span> <span class="n">c</span><span class="p">)</span> <span class="o">=></span> <span class="p">[</span><span class="n">e</span><span class="p">]</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">pushList</span> <span class="n">es</span> <span class="n">c</span> <span class="o">=</span> <span class="n">fromList</span> <span class="n">es</span> <span class="o"><></span> <span class="n">c</span>
</code></pre></div></div>
<p>Naprawdę sygnaturą metody <code class="language-plaintext highlighter-rouge">halibut :: (Show c , Semigroup c , Integral e , FromList e c , Lookup e c , SplitAt e c , Pop1 e c) => c -> c</code> można straszyć dzieci.</p>
<p>Cała implementacja jest w pliku <a href="https://github.com/helvm/helma/blob/v0.6.6.0/hs/src/HelVM/HelMA/Common/Memories/StackUtil.hs">StackUtil</a>.
Czemu <code class="language-plaintext highlighter-rouge">Util</code>?
Bo nie mamy żadnego wspólnego interfejsu,
tylko zbiór przypadkowych metod :(</p>
<h2 id="wszystkie-metody-w-jednej-klasie-typu-z-jedną-implementacją">Wszystkie metody w jednej Klasie Typu z jedną implementacją</h2>
<p>Pomysł jest prosty.
Za pomocą małych <a href="/tags/type-class">Klas Typów</a> zdefiniujemy nową implementację <a href="/tags/type-class">Klasy Typu</a> <code class="language-plaintext highlighter-rouge">Stack</code>.</p>
<p>Najpierw importujemy wszystkie potrzebne funkcje do <code class="language-plaintext highlighter-rouge">I</code> (jak <code class="language-plaintext highlighter-rouge">Implementation</code>):</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">import</span> <span class="k">qualified</span> <span class="nn">HelVM.HelMA.Common.Collections.Drop</span> <span class="k">as</span> <span class="n">I</span>
<span class="kr">import</span> <span class="k">qualified</span> <span class="nn">HelVM.HelMA.Common.Collections.FromList</span> <span class="k">as</span> <span class="n">I</span>
<span class="kr">import</span> <span class="k">qualified</span> <span class="nn">HelVM.HelMA.Common.Collections.Lookup</span> <span class="k">as</span> <span class="n">I</span>
<span class="kr">import</span> <span class="k">qualified</span> <span class="nn">HelVM.HelMA.Common.Collections.Pop</span> <span class="k">as</span> <span class="n">I</span>
<span class="kr">import</span> <span class="k">qualified</span> <span class="nn">HelVM.HelMA.Common.Collections.SplitAt</span> <span class="k">as</span> <span class="n">I</span>
</code></pre></div></div>
<p>I oczywiście ukryjmy domyślne importowane funkcje:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">import</span> <span class="nn">Prelude</span> <span class="k">hiding</span> <span class="p">(</span><span class="nf">divMod</span> <span class="p">,</span> <span class="nf">drop</span> <span class="p">,</span> <span class="nf">empty</span> <span class="p">,</span> <span class="nf">fromList</span> <span class="p">,</span> <span class="nf">splitAt</span> <span class="p">,</span> <span class="nf">swap</span><span class="p">)</span>
</code></pre></div></div>
<p>Następnie tworzymy naszą <a href="/tags/type-class">Klasę Typów</a>:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">class</span> <span class="p">(</span><span class="kt">Semigroup</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">Show</span> <span class="n">c</span><span class="p">)</span> <span class="o">=></span> <span class="kt">Stack</span> <span class="n">e</span> <span class="n">c</span> <span class="o">|</span> <span class="n">c</span> <span class="o">-></span> <span class="n">e</span> <span class="kr">where</span>
<span class="n">fromList</span> <span class="o">::</span> <span class="p">[</span><span class="n">e</span><span class="p">]</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">empty</span> <span class="o">::</span> <span class="n">c</span>
<span class="n">index</span> <span class="o">::</span> <span class="n">c</span> <span class="o">-></span> <span class="kt">Index</span> <span class="o">-></span> <span class="n">e</span>
<span class="n">lookup</span> <span class="o">::</span> <span class="kt">Index</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="kt">Maybe</span> <span class="n">e</span>
<span class="n">splitAt</span> <span class="o">::</span> <span class="kt">Index</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="p">(</span><span class="n">c</span> <span class="p">,</span> <span class="n">c</span><span class="p">)</span>
<span class="n">drop</span> <span class="o">::</span> <span class="kt">Index</span> <span class="o">-></span> <span class="n">c</span> <span class="o">-></span> <span class="n">c</span>
<span class="n">pop1</span> <span class="o">::</span> <span class="n">c</span> <span class="o">-></span> <span class="p">(</span><span class="n">e</span> <span class="p">,</span> <span class="n">c</span><span class="p">)</span>
<span class="n">pop2</span> <span class="o">::</span> <span class="n">c</span> <span class="o">-></span> <span class="p">(</span><span class="n">e</span> <span class="p">,</span> <span class="n">e</span> <span class="p">,</span> <span class="n">c</span><span class="p">)</span>
</code></pre></div></div>
<p>I jedną implementację (instancję) dla niej:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">instance</span> <span class="p">(</span><span class="kt">Show</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">Semigroup</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">I</span><span class="o">.</span><span class="kt">Drop</span> <span class="n">e</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">I</span><span class="o">.</span><span class="kt">FromList</span> <span class="n">e</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">I</span><span class="o">.</span><span class="kt">Lookup</span> <span class="n">e</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">I</span><span class="o">.</span><span class="kt">SplitAt</span> <span class="n">e</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">I</span><span class="o">.</span><span class="kt">Pop1</span> <span class="n">e</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">I</span><span class="o">.</span><span class="kt">Pop2</span> <span class="n">e</span> <span class="n">c</span><span class="p">)</span> <span class="o">=></span> <span class="kt">Stack</span> <span class="n">e</span> <span class="n">c</span> <span class="kr">where</span>
<span class="n">fromList</span> <span class="o">=</span> <span class="kt">I</span><span class="o">.</span><span class="n">fromList</span>
<span class="n">empty</span> <span class="o">=</span> <span class="kt">I</span><span class="o">.</span><span class="n">empty</span>
<span class="n">index</span> <span class="o">=</span> <span class="kt">I</span><span class="o">.</span><span class="n">index</span>
<span class="n">lookup</span> <span class="o">=</span> <span class="kt">I</span><span class="o">.</span><span class="n">lookup</span>
<span class="n">splitAt</span> <span class="o">=</span> <span class="kt">I</span><span class="o">.</span><span class="n">splitAt</span>
<span class="n">drop</span> <span class="o">=</span> <span class="kt">I</span><span class="o">.</span><span class="n">drop</span>
<span class="n">pop1</span> <span class="o">=</span> <span class="kt">I</span><span class="o">.</span><span class="n">pop1</span>
<span class="n">pop2</span> <span class="o">=</span> <span class="kt">I</span><span class="o">.</span><span class="n">pop2</span>
</code></pre></div></div>
<p>Niestety musimy dodać rozszerzenie kompilatora <code class="language-plaintext highlighter-rouge">{-#LANGUAGE UndecidableInstances#-}</code>,
co nie jest fajne.</p>
<p>Cała implementacja jest w pliku <a href="https://github.com/helvm/helma/blob/v0.6.6.0/hs/src/HelVM/HelMA/Common/Memories/StackImpl.hs">StackImpl</a>.
Czemu <code class="language-plaintext highlighter-rouge">*Impl</code>?
Bo przypomina to <a href="/langs/java">Javową</a> patologię z Serwisami z jedną implementacją <code class="language-plaintext highlighter-rouge">*ServiceImpl</code>.</p>
<h2 id="sumowanie-ograniczeń">Sumowanie ograniczeń</h2>
<p>Gdy już byłem zdołowany,
że zostawię projekt z bezsensowną <a href="/tags/type-class">Klasą Typu</a>,
z jedną implementacją to przypadkiem przeczytałem,
że w <a href="/langs/haskell">Haskellu</a> ograniczenia rodzai mogą być <em>obywatelami pierwszej kategori</em>.
Wystarczy łączyć rozszerzenie <code class="language-plaintext highlighter-rouge">{-#LANGUAGE ConstraintKinds#-}</code>.</p>
<p>Po włączeniu rozszerzenia importujemy wszystkie potrzebne nam metody:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">import</span> <span class="nn">HelVM.HelMA.Common.Collections.Drop</span>
<span class="kr">import</span> <span class="nn">HelVM.HelMA.Common.Collections.FromList</span>
<span class="kr">import</span> <span class="nn">HelVM.HelMA.Common.Collections.Lookup</span>
<span class="kr">import</span> <span class="nn">HelVM.HelMA.Common.Collections.Pop</span>
<span class="kr">import</span> <span class="nn">HelVM.HelMA.Common.Collections.SplitAt</span>
</code></pre></div></div>
<p>I ponownie ukrywamy przeszkadzające nam funkcje:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">import</span> <span class="nn">Prelude</span> <span class="k">hiding</span> <span class="p">(</span><span class="nf">divMod</span> <span class="p">,</span> <span class="nf">drop</span> <span class="p">,</span> <span class="nf">empty</span> <span class="p">,</span> <span class="nf">fromList</span> <span class="p">,</span> <span class="nf">splitAt</span> <span class="p">,</span> <span class="nf">swap</span><span class="p">)</span>
</code></pre></div></div>
<p>A następnie piszemy jedną magiczną linię:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">type</span> <span class="kt">Stack</span> <span class="n">e</span> <span class="n">c</span> <span class="o">=</span> <span class="p">(</span><span class="kt">Show</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">Semigroup</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">Drop</span> <span class="n">e</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">FromList</span> <span class="n">e</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">Lookup</span> <span class="n">e</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">SplitAt</span> <span class="n">e</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">Pop1</span> <span class="n">e</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">Pop2</span> <span class="n">e</span> <span class="n">c</span><span class="p">)</span>
</code></pre></div></div>
<p>Właśnie zsumowaliśmy wszystkie ograniczenia do jednego typu <code class="language-plaintext highlighter-rouge">Stack</code>.</p>
<p>I teraz można żyć.
I teraz da się pracować.</p>
<p>Cały kod jest w pliku <a href="https://github.com/helvm/helma/blob/v0.6.6.0/hs/src/HelVM/HelMA/Common/Memories/StackConst.hs">StackConst</a> (<code class="language-plaintext highlighter-rouge">Const</code> jak <code class="language-plaintext highlighter-rouge">Constraint</code>).</p>
<h2 id="podsumowanie">Podsumowanie</h2>
<p>Stworzenie interfejsu kolekcji w <strong><a href="/langs/haskell">Haskellu</a></strong> nie jest jednak trudne.
Wystarczy wiedzieć,
czego się szuka i znaleźć to :)
<a href="/tags/functional-dependency">zależności Funkcyjne</a> są niesamowitym narzędziem pozwalającym pisać bardzo elastyczny i polimorficzny kod.</p>
<p>Kod jednak dalej nie jest idealny.
Co można jeszcze poprawić?</p>
<ul>
<li>Przepisać <a href="/libs/relude">Relude</a> na <a href="/libs/rio">RIO</a> - uniknie się w ten sposób importów ukrywających.</li>
<li>Przepisać <a href="/tags/functional-dependency">Zależności Funkcyjne</a> na <a href="/tags/type-family">Rodziny Typów</a> - rodziny typów są bardziej restrykcyjne i popularniejsze.</li>
<li>Użyć ograniczenia istniejące już w <a href="/langs/haskell">Haskellu</a> jak <code class="language-plaintext highlighter-rouge">IsList</code>.</li>
<li>Użyć biblioteki polimorficznych importów jak <a href="/libs/classy-prelude">ClassyPrelude</a>.</li>
</ul>
<p>Cały kod interpretera <a href="/projects/helma">HelMA</a> jest dostępny na <a href="https://github.com/helvm/helma/tree/v0.6.6.0">githabie</a>.</p>TheKamilAdamSpytano mnie raz, co jest trudnego w Haskellu, co jednocześnie jest łatwe w OOP. Np. stworzenie interfejsu kolekcji i danie możliwości implementowania go klientom-użytkownikom. W tym celu potrzebujemy Klasę Typu od dwóch parametrów. Ale żeby mieć dobry interfejs, to nie wystarczy. O czym się przekonaliśmy w artykule Abstrakcja i dopasowanie do wzorców.Złote testy w Haskellu2021-05-05T00:00:00+02:002021-05-05T00:00:00+02:00http://writeonly.pl/haskell-eta/golden-tests<p>Zainspirowany wpisem o złotych testach na <a href="https://4programmers.net/Mikroblogi/View/90241">4programmers</a> postanowiłem dodać je do swojego projektu w <strong><a href="/langs/haskell">Haskellu</a></strong>.</p>
<p>Dlaczego w ogóle złote testy (ang. <strong>golden tests</strong>)?
Złote testy są dobre dla legacy projektów,
gdzie nie wiemy,
co zwrócą testowane funkcje.
Ja,
pisząc od początku nowy kod,
powinienem dobrze wiedzieć co i kiedy może zostać zwrócone.
Jednak tak nie jest.
O ile tak jest dla prostych przypadków,
o tyle dla długich fragmentów kodu w ezoterycznych asemblerach jak <a href="/eso/eas">EAS</a> czy <a href="/eso/wsa">WSA</a> nie mam pojęcia co zostanie wygenerowane.
Tutaj idealnie sprawdzają się złote testy.</p>
<p>Niestety <a href="/libs/hunit">HUnit</a> nie wspiera złotych testów,
ale już wcześniej byłem zdecydowany na migrację do frameworka testowego <a href="/libs/hspec">HSpec</a>.
Jednak nie sądziłem,
że zmiana będzie od razu tak radykalna.
Oprócz <a href="/libs/hspec">HSpec</a>, także framework <a href="/libs/taste">Taste</a> pozwala na używanie złotych testów.
Jednak zdecydowałem się na HSpec ponieważ:</p>
<ul>
<li>HSpec posiada automatyczne generowanie agregatora testów.</li>
<li>HSpec ma zagnieżdżoną składnię <code class="language-plaintext highlighter-rouge">describe</code>/<code class="language-plaintext highlighter-rouge">context</code>/<code class="language-plaintext highlighter-rouge">it</code>, którą można ładnie wypaczać.</li>
</ul>
<p>Jeśli jednak ktoś wolałby złote testy we frameworku <a href="/libs/taste">Taste</a> znalazłem dwa teksty poświęcone temu zagadnieniu:</p>
<ul>
<li><a href="https://ro-che.info/articles/2017-12-04-golden-tests">Introduction to golden testing</a></li>
<li><a href="https://kseo.github.io/posts/2016-12-15-golden-tests-are-tasty.html">Golden tests are tasty</a></li>
</ul>
<p>A jako przykład użycia polecam projekt <a href="https://justinethier.github.io/husk-scheme/">Husk Schema</a>.</p>
<h2 id="złote-testy-w-projekcie-helpa">Złote testy w projekcie HelPA</h2>
<p>Jako prosty przykład do testów wybrałem asembler <a href="/eso/eas">EAS</a> z projektu <a href="/projects/helpa">HelPA</a>.</p>
<p>Asembler <a href="/eso/eas">EAS</a> składa się z czterech głównych modułów:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">AsmParser</code> - frontend asemblera, który parsuje plik z językiem asemblerowym.</li>
<li><code class="language-plaintext highlighter-rouge">Reducer</code> - frontend backendu asemblera, który redukuje skomplikowane instrukcje do prostych instrukcji.</li>
<li><code class="language-plaintext highlighter-rouge">CodeGenerator</code> - właściwy backend asemblera, który generuje kod w języku ezoterycznym.</li>
<li><code class="language-plaintext highlighter-rouge">Assembler</code> - moduł, który składa to wszystko razem.</li>
</ul>
<p>A więc po kolej.</p>
<h3 id="reducerspec-czyli-parametryzowane-testy">ReducerSpec, czyli parametryzowane testy</h3>
<p>Moduł <code class="language-plaintext highlighter-rouge">ReducerSpec</code> testuje funkcję <code class="language-plaintext highlighter-rouge">Reducer.reduce</code>.
Funkcją <code class="language-plaintext highlighter-rouge">Reducer.reduce :: InstructionList -> InstructionList</code> zamienia listę instrukcji w zredukowaną listę instrukcji.
Redukcja polega na zamianie <code class="language-plaintext highlighter-rouge">wysokopoziomowych</code> instrukcji na ich <code class="language-plaintext highlighter-rouge">niskopoziomowe</code> odpowiedniki możliwe do zapisania w języku <a href="/eso/eta">ETA</a>.</p>
<p>Test wygląda następująco:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">module</span> <span class="nn">HelVM.HelPA.Assemblers.EAS.ReducerSpec</span> <span class="kr">where</span>
<span class="kr">import</span> <span class="nn">HelVM.HelPA.Assemblers.EAS.Reducer</span>
<span class="kr">import</span> <span class="nn">HelVM.HelPA.Assemblers.EAS.TestData</span>
<span class="kr">import</span> <span class="nn">Test.Hspec</span>
<span class="n">spec</span> <span class="o">::</span> <span class="kt">Spec</span>
<span class="n">spec</span> <span class="o">=</span> <span class="kr">do</span>
<span class="n">describe</span> <span class="s">"reduce"</span> <span class="o">$</span> <span class="kr">do</span>
<span class="n">forM_</span> <span class="p">[</span> <span class="p">(</span><span class="s">"true"</span> <span class="p">,</span> <span class="n">trueIL</span> <span class="p">,</span> <span class="n">trueIL</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"hello"</span> <span class="p">,</span> <span class="n">helloIL</span> <span class="p">,</span> <span class="n">helloIL</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"pip"</span> <span class="p">,</span> <span class="n">pipIL</span> <span class="p">,</span> <span class="n">pipILReduced</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"pip2"</span> <span class="p">,</span> <span class="n">pip2IL</span> <span class="p">,</span> <span class="n">pip2ILReduced</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"reverse"</span> <span class="p">,</span> <span class="n">reverseIL</span> <span class="p">,</span> <span class="n">reverseILReduced</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"function"</span> <span class="p">,</span> <span class="n">functionIL</span> <span class="p">,</span> <span class="n">functionIL</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"add"</span> <span class="p">,</span> <span class="n">addILLinked</span> <span class="p">,</span> <span class="n">addILReduced</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"writestr"</span> <span class="p">,</span> <span class="n">writeStrIL</span> <span class="p">,</span> <span class="n">writeStrILReduced</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"hello2"</span> <span class="p">,</span> <span class="n">hello2ILLinked</span> <span class="p">,</span> <span class="n">hello2ILReduced</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"hello4"</span> <span class="p">,</span> <span class="n">hello4ILLinked</span> <span class="p">,</span> <span class="n">hello2ILReduced</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"writenum"</span> <span class="p">,</span> <span class="n">writeNumILLinked</span> <span class="p">,</span> <span class="n">writeNumILReduced</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"multiply"</span> <span class="p">,</span> <span class="n">multiplyIL</span> <span class="p">,</span> <span class="n">multiplyILReduced</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"readnum"</span> <span class="p">,</span> <span class="n">readNumILLinked</span> <span class="p">,</span> <span class="n">readNumILReduced</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"fact"</span> <span class="p">,</span> <span class="n">factILLinked</span> <span class="p">,</span> <span class="n">factILReduced</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"bottles"</span> <span class="p">,</span> <span class="n">bottlesILLinked</span> <span class="p">,</span> <span class="n">bottlesILReduced</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"euclid"</span> <span class="p">,</span> <span class="n">euclidIL</span> <span class="p">,</span> <span class="n">euclidILReduced</span><span class="p">)</span>
<span class="p">]</span> <span class="o">$</span> <span class="nf">\</span><span class="p">(</span><span class="n">fileName</span> <span class="p">,</span> <span class="n">ilLinked</span><span class="p">,</span> <span class="n">ilReduced</span><span class="p">)</span> <span class="o">-></span> <span class="kr">do</span>
<span class="n">it</span> <span class="n">fileName</span> <span class="o">$</span> <span class="kr">do</span> <span class="n">reduce</span> <span class="n">ilLinked</span> <span class="p">`</span><span class="n">shouldBe</span><span class="p">`</span> <span class="n">ilReduced</span>
</code></pre></div></div>
<p>Funkcja <code class="language-plaintext highlighter-rouge">forM_</code> zamienia nam zwykłe testy na testy parametryczne.</p>
<p>Następnie mamy listę krotek.
Pierwszy element krotki zawiera nazwę testu,
drugi — listę instrukcji do zredukowania,
trzeci — zredukowaną listę instrukcji.</p>
<p>Funkcja <code class="language-plaintext highlighter-rouge">shouldBe</code> to asercja.
Dzięki grawisom funkcja może być użyta jak operator.
Bez grawisów trzeba by zapisać:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">it</span> <span class="n">fileName</span> <span class="o">$</span> <span class="kr">do</span> <span class="n">shouldBe</span> <span class="p">(</span><span class="n">reduce</span> <span class="n">ilLinked</span><span class="p">)</span> <span class="n">ilReduced</span>
</code></pre></div></div>
<h3 id="asmparser-czyli-czytanie-danych-testowych-z-pliku">AsmParser, czyli czytanie danych testowych z pliku</h3>
<p>Moduł <code class="language-plaintext highlighter-rouge">AsmParserSpec</code> testuje funkcję <code class="language-plaintext highlighter-rouge">AsmParser.parseAssembler</code>.
Funkcja <code class="language-plaintext highlighter-rouge">parseAssembler :: Text -> Parsed InstructionList</code> parsuje plik w języku <a href="/eso/eas">EAS</a> i zwraca listę instrukcji.
Ponieważ parsowanie może się nie udać to lista instrukcji opakowana jest w typ <code class="language-plaintext highlighter-rouge">Parsed</code>, który ma postać:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">type</span> <span class="kt">Parsed</span> <span class="n">a</span> <span class="o">=</span> <span class="kt">Either</span> <span class="kt">String</span> <span class="n">a</span>
</code></pre></div></div>
<p>Ponieważ jednak nie będziemy pracować ze zmiennej typu <code class="language-plaintext highlighter-rouge">Text</code>,
a zmienną typu <code class="language-plaintext highlighter-rouge">IO Text</code> to naszym ostatecznym typem do porównania będzie <code class="language-plaintext highlighter-rouge">IO (Parsed InstructionList)</code>,
czyli dokładniej <code class="language-plaintext highlighter-rouge">IO (Either String InstructionList)</code>.
Który dla wygody nazwiemy <code class="language-plaintext highlighter-rouge">ParsedIO</code>:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">type</span> <span class="kt">ParsedIO</span> <span class="n">a</span> <span class="o">=</span> <span class="kt">IO</span> <span class="p">(</span><span class="kt">Parsed</span> <span class="n">a</span><span class="p">)</span>
</code></pre></div></div>
<p>Biblioteka <a href="/libs/hspec">HSpec</a> nie posiada oczywiście asercji dla typu <code class="language-plaintext highlighter-rouge">IO (Either String a)</code>,
ale posiada asercję <code class="language-plaintext highlighter-rouge">shouldReturn</code> dla typu <code class="language-plaintext highlighter-rouge">IO a</code>:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">shouldReturn</span> <span class="o">::</span> <span class="p">(</span><span class="kt">HasCallStack</span><span class="p">,</span> <span class="kt">Show</span> <span class="n">a</span><span class="p">,</span> <span class="kt">Eq</span> <span class="n">a</span><span class="p">)</span> <span class="o">=></span> <span class="kt">IO</span> <span class="n">a</span> <span class="o">-></span> <span class="n">a</span> <span class="o">-></span> <span class="kt">Expectation</span>
</code></pre></div></div>
<p>Jedyne co musimy zrobić to tylko zamienić <code class="language-plaintext highlighter-rouge">IO (Either String a)</code> na <code class="language-plaintext highlighter-rouge">IO a</code>.</p>
<p>Najpierw zamieniamy <code class="language-plaintext highlighter-rouge">Either String a</code> na <code class="language-plaintext highlighter-rouge">IO a</code></p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">eitherToIO</span> <span class="o">::</span> <span class="kt">Parsed</span> <span class="n">a</span> <span class="o">-></span> <span class="kt">IO</span> <span class="n">a</span>
<span class="n">eitherToIO</span> <span class="p">(</span><span class="kt">Right</span> <span class="n">value</span><span class="p">)</span> <span class="o">=</span> <span class="n">pure</span> <span class="n">value</span>
<span class="n">eitherToIO</span> <span class="p">(</span><span class="kt">Left</span> <span class="n">message</span><span class="p">)</span> <span class="o">=</span> <span class="n">fail</span> <span class="n">message</span>
</code></pre></div></div>
<p>A następnie możemy dodać do tego składanie monad (flatMapowanie) z <code class="language-plaintext highlighter-rouge">IO (IO a)</code> na <code class="language-plaintext highlighter-rouge">IO a</code></p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">joinEitherToIO</span> <span class="o">::</span> <span class="kt">ParsedIO</span> <span class="n">a</span> <span class="o">-></span> <span class="kt">IO</span> <span class="n">a</span>
<span class="n">joinEitherToIO</span> <span class="n">io</span> <span class="o">=</span> <span class="n">eitherToIO</span> <span class="o">=<<</span> <span class="n">io</span>
</code></pre></div></div>
<p>Teraz możemy wszystko opakować w nową asercję:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">infix</span> <span class="mi">1</span> <span class="p">`</span><span class="n">shouldParseReturn</span><span class="p">`</span>
<span class="n">shouldParseReturn</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Show</span> <span class="n">a</span><span class="p">,</span> <span class="kt">Eq</span> <span class="n">a</span><span class="p">)</span> <span class="o">=></span> <span class="kt">ParsedIO</span> <span class="n">a</span> <span class="o">-></span> <span class="n">a</span> <span class="o">-></span> <span class="kt">Expectation</span>
<span class="n">shouldParseReturn</span> <span class="n">action</span> <span class="o">=</span> <span class="n">shouldReturn</span> <span class="p">(</span><span class="n">joinEitherToIO</span> <span class="n">action</span><span class="p">)</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">infix 1</code> pozwala ustalić priorytet operatora.</p>
<p>Ostatecznie test wygląda następująco:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">module</span> <span class="nn">HelVM.HelPA.Assemblers.EAS.AsmParserSpec</span> <span class="p">(</span><span class="nf">spec</span><span class="p">)</span> <span class="kr">where</span>
<span class="kr">import</span> <span class="nn">HelVM.HelPA.Assemblers.EAS.AsmParser</span>
<span class="kr">import</span> <span class="nn">HelVM.HelPA.Assemblers.EAS.Instruction</span>
<span class="kr">import</span> <span class="nn">HelVM.HelPA.Assemblers.EAS.FileUtil</span>
<span class="kr">import</span> <span class="nn">HelVM.HelPA.Assemblers.EAS.TestData</span>
<span class="kr">import</span> <span class="nn">HelVM.HelPA.Assemblers.Expectations</span>
<span class="kr">import</span> <span class="nn">HelVM.HelPA.Common.Value</span>
<span class="kr">import</span> <span class="nn">Test.Hspec</span>
<span class="kr">import</span> <span class="nn">Test.Hspec.Attoparsec</span>
<span class="n">spec</span> <span class="o">::</span> <span class="kt">Spec</span>
<span class="n">spec</span> <span class="o">=</span> <span class="kr">do</span>
<span class="n">describe</span> <span class="s">"parseFromFile"</span> <span class="o">$</span> <span class="kr">do</span>
<span class="n">forM_</span> <span class="p">[</span> <span class="p">(</span><span class="s">"true"</span> <span class="p">,</span> <span class="n">trueIL</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"hello"</span> <span class="p">,</span> <span class="n">helloIL</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"pip"</span> <span class="p">,</span> <span class="n">pipIL</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"pip2"</span> <span class="p">,</span> <span class="n">pip2IL</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"reverse"</span> <span class="p">,</span> <span class="n">reverseIL</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"function"</span> <span class="p">,</span> <span class="n">functionIL</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"writestr"</span> <span class="p">,</span> <span class="n">writeStrIL</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"hello2"</span> <span class="p">,</span> <span class="n">hello2IL</span> <span class="o"><></span> <span class="p">[</span><span class="kt">D</span> <span class="s">"writestr.eas"</span><span class="p">])</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"hello3"</span> <span class="p">,</span> <span class="n">hello2IL</span> <span class="o"><></span> <span class="p">[</span><span class="kt">D</span> <span class="s">"writestr.eas"</span><span class="p">])</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"hello4"</span> <span class="p">,</span> <span class="n">hello4IL</span> <span class="o"><></span> <span class="p">[</span><span class="kt">D</span> <span class="s">"writestr.eas"</span><span class="p">])</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"writenum"</span> <span class="p">,</span> <span class="n">writeNumIL</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"multiply"</span> <span class="p">,</span> <span class="n">multiplyIL</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"readnum"</span> <span class="p">,</span> <span class="n">readNumIL</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"fact"</span> <span class="p">,</span> <span class="n">factIL</span> <span class="o"><></span> <span class="p">[</span><span class="kt">D</span> <span class="s">"readnum.eas"</span><span class="p">,</span><span class="kt">D</span> <span class="s">"writenum.eas"</span><span class="p">,</span><span class="kt">D</span> <span class="s">"multiply.eas"</span><span class="p">,</span><span class="kt">D</span> <span class="s">"writestr.eas"</span><span class="p">])</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"bottles"</span> <span class="p">,</span> <span class="n">bottlesIL</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"euclid"</span> <span class="p">,</span> <span class="n">euclidIL</span><span class="p">)</span>
<span class="p">]</span> <span class="o">$</span> <span class="nf">\</span><span class="p">(</span><span class="n">fileName</span> <span class="p">,</span> <span class="n">il</span><span class="p">)</span> <span class="o">-></span> <span class="kr">do</span>
<span class="kr">let</span> <span class="n">parseFromFile</span> <span class="o">=</span> <span class="n">parseAssembler</span> <span class="o"><$></span> <span class="n">readFileText</span> <span class="p">(</span><span class="n">buildAbsolutePathToEasFile</span> <span class="n">fileName</span><span class="p">)</span>
<span class="n">it</span> <span class="n">fileName</span> <span class="o">$</span> <span class="kr">do</span> <span class="n">parseFromFile</span> <span class="p">`</span><span class="n">shouldParseReturn</span><span class="p">`</span> <span class="n">il</span>
</code></pre></div></div>
<h3 id="codegeneratorspec-czyli-złote-testy">CodeGeneratorSpec, czyli złote testy</h3>
<p>Moduł <code class="language-plaintext highlighter-rouge">CodeGeneratorSpec</code> testuje funkcję <code class="language-plaintext highlighter-rouge">CodeGenerator.generateCode</code>.
Funkcja <code class="language-plaintext highlighter-rouge">generateCode :: InstructionList -> String</code> generuje kod w języku <a href="/eso/eta">ETA</a> na podstawie listy instrukcji.
Wyniku wygenerowanego z <code class="language-plaintext highlighter-rouge">generateCode</code> nie będziemy porównywać z wartościami zapisanymi w testach tylko ze złotym plikiem.</p>
<p>Tym razem musimy samodzielnie napisać asercję:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">infix</span> <span class="mi">1</span> <span class="p">`</span><span class="n">goldenShouldBe</span><span class="p">`</span>
<span class="n">goldenShouldBe</span> <span class="o">::</span> <span class="kt">String</span> <span class="o">-></span> <span class="kt">String</span> <span class="o">-></span> <span class="kt">Golden</span> <span class="kt">String</span>
<span class="n">goldenShouldBe</span> <span class="n">actualOutput</span> <span class="n">fileName</span> <span class="o">=</span>
<span class="kt">Golden</span> <span class="p">{</span>
<span class="n">output</span> <span class="o">=</span> <span class="n">actualOutput</span><span class="p">,</span>
<span class="n">encodePretty</span> <span class="o">=</span> <span class="n">show</span><span class="p">,</span>
<span class="n">writeToFile</span> <span class="o">=</span> <span class="n">writeFile</span><span class="p">,</span>
<span class="n">readFromFile</span> <span class="o">=</span> <span class="n">readFile</span><span class="p">,</span>
<span class="n">goldenFile</span> <span class="o">=</span> <span class="s">".output"</span> <span class="o"></></span> <span class="s">"golden"</span> <span class="o"></></span> <span class="n">fileName</span><span class="p">,</span>
<span class="n">actualFile</span> <span class="o">=</span> <span class="kt">Just</span> <span class="p">(</span><span class="s">".output"</span> <span class="o"></></span> <span class="s">"actual"</span> <span class="o"></></span> <span class="n">fileName</span><span class="p">),</span>
<span class="n">failFirstTime</span> <span class="o">=</span> <span class="kt">False</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Kod testu wygląda następująco:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">module</span> <span class="nn">HelVM.HelPA.Assemblers.EAS.CodeGeneratorSpec</span> <span class="p">(</span><span class="nf">spec</span><span class="p">)</span> <span class="kr">where</span>
<span class="kr">import</span> <span class="nn">HelVM.HelPA.Assemblers.EAS.CodeGenerator</span>
<span class="kr">import</span> <span class="nn">HelVM.HelPA.Assemblers.EAS.TestData</span>
<span class="kr">import</span> <span class="nn">HelVM.HelPA.Assemblers.EAS.FileUtil</span>
<span class="kr">import</span> <span class="nn">HelVM.HelPA.Assemblers.Expectations</span>
<span class="kr">import</span> <span class="nn">Test.Hspec</span>
<span class="n">spec</span> <span class="o">::</span> <span class="kt">Spec</span>
<span class="n">spec</span> <span class="o">=</span> <span class="kr">do</span>
<span class="n">describe</span> <span class="s">"generateCode"</span> <span class="o">$</span> <span class="kr">do</span>
<span class="n">forM_</span> <span class="p">[</span> <span class="p">(</span><span class="s">"true"</span> <span class="p">,</span> <span class="n">trueIL</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"pip"</span> <span class="p">,</span> <span class="n">pipILReduced</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"pip2"</span> <span class="p">,</span> <span class="n">pip2ILReduced</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"reverse"</span> <span class="p">,</span> <span class="n">reverseILReduced</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"function"</span> <span class="p">,</span> <span class="n">functionIL</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"add"</span> <span class="p">,</span> <span class="n">addILReduced</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"writestr"</span> <span class="p">,</span> <span class="n">writeStrILReduced</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"hello2"</span> <span class="p">,</span> <span class="n">hello2ILReduced</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"hello4"</span> <span class="p">,</span> <span class="n">hello2ILReduced</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"writenum"</span> <span class="p">,</span> <span class="n">writeNumILReduced</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"multiply"</span> <span class="p">,</span> <span class="n">multiplyILReduced</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"readnum"</span> <span class="p">,</span> <span class="n">readNumILReduced</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"fact"</span> <span class="p">,</span> <span class="n">factILReduced</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"bottles"</span> <span class="p">,</span> <span class="n">bottlesILReduced</span><span class="p">)</span>
<span class="p">,</span> <span class="p">(</span><span class="s">"euclid"</span> <span class="p">,</span> <span class="n">euclidILReduced</span><span class="p">)</span>
<span class="p">]</span> <span class="o">$</span> <span class="nf">\</span><span class="p">(</span><span class="n">fileName</span> <span class="p">,</span> <span class="n">ilReduced</span><span class="p">)</span> <span class="o">-></span> <span class="kr">do</span>
<span class="n">it</span> <span class="n">fileName</span> <span class="o">$</span> <span class="kr">do</span> <span class="n">generateCode</span> <span class="n">ilReduced</span> <span class="p">`</span><span class="n">goldenShouldBe</span><span class="p">`</span> <span class="n">buildAbsolutePathToEtaFile</span> <span class="n">fileName</span>
</code></pre></div></div>
<p>Tablica zawiera krotki (tuple).
Pierwszy element krotki to nazwa pliku,
drugi element krotki to lista instrukcji ze zredukowanymi rozkazami.
Po lewej stronie asercji mamy generowanie kodu.
Po prawej stronie asercji mamy wczytanie pliku z kodem źródłowym w <strong><a href="/eso/eta">ETA</a></strong>.</p>
<h3 id="assemblerspec-czyli-złote-testy-z-czytaniem-danych-testowych-z-pliku">AssemblerSpec, czyli złote testy z czytaniem danych testowych z pliku</h3>
<p>Pora na przetestowanie wszystkiego razem.
Moduł <code class="language-plaintext highlighter-rouge">AssemblerSpec</code> testuje funkcję <code class="language-plaintext highlighter-rouge">Assembler.assembleFile</code>.
Funkcja <code class="language-plaintext highlighter-rouge">assembleFile :: SourcePath -> ParsedIO String</code> generuje kod w języku <a href="/eso/eta">ETA</a> na podstawie zmiennej <code class="language-plaintext highlighter-rouge">SourcePath</code>.
Typ <code class="language-plaintext highlighter-rouge">SourcePath</code> ma postać:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">data</span> <span class="kt">SourcePath</span> <span class="o">=</span> <span class="kt">SourcePath</span>
<span class="p">{</span> <span class="n">dirPath</span> <span class="o">::</span> <span class="kt">String</span> <span class="c1">-- ścieżka do folderu z bibliotekami z kodem w EAS</span>
<span class="p">,</span> <span class="n">filePath</span> <span class="o">::</span> <span class="kt">String</span> <span class="c1">-- ścieżka do pliku z kodem w EAS</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Ponieważ funkcja <code class="language-plaintext highlighter-rouge">Assembler.assembleFile</code> zwraca monadę <code class="language-plaintext highlighter-rouge">IO</code> potrzebujemy asercji działającej dla typu <code class="language-plaintext highlighter-rouge">IO (Golden String)</code>.</p>
<p>W celu prostszego zapisu tworzymy alias typu:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">type</span> <span class="kt">GoldenIO</span> <span class="n">a</span> <span class="o">=</span> <span class="kt">IO</span> <span class="p">(</span><span class="kt">Golden</span> <span class="n">a</span><span class="p">)</span>
</code></pre></div></div>
<p>A następnie asercję:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">goldenShouldReturn'</span> <span class="o">::</span> <span class="kt">IO</span> <span class="kt">String</span> <span class="o">-></span> <span class="kt">String</span> <span class="o">-></span> <span class="kt">GoldenIO</span> <span class="kt">String</span>
<span class="n">goldenShouldReturn'</span> <span class="n">actualOutputIO</span> <span class="n">fileName</span> <span class="o">=</span> <span class="n">flip</span> <span class="n">goldenShouldBe</span> <span class="n">fileName</span> <span class="o"><$></span> <span class="n">actualOutputIO</span>
</code></pre></div></div>
<p>Jednak ta asercja nie zadziała z powodu niezgodności typów:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">hs</span><span class="o">/</span><span class="n">test</span><span class="o">/</span><span class="kt">HelVM</span><span class="o">/</span><span class="kt">HelPA</span><span class="o">/</span><span class="kt">Assemblers</span><span class="o">/</span><span class="kt">EAS</span><span class="o">/</span><span class="kt">AssemblerSpec</span><span class="o">.</span><span class="n">hs</span><span class="o">:</span><span class="mi">39</span><span class="o">:</span><span class="mi">7</span><span class="o">:</span> <span class="n">error</span><span class="o">:</span>
<span class="err">•</span> <span class="kt">Couldn't</span> <span class="n">match</span> <span class="kr">type</span> <span class="err">‘</span><span class="kt">Arg</span>
<span class="p">(</span><span class="kt">HelVM</span><span class="o">.</span><span class="kt">HelPA</span><span class="o">.</span><span class="kt">Assemblers</span><span class="o">.</span><span class="kt">Expectations</span><span class="o">.</span><span class="kt">GoldenIO</span> <span class="kt">String</span><span class="p">)</span><span class="err">’</span>
<span class="n">with</span> <span class="err">‘</span><span class="nb">()</span><span class="err">’</span>
<span class="kt">Expected</span> <span class="kr">type</span><span class="o">:</span> <span class="n">hspec</span><span class="o">-</span><span class="n">core</span><span class="o">-</span><span class="mf">2.8</span><span class="o">.</span><span class="mi">2</span><span class="o">:</span><span class="kt">Test</span><span class="o">.</span><span class="kt">Hspec</span><span class="o">.</span><span class="kt">Core</span><span class="o">.</span><span class="kt">Spec</span><span class="o">.</span><span class="kt">Monad</span><span class="o">.</span><span class="kt">SpecM</span>
<span class="nb">()</span> <span class="nb">()</span>
<span class="kt">Actual</span> <span class="kr">type</span><span class="o">:</span> <span class="kt">SpecWith</span>
<span class="p">(</span><span class="kt">Arg</span> <span class="p">(</span><span class="kt">HelVM</span><span class="o">.</span><span class="kt">HelPA</span><span class="o">.</span><span class="kt">Assemblers</span><span class="o">.</span><span class="kt">Expectations</span><span class="o">.</span><span class="kt">GoldenIO</span> <span class="kt">String</span><span class="p">))</span>
<span class="err">•</span> <span class="kt">In</span> <span class="n">a</span> <span class="n">stmt</span> <span class="kr">of</span> <span class="n">a</span> <span class="n">'do'</span> <span class="n">block</span><span class="o">:</span>
<span class="n">it</span> <span class="n">fileName</span>
<span class="o">$</span> <span class="kr">do</span> <span class="n">assemble</span>
<span class="p">`</span><span class="n">goldenShouldParseReturn</span><span class="p">`</span>
<span class="n">buildAbsolutePathToEtaFile</span> <span class="p">(</span><span class="s">"assembleFile"</span> <span class="o"></></span> <span class="n">fileName</span><span class="p">)</span>
<span class="kt">In</span> <span class="n">the</span> <span class="n">expression</span><span class="o">:</span>
<span class="kr">do</span> <span class="kr">let</span> <span class="n">assemble</span> <span class="o">=</span> <span class="n">assembleFile</span> <span class="o">...</span>
<span class="n">it</span> <span class="n">fileName</span>
<span class="o">$</span> <span class="kr">do</span> <span class="n">assemble</span>
<span class="p">`</span><span class="n">goldenShouldParseReturn</span><span class="p">`</span>
<span class="n">buildAbsolutePathToEtaFile</span> <span class="p">(</span><span class="s">"assembleFile"</span> <span class="o"></></span> <span class="n">fileName</span><span class="p">)</span>
<span class="kt">In</span> <span class="n">the</span> <span class="n">second</span> <span class="n">argument</span> <span class="kr">of</span> <span class="err">‘</span><span class="p">(</span><span class="o">$</span><span class="p">)</span><span class="err">’</span><span class="p">,</span> <span class="n">namely</span>
<span class="err">‘</span><span class="nf">\</span> <span class="n">fileName</span>
<span class="o">-></span> <span class="kr">do</span> <span class="kr">let</span> <span class="o">...</span>
<span class="n">it</span> <span class="n">fileName</span> <span class="o">$</span> <span class="kr">do</span> <span class="o">...</span><span class="err">’</span>
<span class="o">|</span>
<span class="mi">39</span> <span class="o">|</span> <span class="n">it</span> <span class="n">fileName</span> <span class="o">$</span> <span class="kr">do</span> <span class="n">assemble</span> <span class="p">`</span><span class="n">goldenShouldParseReturn</span><span class="p">`</span> <span class="n">buildAbsolutePathToEtaFile</span> <span class="p">(</span><span class="s">"assembleFile"</span> <span class="o"></></span> <span class="n">fileName</span><span class="p">)</span>
<span class="o">|</span> <span class="o">^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</span>
</code></pre></div></div>
<p>Dlaczego?
Otóż:</p>
<ul>
<li>Normalne asercje zwracają typ <code class="language-plaintext highlighter-rouge">Expectation</code>, który jest aliasem dla typu <code class="language-plaintext highlighter-rouge">IO ()</code>.</li>
<li>Złote testy zwracają - <code class="language-plaintext highlighter-rouge">Golden a</code>.</li>
<li>Nasze testy zwracają - <code class="language-plaintext highlighter-rouge">Golden (IO String)</code>.</li>
</ul>
<p>Problemem jest brak instancji (implementacji) klasy typu <code class="language-plaintext highlighter-rouge">Example</code> dla <code class="language-plaintext highlighter-rouge">Golden (IO String)</code>.
Dlatego spróbujmy ją napisać:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">instance</span> <span class="kt">Eq</span> <span class="n">str</span> <span class="o">=></span> <span class="kt">Example</span> <span class="p">(</span><span class="kt">GoldenIO</span> <span class="n">str</span><span class="p">)</span> <span class="kr">where</span>
<span class="kr">type</span> <span class="kt">Arg</span> <span class="p">(</span><span class="kt">GoldenIO</span> <span class="n">str</span><span class="p">)</span> <span class="o">=</span> <span class="nb">()</span>
<span class="n">evaluateExample</span> <span class="n">wrapped</span> <span class="n">params</span> <span class="n">action</span> <span class="n">callback</span> <span class="o">=</span> <span class="n">evaluateExample'</span> <span class="o">=<<</span> <span class="n">unWrappedGoldenIO</span> <span class="n">wrapped</span> <span class="kr">where</span>
<span class="n">evaluateExample'</span> <span class="n">golden</span> <span class="o">=</span> <span class="n">evaluateExample</span> <span class="n">golden</span> <span class="n">params</span> <span class="n">action</span> <span class="n">callback</span>
</code></pre></div></div>
<p>BTW to, co widzimy powyżej to chyba rodziny typów (ang. <a href="/tags/type-family">Type Family</a>)</p>
<p>Niestety to także nie zadziała i dostaniemy błąd:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">hs</span><span class="o">/</span><span class="n">test</span><span class="o">/</span><span class="kt">HelVM</span><span class="o">/</span><span class="kt">HelPA</span><span class="o">/</span><span class="kt">Assemblers</span><span class="o">/</span><span class="kt">Expectations</span><span class="o">.</span><span class="n">hs</span><span class="o">:</span><span class="mi">87</span><span class="o">:</span><span class="mi">1</span><span class="o">:</span> <span class="n">error</span><span class="o">:</span> <span class="p">[</span><span class="o">-</span><span class="kt">Worphans</span><span class="p">,</span> <span class="o">-</span><span class="kt">Werror</span><span class="o">=</span><span class="n">orphans</span><span class="p">]</span>
<span class="kt">Orphan</span> <span class="kr">instance</span><span class="o">:</span> <span class="kr">instance</span> <span class="kt">Eq</span> <span class="n">str</span> <span class="o">=></span> <span class="kt">Example</span> <span class="p">(</span><span class="kt">GoldenIO</span> <span class="n">str</span><span class="p">)</span>
<span class="kt">To</span> <span class="n">avoid</span> <span class="n">this</span>
<span class="n">move</span> <span class="n">the</span> <span class="kr">instance</span> <span class="n">declaration</span> <span class="n">to</span> <span class="n">the</span> <span class="kr">module</span> <span class="err">of</span> <span class="err">the</span> <span class="err">class</span> <span class="err">or</span> <span class="err">of</span> <span class="err">the</span> <span class="err">type,</span> <span class="err">or</span>
<span class="err">wrap</span> <span class="err">the</span> <span class="err">type</span> <span class="err">with</span> <span class="err">a</span> <span class="err">newtype</span> <span class="err">and</span> <span class="err">declare</span> <span class="err">the</span> <span class="err">instance</span> <span class="err">on</span> <span class="err">the</span> <span class="err">new</span> <span class="err">type.</span>
<span class="err">|</span>
<span class="err">87</span> <span class="err">|</span> <span class="err">instance</span> <span class="nn">Eq</span> <span class="n">str</span> <span class="o">=></span> <span class="kt">Example</span> <span class="p">(</span><span class="kt">GoldenIO</span> <span class="n">str</span><span class="p">)</span> <span class="kr">where</span>
<span class="o">|</span> <span class="o">^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^...</span>
</code></pre></div></div>
<p>Wynika to z tego,
że w tej chwili mamy osieroconą instancję klasy typu <code class="language-plaintext highlighter-rouge">Example</code>.
Instancje klas typów dla typu danych możemy tworzyć tylko w modułach:</p>
<ul>
<li>gdzie zdefiniowana jest klasa typu;</li>
<li>gdzie zdefiniowany jest typ danych.</li>
</ul>
<p>Ponieważ nie mamy wpływu na klasę typu to musimy utworzyć nowy typ danych opakowujący typ <code class="language-plaintext highlighter-rouge">GoldenIO</code>:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">newtype</span> <span class="kt">WrappedGoldenIO</span> <span class="n">a</span> <span class="o">=</span> <span class="kt">WrappedGoldenIO</span> <span class="p">{</span> <span class="n">unWrappedGoldenIO</span> <span class="o">::</span> <span class="kt">GoldenIO</span> <span class="n">a</span> <span class="p">}</span>
</code></pre></div></div>
<p>Nową asercję:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">infix</span> <span class="mi">1</span> <span class="p">`</span><span class="n">goldenShouldReturn</span><span class="p">`</span>
<span class="n">goldenShouldReturn</span> <span class="o">::</span> <span class="kt">IO</span> <span class="kt">String</span> <span class="o">-></span> <span class="kt">String</span> <span class="o">-></span> <span class="kt">WrappedGoldenIO</span> <span class="kt">String</span>
<span class="n">goldenShouldReturn</span> <span class="n">actualOutputIO</span> <span class="o">=</span> <span class="kt">WrappedGoldenIO</span> <span class="o">.</span> <span class="n">goldenShouldReturn'</span> <span class="n">actualOutputIO</span>
</code></pre></div></div>
<p>Oraz nową instancji klasy typu <code class="language-plaintext highlighter-rouge">Example</code>:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">instance</span> <span class="kt">Eq</span> <span class="n">str</span> <span class="o">=></span> <span class="kt">Example</span> <span class="p">(</span><span class="kt">WrappedGoldenIO</span> <span class="n">str</span><span class="p">)</span> <span class="kr">where</span>
<span class="kr">type</span> <span class="kt">Arg</span> <span class="p">(</span><span class="kt">WrappedGoldenIO</span> <span class="n">str</span><span class="p">)</span> <span class="o">=</span> <span class="nb">()</span>
<span class="n">evaluateExample</span> <span class="n">wrapped</span> <span class="n">params</span> <span class="n">action</span> <span class="n">callback</span> <span class="o">=</span> <span class="n">evaluateExample'</span> <span class="o">=<<</span> <span class="n">unWrappedGoldenIO</span> <span class="n">wrapped</span> <span class="kr">where</span>
<span class="n">evaluateExample'</span> <span class="n">golden</span> <span class="o">=</span> <span class="n">evaluateExample</span> <span class="n">golden</span> <span class="n">params</span> <span class="n">action</span> <span class="n">callback</span>
</code></pre></div></div>
<p>Dzięki temu możemy napisać złoty test end-to-end:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">module</span> <span class="nn">HelVM.HelPA.Assemblers.EAS.AssemblerSpec</span> <span class="kr">where</span>
<span class="kr">import</span> <span class="nn">HelVM.HelPA.Assemblers.EAS.Assembler</span>
<span class="kr">import</span> <span class="nn">HelVM.HelPA.Assemblers.EAS.FileUtil</span>
<span class="kr">import</span> <span class="nn">HelVM.HelPA.Assemblers.Expectations</span>
<span class="kr">import</span> <span class="nn">HelVM.HelPA.Common.API</span>
<span class="kr">import</span> <span class="nn">Test.Hspec</span>
<span class="n">spec</span> <span class="o">::</span> <span class="kt">Spec</span>
<span class="n">spec</span> <span class="o">=</span> <span class="kr">do</span>
<span class="n">describe</span> <span class="s">"assembleFile"</span> <span class="o">$</span> <span class="kr">do</span>
<span class="n">forM_</span> <span class="p">[</span> <span class="s">"true"</span>
<span class="p">,</span> <span class="s">"hello"</span>
<span class="p">,</span> <span class="s">"pip"</span>
<span class="p">,</span> <span class="s">"pip2"</span>
<span class="p">,</span> <span class="s">"reverse"</span>
<span class="p">,</span> <span class="s">"function"</span>
<span class="p">,</span> <span class="s">"add"</span>
<span class="p">,</span> <span class="s">"writestr"</span>
<span class="p">,</span> <span class="s">"hello2"</span>
<span class="p">,</span> <span class="s">"hello3"</span>
<span class="p">,</span> <span class="s">"hello4"</span>
<span class="p">,</span> <span class="s">"multiply"</span>
<span class="p">,</span> <span class="s">"fact"</span>
<span class="p">,</span> <span class="s">"bottles"</span>
<span class="p">,</span> <span class="s">"euclid"</span>
<span class="p">]</span> <span class="o">$</span> <span class="nf">\</span><span class="n">fileName</span> <span class="o">-></span> <span class="kr">do</span>
<span class="kr">let</span> <span class="n">assembleFile</span> <span class="o">=</span> <span class="n">assembly</span> <span class="kt">SourcePath</span> <span class="p">{</span><span class="n">dirPath</span> <span class="o">=</span> <span class="n">easDir</span><span class="p">,</span> <span class="n">filePath</span> <span class="o">=</span> <span class="n">buildAbsolutePathToEasFile</span> <span class="n">fileName</span><span class="p">}</span>
<span class="n">it</span> <span class="n">fileName</span> <span class="o">$</span> <span class="kr">do</span> <span class="n">assembleFile</span> <span class="p">`</span><span class="n">goldenShouldParseReturn</span><span class="p">`</span> <span class="n">buildAbsolutePathToEtaFile</span> <span class="n">fileName</span>
</code></pre></div></div>
<h2 id="testy-jednostkowe-kontra-testy-integracyjne">Testy jednostkowe kontra testy integracyjne</h2>
<p>Po tym wszystkim rodzą się dwa pytania:</p>
<ul>
<li>Co z piramidą testów i testami jednostkowymi?</li>
<li>Na ile to jest szybkie?</li>
</ul>
<h3 id="gdzie-testy-jednostkowe-i-piramida-testów">Gdzie testy jednostkowe i piramida testów?</h3>
<p>Trochę offtop, ale IMHO ta cała piramida testów (i odwrócona piramida testów) to pic na wodę.
Dlaczego o tym mówimy?
Bo pokazywali nam to na konferencjach.
A czemu nam to pokazywali? Bo piramida ładnie wygląda na slajdach.
Chyba widziałem wszystkie możliwe ułożenia testów (z wyjątkiem piramid):</p>
<ul>
<li>Pracowałem w firmach,
gdzie istniały tylko testy manualne.</li>
<li>Widziałem firmę,
gdzie istniały tylko testy manualne i jednostkowe, bo testerzy nie mieli czasu pisać testów systemowo-akceptacyjnych.</li>
<li>Pracowałem w firmie,
gdzie nie dało się powiedzieć czy jest więcej jednostkowych czy systemowo-akceptacyjnych,
bo programiści pisali swoje testy, a testerzy swoje.</li>
<li>Pracowałem w firmie,
gdzie była niechęć do testów jednostkowych, a programiści i testerzy wspólnie pisali testy integracyjno-akceptacyjne.</li>
</ul>
<p>Co do samych definicji to nie widziałem żadnego porządnego papieru,
który określałby co to jest jednostka.
Metoda/funkcja?
Klasa/moduł?
Pakiet?
Mikroserwis?
Mikroserwis z własną bazą danych?
Wszystkie te sprzeczne definicje można spotkać na konferencjach.
Niektórzy wprost mówią,
że definicje się zmieniły odkąd mamy mikroserwisy.
Jeśli ktoś ma uznany papier z porządną definicją to z chęcią przeczytam.</p>
<p>Które testy osobiście uważam za najlepsze?
Te które są szybkie, ale jednocześnie testują maksymalnie dużo kodu.
Dla mnie takimi testami dla większości aplikacji webowych są testy na poziomie mikroserwisu z prawdziwą bazą danych postawioną w dockerze.
Jeśli czegoś w prosty sposób nie da się postawić w dockerze to mockuję.
Albo na poziomie http, albo dostarczam alternatywną implementację klienta.</p>
<p>Tutaj jednak, na szczęście, nie mamy aplikacji webowej z http i bazą danych.
Mamy aplikację pracującą na plikach i to na plikach powinniśmy ją testować.</p>
<p>Nie mówię,
że testy jednostkowe są całkiem złe.
Testy jednostkowe były dla mnie przydatne na początku pisania.
Ale teraz małe testy jednostkowe są spowalniaczem przy refaktoryzacji.</p>
<h3 id="czas-czyli-czy-to-nie-jest-za-wolne">Czas, czyli czy to nie jest za wolne.</h3>
<p>Jeśli rezygnujemy z testów jednostkowych na rzecz testów integracyjnych to najważniejsze jest pytanie o czas.
Czy testy integracyjne nie wykonują się za wolno?</p>
<p>Z pomocą przychodzi nam tu biblioteka <code class="language-plaintext highlighter-rouge">hspec-slow</code>.
Pozwala ona mierzyć czas wykonywania pojedynczych przypadków testowych.</p>
<p>Przy założeniu,
że punkt wejściowy dla testów był w pliku <code class="language-plaintext highlighter-rouge">hs/test/Spec.hs</code>,
tworzymy plik <code class="language-plaintext highlighter-rouge">hs/test/Main.hs</code>,
który będzie nowym punktem wejściowym dla testów:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">module</span> <span class="nn">Main</span> <span class="kr">where</span>
<span class="kr">import</span> <span class="k">qualified</span> <span class="nn">Spec</span>
<span class="kr">import</span> <span class="nn">Test.Hspec.Slow</span>
<span class="kr">import</span> <span class="nn">Test.Hspec</span> <span class="p">(</span><span class="nf">hspec</span><span class="p">)</span>
<span class="n">main</span> <span class="o">::</span> <span class="kt">IO</span> <span class="nb">()</span>
<span class="n">main</span> <span class="o">=</span> <span class="kr">do</span>
<span class="n">config</span> <span class="o"><-</span> <span class="n">configure</span> <span class="mi">1</span>
<span class="n">hspec</span> <span class="o">$</span> <span class="n">timeThese</span> <span class="n">config</span> <span class="kt">Spec</span><span class="o">.</span><span class="n">spec</span>
</code></pre></div></div>
<p>Jednocześnie,
jeśli chcemy dalej automatycznie generować agregator dla wszystkich testów,
musimy zmienić plik <code class="language-plaintext highlighter-rouge">hs/test/Spec.hs</code> na</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">{-# OPTIONS_GHC -F -pgmF hspec-discover -optF --module-name=Spec #-}</span>
</code></pre></div></div>
<p>W projekcie <a href="/projects/helpa">HelPA</a> nie ma żadnych testów dłuższych niż jedna sekunda.
Jednak w projekcie <a href="/projects/helma">HelMA</a> kilka takich testów się znalazło:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1.128178606s: hs/test/HelVM/HelMA/Automata/ETA/EvaluatorSpec.hs[70:9]
interact/ListStackType/bottles
1.083167682s: hs/test/HelVM/HelMA/Automata/ETA/EvaluatorSpec.hs[72:9]
monadic/ListStackType/bottles
1.04183862s: hs/test/HelVM/HelMA/Automata/ETA/EvaluatorSpec.hs[74:9]
logging/ListStackType/bottles
1.266628515s: hs/test/HelVM/HelMA/Automata/ETA/EvaluatorSpec.hs[70:9]
interact/SeqStackType/bottles
1.120756983s: hs/test/HelVM/HelMA/Automata/ETA/EvaluatorSpec.hs[72:9]
monadic/SeqStackType/bottles
1.118947099s: hs/test/HelVM/HelMA/Automata/ETA/EvaluatorSpec.hs[74:9]
logging/SeqStackType/bottles
</code></pre></div></div>
<p>W zasadzie jest to jeden test wywoływany sześciokrotnie z różnymi parametrami.</p>
<p>Całość testów trochę trwa:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Finished in 24.1430 seconds
</code></pre></div></div>
<p>Ilość testów jest jednak spora:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1252 examples, 0 failures
</code></pre></div></div>
<p>Rozwiązaniem może być pozbycie się części testów.
W tej chwili testuję wszystkie kombinacje parametrów na wszystkich przykładowych programach w językach ezoterycznych.
Drugim rozwiązaniem może być podzielenie testów na dwa zestawy:</p>
<ul>
<li>Szybko wykonujące się testy dymne (ang. smoke test)</li>
<li>Pozostałe testy</li>
</ul>
<h2 id="złote-testy---czy-warto">Złote testy - czy warto?</h2>
<p>Krótko - warto.
Kod testów się skrócił,
ponieważ wartości oczekiwane do testów zostały przeniesione do złotych plików.
Jednocześnie zlikwidowało to przymus używania znaków ucieczki do zapisywania znaku końca linii.
Oraz rozwiązało to problem,
że niektóre skrypty w <a href="/eso/brainfuck">BrainFucku</a> są zgodne z Windowsem, a nie Linuksem</p>
<p>Kod asemblera <strong><a href="/projects/helpa">HelPA</a></strong> po zmianach znajduje się na <a href="https://github.com/helvm/helpa/tree/v0.3.2.0">githubie</a>.
Podobnie jak kod interpretera <strong><a href="/projects/helma">HelMA</a></strong> po zmianach znajduje się na <a href="https://github.com/helvm/helma/tree/v0.6.5.0">githubie</a>.</p>TheKamilAdamZainspirowany wpisem o złotych testach na 4programmers postanowiłem dodać je do swojego projektu w Haskellu.Abstrakcja i dopasowanie do wzorców w Haskellu2021-04-07T00:00:00+02:002021-04-07T00:00:00+02:00http://writeonly.pl/haskell-eta/pattern-matching<p>Po <a href="/haskell-eta/encapsulation">hermetyzacji</a> i <a href="/haskell-eta/abstraction">abstrakcji</a> RAMu dla interpretera <a href="/projects/helma">HelMA</a> pora na stos.
Stos, zwłaszcza stos arytmetyczny, jest strukturą używaną w wielu interpreterach jezyków ezoterycznych.
Więc warto wydzielić tą abstrakcję do osobnego modułu.</p>
<p>Żeby zaimplementować stos będziemy potrzebować dopasowania do wzorców (ang. <em><a href="/tags/pattern-matching">pattern matching</a></em>),
niestety abstrakcje i dopasowanie do wzorców są to pojęcia kłócące się.</p>
<p>Ponieważ dopasowanie do wzorców działa na implementacji,
Trzeba opakować dopasowanie do wzorców w abstrakcje.</p>
<h2 id="abstrakcja-i-klasy-typów">Abstrakcja i klasy typów</h2>
<p>Spójrzmy na moduł <code class="language-plaintext highlighter-rouge">HelVM.HelCam.Common.Memories.Stack</code>,
dla czytelności podzielony na pięć listingów.</p>
<p>Najpierw deklaracje i importy:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">{-# Language AllowAmbiguousTypes #-}</span>
<span class="cp">{-# Language FlexibleInstances #-}</span>
<span class="cp">{-# Language MultiParamTypeClasses #-}</span>
<span class="kr">module</span> <span class="nn">HelVM.HelCam.Common.Memories.Stack</span> <span class="p">(</span>
<span class="kt">Index</span><span class="p">,</span>
<span class="kt">Stack</span><span class="p">,</span>
<span class="nf">select</span><span class="p">,</span>
<span class="kt">HelVM</span><span class="o">.</span><span class="kt">HelCam</span><span class="o">.</span><span class="kt">Common</span><span class="o">.</span><span class="kt">Memories</span><span class="o">.</span><span class="kt">Stack</span><span class="o">.</span><span class="nf">empty</span><span class="p">,</span>
<span class="kt">HelVM</span><span class="o">.</span><span class="kt">HelCam</span><span class="o">.</span><span class="kt">Common</span><span class="o">.</span><span class="kt">Memories</span><span class="o">.</span><span class="kt">Stack</span><span class="o">.</span><span class="nf">lookup</span><span class="p">,</span>
<span class="kt">HelVM</span><span class="o">.</span><span class="kt">HelCam</span><span class="o">.</span><span class="kt">Common</span><span class="o">.</span><span class="kt">Memories</span><span class="o">.</span><span class="kt">Stack</span><span class="o">.</span><span class="nf">splitAt'</span><span class="p">,</span>
<span class="kt">HelVM</span><span class="o">.</span><span class="kt">HelCam</span><span class="o">.</span><span class="kt">Common</span><span class="o">.</span><span class="kt">Memories</span><span class="o">.</span><span class="kt">Stack</span><span class="o">.</span><span class="nf">drop'</span><span class="p">,</span>
<span class="nf">push1</span><span class="p">,</span>
<span class="nf">pop1</span><span class="p">,</span>
<span class="nf">push2</span><span class="p">,</span>
<span class="nf">pop2</span>
<span class="p">)</span> <span class="kr">where</span>
<span class="kr">import</span> <span class="nn">Data.Sequence</span> <span class="k">as</span> <span class="n">Seq</span>
<span class="kr">type</span> <span class="kt">Index</span> <span class="o">=</span> <span class="kt">Int</span>
</code></pre></div></div>
<p>Jak się później okaże, eksporty zawierają jeden typ,
jedną klasę typów (ang. <em><a href="/tags/type-class">Type Class</a></em>)
i 9 funkcji,
w tym jedna funkcja generyczna i osiem metod (uparcie nazywam tak funkcje w klasach typów).</p>
<p>Następnie kod,
który normalnie znalazłby się w klasie bazowej:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">select</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="n">s</span> <span class="n">m</span> <span class="o">=></span> <span class="kt">Index</span> <span class="o">-></span> <span class="n">m</span> <span class="o">-></span> <span class="n">s</span>
<span class="n">select</span> <span class="n">i</span> <span class="n">stack</span> <span class="o">=</span> <span class="n">check</span> <span class="o">$</span> <span class="kt">HelVM</span><span class="o">.</span><span class="kt">HelCam</span><span class="o">.</span><span class="kt">Common</span><span class="o">.</span><span class="kt">Memories</span><span class="o">.</span><span class="kt">Stack</span><span class="o">.</span><span class="n">lookup</span> <span class="n">i</span> <span class="n">stack</span> <span class="kr">where</span>
<span class="n">check</span> <span class="p">(</span><span class="kt">Just</span> <span class="n">symbol</span><span class="p">)</span> <span class="o">=</span> <span class="n">symbol</span>
<span class="n">check</span> <span class="kt">Nothing</span> <span class="o">=</span> <span class="n">error</span> <span class="o">$</span> <span class="s">"Empty stack "</span> <span class="o"><></span> <span class="n">show</span> <span class="n">stack</span> <span class="o"><></span> <span class="s">" index "</span> <span class="o"><></span> <span class="n">show</span> <span class="n">i</span>
</code></pre></div></div>
<p>Definiujemy jedną funkcję generyczną.
Czemu tylko jedną?
O tym później.</p>
<h3 id="abstrakcja-oparta-na-klasie-typu">Abstrakcja oparta na klasie typu</h3>
<p>Nasz stos będzie potrzebować 8 podstawowych metod.</p>
<p>Podobnie jak dla klasy typów <code class="language-plaintext highlighter-rouge">RAM</code> potrzebujemy klasy typów dla dwóch parametrów,
symbolu <code class="language-plaintext highlighter-rouge">s</code> i pamieci <code class="language-plaintext highlighter-rouge">m</code>:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">class</span> <span class="p">(</span><span class="kt">Semigroup</span> <span class="n">m</span><span class="p">,</span> <span class="kt">Show</span> <span class="n">m</span><span class="p">)</span> <span class="o">=></span> <span class="kt">Stack</span> <span class="n">s</span> <span class="n">m</span> <span class="kr">where</span>
<span class="n">empty</span> <span class="o">::</span> <span class="n">m</span>
<span class="n">lookup</span> <span class="o">::</span> <span class="kt">Index</span> <span class="o">-></span> <span class="n">m</span> <span class="o">-></span> <span class="kt">Maybe</span> <span class="n">s</span>
<span class="n">splitAt'</span> <span class="o">::</span> <span class="n">s</span> <span class="o">-></span> <span class="kt">Index</span> <span class="o">-></span> <span class="n">m</span> <span class="o">-></span> <span class="p">(</span><span class="n">m</span><span class="p">,</span> <span class="n">m</span><span class="p">)</span>
<span class="n">drop'</span> <span class="o">::</span> <span class="n">s</span> <span class="o">-></span> <span class="kt">Index</span> <span class="o">-></span> <span class="n">m</span> <span class="o">-></span> <span class="n">m</span>
<span class="n">push1</span> <span class="o">::</span> <span class="n">s</span> <span class="o">-></span> <span class="n">m</span> <span class="o">-></span> <span class="n">m</span>
<span class="n">pop1</span> <span class="o">::</span> <span class="n">m</span> <span class="o">-></span> <span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">m</span><span class="p">)</span>
<span class="n">push2</span> <span class="o">::</span> <span class="n">s</span> <span class="o">-></span> <span class="n">s</span> <span class="o">-></span> <span class="n">m</span> <span class="o">-></span> <span class="n">m</span>
<span class="n">pop2</span> <span class="o">::</span> <span class="n">m</span> <span class="o">-></span> <span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">s</span><span class="p">,</span> <span class="n">m</span><span class="p">)</span>
</code></pre></div></div>
<p>Są tu dwie brzydkie metody <code class="language-plaintext highlighter-rouge">splitAt'</code> i <code class="language-plaintext highlighter-rouge">drop'</code>.
Wynika to z tego,
że w każdej sygnaturze muszą być użyte oba parametry generyczne.
Niestety nie umiałem tego napisać lepiej.</p>
<h3 id="implementacja-oparta-na-liście">Implementacja oparta na liście</h3>
<p>Najpierw prostsza implementacja dla listy:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">instance</span> <span class="kt">Show</span> <span class="n">s</span> <span class="o">=></span> <span class="kt">Stack</span> <span class="n">s</span> <span class="p">[</span><span class="n">s</span><span class="p">]</span> <span class="kr">where</span>
<span class="n">empty</span> <span class="o">=</span> <span class="kt">[]</span>
<span class="n">lookup</span> <span class="n">i</span> <span class="n">stack</span> <span class="o">=</span> <span class="n">stack</span> <span class="o">!!?</span> <span class="n">i</span>
<span class="n">splitAt'</span> <span class="kr">_</span> <span class="n">i</span> <span class="n">stack</span> <span class="o">=</span> <span class="kt">Prelude</span><span class="o">.</span><span class="n">splitAt</span> <span class="n">i</span> <span class="n">stack</span>
<span class="n">drop'</span> <span class="kr">_</span> <span class="n">i</span> <span class="n">stack</span> <span class="o">=</span> <span class="kt">Prelude</span><span class="o">.</span><span class="n">drop</span> <span class="n">i</span> <span class="n">stack</span>
<span class="n">push1</span> <span class="n">symbol</span> <span class="n">stack</span> <span class="o">=</span> <span class="n">symbol</span><span class="o">:</span> <span class="n">stack</span>
<span class="n">pop1</span> <span class="p">(</span><span class="n">symbol</span> <span class="o">:</span> <span class="n">stack</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="n">symbol</span><span class="p">,</span> <span class="n">stack</span><span class="p">)</span>
<span class="n">pop1</span> <span class="n">stack</span> <span class="o">=</span> <span class="n">error</span> <span class="o">$</span> <span class="s">"Empty stack "</span> <span class="o"><></span> <span class="n">show</span> <span class="n">stack</span>
<span class="n">push2</span> <span class="n">symbol</span> <span class="n">symbol'</span> <span class="n">stack</span> <span class="o">=</span> <span class="n">symbol</span><span class="o">:</span> <span class="n">symbol'</span><span class="o">:</span> <span class="n">stack</span>
<span class="n">pop2</span> <span class="p">(</span><span class="n">symbol</span> <span class="o">:</span> <span class="n">symbol'</span> <span class="o">:</span> <span class="n">stack</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="n">symbol</span><span class="p">,</span> <span class="n">symbol'</span><span class="p">,</span> <span class="n">stack</span><span class="p">)</span>
<span class="n">pop2</span> <span class="n">stack</span> <span class="o">=</span> <span class="n">error</span> <span class="o">$</span> <span class="s">"Empty stack "</span> <span class="o"><></span> <span class="n">show</span> <span class="n">stack</span>
</code></pre></div></div>
<p>Mamy tu klasyczne dopasowanie do wzorców dla list.
Nic nadzwyczajnego.</p>
<h3 id="implementacja-oparta-na-sekwencji">Implementacja oparta na sekwencji</h3>
<p>Implementacja dla sekwencji:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">instance</span> <span class="kt">Show</span> <span class="n">s</span> <span class="o">=></span> <span class="kt">Stack</span> <span class="n">s</span> <span class="p">(</span><span class="kt">Seq</span> <span class="n">s</span><span class="p">)</span> <span class="kr">where</span>
<span class="n">empty</span> <span class="o">=</span> <span class="kt">Seq</span><span class="o">.</span><span class="n">fromList</span> <span class="kt">[]</span>
<span class="n">lookup</span> <span class="n">i</span> <span class="n">stack</span> <span class="o">=</span> <span class="kt">Seq</span><span class="o">.</span><span class="n">lookup</span> <span class="n">i</span> <span class="n">stack</span>
<span class="n">splitAt'</span> <span class="kr">_</span> <span class="n">i</span> <span class="n">stack</span> <span class="o">=</span> <span class="kt">Seq</span><span class="o">.</span><span class="n">splitAt</span> <span class="n">i</span> <span class="n">stack</span>
<span class="n">drop'</span> <span class="kr">_</span> <span class="n">i</span> <span class="n">stack</span> <span class="o">=</span> <span class="kt">Seq</span><span class="o">.</span><span class="n">drop</span> <span class="n">i</span> <span class="n">stack</span>
<span class="n">push1</span> <span class="n">symbol</span> <span class="n">stack</span> <span class="o">=</span> <span class="n">symbol</span> <span class="o"><|</span> <span class="n">stack</span>
<span class="n">pop1</span> <span class="p">(</span><span class="n">symbol</span> <span class="o">:<|</span> <span class="n">stack</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="n">symbol</span><span class="p">,</span> <span class="n">stack</span><span class="p">)</span>
<span class="n">pop1</span> <span class="n">stack</span> <span class="o">=</span> <span class="n">error</span> <span class="o">$</span> <span class="s">"Empty stack "</span> <span class="o"><></span> <span class="n">show</span> <span class="n">stack</span>
<span class="n">push2</span> <span class="n">symbol</span> <span class="n">symbol'</span> <span class="n">stack</span> <span class="o">=</span> <span class="n">symbol</span> <span class="o"><|</span> <span class="n">symbol'</span> <span class="o"><|</span> <span class="n">stack</span>
<span class="n">pop2</span> <span class="p">(</span><span class="n">symbol</span> <span class="o">:<|</span> <span class="n">symbol'</span> <span class="o">:<|</span> <span class="n">stack</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="n">symbol</span><span class="p">,</span> <span class="n">symbol'</span><span class="p">,</span> <span class="n">stack</span><span class="p">)</span>
<span class="n">pop2</span> <span class="n">stack</span> <span class="o">=</span> <span class="n">error</span> <span class="o">$</span> <span class="s">"Empty stack "</span> <span class="o"><></span> <span class="n">show</span> <span class="n">stack</span>
</code></pre></div></div>
<p>Mamy tu dwa nowe operatory <code class="language-plaintext highlighter-rouge">:<|</code> dla dopasowania do wzorców oraz <code class="language-plaintext highlighter-rouge"><|</code> dla dołączania do sekwencji.
Jeśli operowalibyśmy na drugim końcu sekwencji należałoby użyć <code class="language-plaintext highlighter-rouge">:|></code> i <code class="language-plaintext highlighter-rouge">|></code>.</p>
<h2 id="ograniczenia">Ograniczenia</h2>
<p>Tutaj pojawia się mały zgrzyt.
Niestety nie potrafię zdefinioć niektórych funkcji generycznych dla parametru generycznego <code class="language-plaintext highlighter-rouge">Stack s m</code>.
Na razie musimy zadowolić się mniej genrycznymi wersjami dla parametru <code class="language-plaintext highlighter-rouge">Stack Symbol m</code>.
Funkcje te są zdefiniowane w modułach <code class="language-plaintext highlighter-rouge">HelVM.HelCam.Machines.ETA.StackOfSymbols</code> oraz <code class="language-plaintext highlighter-rouge">HelVM.HelCam.Machines.WhiteSpace.StackOfSymbols</code>.</p>
<p>Funkcje pomocnicze dla interpretera eso języka <strong><a href="/eso/eta">ETA</a></strong>:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">{-# Language FlexibleContexts #-}</span>
<span class="kr">module</span> <span class="nn">HelVM.HelCam.Machines.ETA.StackOfSymbols</span> <span class="kr">where</span>
<span class="kr">import</span> <span class="nn">HelVM.HelCam.Machines.ETA.EvaluatorUtil</span>
<span class="kr">import</span> <span class="nn">HelVM.HelCam.Common.Memories.Stack</span>
<span class="c1">-- Arithmetic</span>
<span class="n">divMod</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="kt">Symbol</span> <span class="n">m</span> <span class="o">=></span> <span class="n">m</span> <span class="o">-></span> <span class="n">m</span>
<span class="n">divMod</span> <span class="n">stack</span> <span class="o">=</span> <span class="n">push2</span> <span class="p">(</span><span class="n">symbol'</span> <span class="p">`</span><span class="n">mod</span><span class="p">`</span> <span class="n">symbol</span> <span class="o">::</span><span class="kt">Symbol</span><span class="p">)</span> <span class="p">(</span><span class="n">symbol'</span> <span class="p">`</span><span class="n">div</span><span class="p">`</span> <span class="n">symbol</span> <span class="o">::</span><span class="kt">Symbol</span><span class="p">)</span> <span class="n">stack'</span>
<span class="kr">where</span> <span class="p">(</span><span class="n">symbol</span><span class="p">,</span> <span class="n">symbol'</span><span class="p">,</span> <span class="n">stack'</span><span class="p">)</span> <span class="o">=</span> <span class="n">pop2</span> <span class="n">stack</span>
<span class="n">sub</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="kt">Symbol</span> <span class="n">m</span> <span class="o">=></span> <span class="n">m</span> <span class="o">-></span> <span class="n">m</span>
<span class="n">sub</span> <span class="n">stack</span> <span class="o">=</span> <span class="n">push1</span> <span class="p">(</span><span class="n">symbol'</span> <span class="o">-</span> <span class="n">symbol</span> <span class="o">::</span><span class="kt">Symbol</span><span class="p">)</span> <span class="n">stack'</span>
<span class="kr">where</span> <span class="p">(</span><span class="n">symbol</span><span class="p">,</span> <span class="n">symbol'</span><span class="p">,</span> <span class="n">stack'</span><span class="p">)</span> <span class="o">=</span> <span class="n">pop2</span> <span class="n">stack</span>
<span class="c1">-- Stack instructions</span>
<span class="n">halibut</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="kt">Symbol</span> <span class="n">m</span> <span class="o">=></span> <span class="n">m</span> <span class="o">-></span> <span class="n">m</span>
<span class="n">halibut</span> <span class="n">stack</span>
<span class="o">|</span> <span class="n">i</span> <span class="o"><=</span> <span class="mi">0</span> <span class="o">=</span> <span class="n">copy</span> <span class="p">(</span><span class="n">negate</span> <span class="n">i</span><span class="p">)</span> <span class="n">stack'</span>
<span class="o">|</span> <span class="n">otherwise</span> <span class="o">=</span> <span class="n">move</span> <span class="p">(</span><span class="mi">0</span> <span class="o">::</span><span class="kt">Symbol</span><span class="p">)</span> <span class="n">i</span> <span class="n">stack'</span>
<span class="kr">where</span> <span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">stack'</span><span class="p">)</span> <span class="o">=</span> <span class="n">pop1</span> <span class="n">stack</span>
<span class="n">move</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="kt">Symbol</span> <span class="n">m</span> <span class="o">=></span> <span class="kt">Symbol</span> <span class="o">-></span> <span class="kt">Index</span> <span class="o">-></span> <span class="n">m</span> <span class="o">-></span> <span class="n">m</span>
<span class="n">move</span> <span class="n">symbol</span> <span class="n">i</span> <span class="n">stack</span> <span class="o">=</span> <span class="n">tops</span> <span class="o"><></span> <span class="n">middles</span> <span class="o"><></span> <span class="n">bottoms</span> <span class="kr">where</span>
<span class="p">(</span><span class="n">middles</span><span class="p">,</span> <span class="n">stack'</span><span class="p">)</span> <span class="o">=</span> <span class="n">splitAt'</span> <span class="n">symbol</span> <span class="n">i</span> <span class="n">stack</span>
<span class="p">(</span><span class="n">tops</span><span class="p">,</span> <span class="n">bottoms</span><span class="p">)</span> <span class="o">=</span> <span class="n">splitAt'</span> <span class="n">symbol</span> <span class="mi">1</span> <span class="n">stack'</span>
<span class="n">copy</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="kt">Symbol</span> <span class="n">m</span> <span class="o">=></span> <span class="kt">Index</span> <span class="o">-></span> <span class="n">m</span> <span class="o">-></span> <span class="n">m</span>
<span class="n">copy</span> <span class="n">i</span> <span class="n">stack</span> <span class="o">=</span> <span class="n">push1</span> <span class="p">(</span><span class="n">select</span> <span class="n">i</span> <span class="n">stack</span> <span class="o">::</span><span class="kt">Symbol</span><span class="p">)</span> <span class="n">stack</span>
</code></pre></div></div>
<p>Funkcje pomocnicze dla interpretera eso języka <strong><a href="/eso/whitespace">WhiteSpace</a></strong>:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">{-# Language AllowAmbiguousTypes #-}</span>
<span class="cp">{-# Language FlexibleContexts #-}</span>
<span class="cp">{-# Language FlexibleInstances #-}</span>
<span class="cp">{-# Language MultiParamTypeClasses #-}</span>
<span class="kr">module</span> <span class="nn">HelVM.HelCam.Machines.WhiteSpace.StackOfSymbols</span> <span class="kr">where</span>
<span class="kr">import</span> <span class="nn">HelVM.HelCam.Machines.WhiteSpace.EvaluatorUtil</span>
<span class="kr">import</span> <span class="nn">HelVM.HelCam.Machines.WhiteSpace.Instruction</span>
<span class="kr">import</span> <span class="nn">HelVM.HelCam.Common.Memories.Stack</span>
<span class="c1">-- Arithmetic</span>
<span class="n">binaryOp</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="kt">Symbol</span> <span class="n">m</span> <span class="o">=></span> <span class="kt">BinaryOperator</span> <span class="o">-></span> <span class="n">m</span> <span class="o">-></span> <span class="n">m</span>
<span class="n">binaryOp</span> <span class="n">op</span> <span class="n">stack</span> <span class="o">=</span> <span class="n">push1</span> <span class="p">(</span><span class="n">doBinary</span> <span class="n">op</span> <span class="n">symbol</span> <span class="n">symbol'</span> <span class="o">::</span><span class="kt">Symbol</span><span class="p">)</span> <span class="n">stack'</span> <span class="kr">where</span> <span class="p">(</span><span class="n">symbol</span><span class="p">,</span> <span class="n">symbol'</span><span class="p">,</span> <span class="n">stack'</span><span class="p">)</span> <span class="o">=</span> <span class="n">pop2</span> <span class="n">stack</span>
<span class="c1">-- Stack instructions</span>
<span class="n">swap</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="kt">Symbol</span> <span class="n">m</span> <span class="o">=></span> <span class="n">m</span> <span class="o">-></span> <span class="n">m</span>
<span class="n">swap</span> <span class="n">stack</span> <span class="o">=</span> <span class="n">push2</span> <span class="p">(</span><span class="n">symbol'</span><span class="o">::</span><span class="kt">Symbol</span><span class="p">)</span> <span class="n">symbol</span> <span class="n">stack'</span> <span class="kr">where</span> <span class="p">(</span><span class="n">symbol</span><span class="p">,</span> <span class="n">symbol'</span><span class="p">,</span> <span class="n">stack'</span><span class="p">)</span> <span class="o">=</span> <span class="n">pop2</span> <span class="n">stack</span>
<span class="n">discard</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="kt">Symbol</span> <span class="n">m</span> <span class="o">=></span> <span class="n">m</span> <span class="o">-></span> <span class="n">m</span>
<span class="n">discard</span> <span class="o">=</span> <span class="n">drop'</span> <span class="p">(</span><span class="mi">0</span><span class="o">::</span><span class="kt">Symbol</span><span class="p">)</span> <span class="mi">1</span>
<span class="n">slide</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="kt">Symbol</span> <span class="n">m</span> <span class="o">=></span> <span class="kt">Index</span> <span class="o">-></span> <span class="n">m</span> <span class="o">-></span> <span class="n">m</span>
<span class="n">slide</span> <span class="n">i</span> <span class="n">stack</span> <span class="o">=</span> <span class="n">push1</span> <span class="p">(</span><span class="n">symbol</span><span class="o">::</span><span class="kt">Symbol</span><span class="p">)</span> <span class="p">(</span><span class="n">drop'</span> <span class="p">(</span><span class="mi">0</span><span class="o">::</span><span class="kt">Symbol</span><span class="p">)</span> <span class="n">i</span> <span class="n">stack'</span><span class="p">)</span> <span class="kr">where</span> <span class="p">(</span><span class="n">symbol</span><span class="p">,</span> <span class="n">stack'</span><span class="p">)</span> <span class="o">=</span> <span class="n">pop1</span> <span class="n">stack</span>
<span class="n">dup</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="kt">Symbol</span> <span class="n">m</span> <span class="o">=></span> <span class="n">m</span> <span class="o">-></span> <span class="n">m</span>
<span class="n">dup</span> <span class="o">=</span> <span class="n">copy</span> <span class="mi">0</span>
<span class="n">copy</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="kt">Symbol</span> <span class="n">m</span> <span class="o">=></span> <span class="kt">Index</span> <span class="o">-></span> <span class="n">m</span> <span class="o">-></span> <span class="n">m</span>
<span class="n">copy</span> <span class="n">i</span> <span class="n">stack</span> <span class="o">=</span> <span class="n">push1</span> <span class="p">(</span><span class="n">select</span> <span class="n">i</span> <span class="n">stack</span> <span class="o">::</span><span class="kt">Symbol</span><span class="p">)</span> <span class="n">stack</span>
</code></pre></div></div>
<p>Jak widać funkcja <code class="language-plaintext highlighter-rouge">copy :: Stack Symbol m => Index -> m -> m</code> jest w obu modułach,
jednak nie potrafię jej w prosty sposób uogólnić.</p>
<h2 id="przykład-użycia">Przykład użycia</h2>
<p>Jako przykład użycia fragment modułu <code class="language-plaintext highlighter-rouge">HelVM.HelCam.Machines.ETA.Evaluator</code>:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">class</span> <span class="kt">Evaluator</span> <span class="n">r</span> <span class="kr">where</span>
<span class="n">next</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="kt">Symbol</span> <span class="n">m</span> <span class="o">=></span> <span class="kt">InstructionUnit</span> <span class="o">-></span> <span class="n">m</span> <span class="o">-></span> <span class="n">r</span>
<span class="n">next</span> <span class="n">iu</span> <span class="n">s</span> <span class="o">=</span> <span class="n">doInstruction</span> <span class="n">t</span> <span class="n">iu'</span> <span class="n">s</span> <span class="kr">where</span> <span class="p">(</span><span class="n">t</span><span class="p">,</span> <span class="n">iu'</span><span class="p">)</span> <span class="o">=</span> <span class="n">nextIU</span> <span class="n">iu</span>
<span class="n">doInstruction</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="kt">Symbol</span> <span class="n">m</span> <span class="o">=></span> <span class="kt">Maybe</span> <span class="kt">Token</span> <span class="o">-></span> <span class="kt">InstructionUnit</span> <span class="o">-></span> <span class="n">m</span> <span class="o">-></span> <span class="n">r</span>
<span class="c1">-- IO instructions</span>
<span class="n">doInstruction</span> <span class="p">(</span><span class="kt">Just</span> <span class="kt">O</span><span class="p">)</span> <span class="n">iu</span> <span class="n">s</span> <span class="o">=</span> <span class="n">doOutputChar</span> <span class="n">iu</span> <span class="n">s</span>
<span class="n">doInstruction</span> <span class="p">(</span><span class="kt">Just</span> <span class="kt">I</span><span class="p">)</span> <span class="n">iu</span> <span class="n">s</span> <span class="o">=</span> <span class="n">doInputChar</span> <span class="n">iu</span> <span class="n">s</span>
<span class="c1">-- Stack instructions</span>
<span class="n">doInstruction</span> <span class="p">(</span><span class="kt">Just</span> <span class="kt">N</span><span class="p">)</span> <span class="n">iu</span> <span class="n">s</span> <span class="o">=</span> <span class="n">next</span> <span class="n">iu'</span> <span class="p">(</span><span class="n">push1</span> <span class="p">(</span><span class="n">symbol</span><span class="o">::</span><span class="kt">Symbol</span><span class="p">)</span> <span class="n">s</span><span class="p">)</span> <span class="kr">where</span> <span class="p">(</span><span class="n">symbol</span><span class="p">,</span> <span class="n">iu'</span><span class="p">)</span> <span class="o">=</span> <span class="n">parseNumber</span> <span class="n">iu</span>
<span class="n">doInstruction</span> <span class="p">(</span><span class="kt">Just</span> <span class="kt">H</span><span class="p">)</span> <span class="n">iu</span> <span class="n">s</span> <span class="o">=</span> <span class="n">next</span> <span class="n">iu</span> <span class="o">$</span> <span class="n">halibut</span> <span class="n">s</span>
<span class="c1">-- Arithmetic</span>
<span class="n">doInstruction</span> <span class="p">(</span><span class="kt">Just</span> <span class="kt">S</span><span class="p">)</span> <span class="n">iu</span> <span class="n">s</span> <span class="o">=</span> <span class="n">next</span> <span class="n">iu</span> <span class="o">$</span> <span class="n">sub</span> <span class="n">s</span>
<span class="n">doInstruction</span> <span class="p">(</span><span class="kt">Just</span> <span class="kt">E</span><span class="p">)</span> <span class="n">iu</span> <span class="n">s</span> <span class="o">=</span> <span class="n">next</span> <span class="n">iu</span> <span class="o">$</span> <span class="kt">Stack</span><span class="o">.</span><span class="n">divMod</span> <span class="n">s</span>
<span class="c1">-- Control</span>
<span class="n">doInstruction</span> <span class="p">(</span><span class="kt">Just</span> <span class="kt">R</span><span class="p">)</span> <span class="n">iu</span> <span class="n">s</span> <span class="o">=</span> <span class="n">next</span> <span class="n">iu</span> <span class="n">s</span>
<span class="n">doInstruction</span> <span class="p">(</span><span class="kt">Just</span> <span class="kt">A</span><span class="p">)</span> <span class="n">iu</span><span class="o">@</span><span class="p">(</span><span class="kt">IU</span> <span class="n">il</span> <span class="n">ic</span><span class="p">)</span> <span class="n">s</span> <span class="o">=</span> <span class="n">next</span> <span class="n">iu</span> <span class="p">(</span><span class="n">push1</span> <span class="p">(</span><span class="n">nextLabel</span> <span class="n">il</span> <span class="n">ic</span><span class="p">)</span> <span class="n">s</span><span class="p">)</span>
<span class="n">doInstruction</span> <span class="p">(</span><span class="kt">Just</span> <span class="kt">T</span><span class="p">)</span> <span class="n">iu</span><span class="o">@</span><span class="p">(</span><span class="kt">IU</span> <span class="n">il</span> <span class="kr">_</span> <span class="p">)</span> <span class="n">s</span> <span class="o">=</span> <span class="n">transfer</span> <span class="o">$</span> <span class="n">pop2</span> <span class="n">s</span> <span class="kr">where</span>
<span class="n">transfer</span> <span class="p">(</span><span class="kr">_</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">s'</span><span class="p">)</span> <span class="o">=</span> <span class="n">next</span> <span class="n">iu</span> <span class="n">s'</span>
<span class="n">transfer</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="kr">_</span><span class="p">,</span> <span class="kr">_</span> <span class="p">)</span> <span class="o">=</span> <span class="n">doEnd</span>
<span class="n">transfer</span> <span class="p">(</span><span class="n">l</span><span class="p">,</span> <span class="kr">_</span><span class="p">,</span> <span class="n">s'</span><span class="p">)</span> <span class="o">=</span> <span class="n">next</span> <span class="p">(</span><span class="kt">IU</span> <span class="n">il</span> <span class="o">$</span> <span class="n">findAddress</span> <span class="n">il</span> <span class="n">l</span><span class="p">)</span> <span class="n">s'</span>
<span class="n">doInstruction</span> <span class="kt">Nothing</span> <span class="kr">_</span> <span class="kr">_</span> <span class="o">=</span> <span class="n">doEnd</span>
<span class="c1">----</span>
<span class="n">doEnd</span> <span class="o">::</span> <span class="n">r</span>
<span class="n">doOutputChar</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="kt">Symbol</span> <span class="n">m</span> <span class="o">=></span> <span class="kt">InstructionUnit</span> <span class="o">-></span> <span class="n">m</span> <span class="o">-></span> <span class="n">r</span>
<span class="n">doInputChar</span> <span class="o">::</span> <span class="kt">Stack</span> <span class="kt">Symbol</span> <span class="n">m</span> <span class="o">=></span> <span class="kt">InstructionUnit</span> <span class="o">-></span> <span class="n">m</span> <span class="o">-></span> <span class="n">r</span>
</code></pre></div></div>
<p>Cel został osiągnięty.
Struktura stosu została schowana za abstrakcją klasy typu <code class="language-plaintext highlighter-rouge">Stack</code>.
Teraz można swobodnie wymieniać implementację między listą a sekwencją.</p>
<h2 id="podsumowanie">Podsumowanie</h2>
<p>Mimo że cel został osiągnięty,
kod jednak nie wyszedł tak dobry jak chciałem.
Może to pora na użycie <em><a href="https://wiki.haskell.org/GHC/Type_families">Rodzin typów</a></em>?</p>
<p>Kod interpretera <strong><a href="/projects/helma">HelMA</a></strong> po zmianach znajduje się na <a href="https://github.com/helvm/helma/tree/v0.6.4.0">githubie</a>.</p>TheKamilAdamPo hermetyzacji i abstrakcji RAMu dla interpretera HelMA pora na stos. Stos, zwłaszcza stos arytmetyczny, jest strukturą używaną w wielu interpreterach jezyków ezoterycznych. Więc warto wydzielić tą abstrakcję do osobnego modułu.Abstrakcja w Haskellu, czyli klasy typów2021-03-03T00:00:00+01:002021-03-03T00:00:00+01:00http://writeonly.pl/haskell-eta/abstraction<p>Po <a href="/encapsulation">Hermetyzacji</a> pora na Abstrakcje.
Abstrakcja ma w programowaniu wiele znaczeń.
Jednak w tym artykule będzie mi chodzić o abstrakcję spotykaną w <a href="/tags/oop">OOP</a>,
czyli <a href="/tags/interface">interfejsy</a> (w <strong><a href="/langs/java">Javie</a></strong>),
<a href="/tags/trait">traity</a> (w <strong><a href="/langs/scala">Scali</a></strong>) czy klasy czysto abstrakcyjne (w C++).
Czy <strong><a href="/langs/haskell">Haskell</a></strong> ma odpowiednik interfejsów/traitów?
Tak są to <a href="/tags/type-class">klasy typów</a> (ang. <em>Type Classy</em>).
Dzięki nim możemy wybierać implementację podczas działania programu.</p>
<h2 id="abstrakcja-i-klasy-typów">Abstrakcja i klasy typów</h2>
<p>Spójrzmy na moduł <code class="language-plaintext highlighter-rouge">HelVM.HelCam.Common.RAM</code>,
dla czytelności podzielony na trzy listingi.</p>
<p>Najpierw deklaracje i importy:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">{-# Language FlexibleInstances #-}</span>
<span class="cp">{-# Language MultiParamTypeClasses #-}</span>
<span class="cp">{-# Language AllowAmbiguousTypes #-}</span>
<span class="kr">module</span> <span class="nn">HelVM.HelCam.Common.RAM</span> <span class="p">(</span>
<span class="kt">RAM</span><span class="p">,</span>
<span class="kt">HelVM</span><span class="o">.</span><span class="kt">HelCam</span><span class="o">.</span><span class="kt">Common</span><span class="o">.</span><span class="kt">RAM</span><span class="o">.</span><span class="nf">empty</span><span class="p">,</span>
<span class="kt">HelVM</span><span class="o">.</span><span class="kt">HelCam</span><span class="o">.</span><span class="kt">Common</span><span class="o">.</span><span class="kt">RAM</span><span class="o">.</span><span class="nf">fromList</span><span class="p">,</span>
<span class="nf">load</span><span class="p">,</span>
<span class="nf">store</span>
<span class="p">)</span> <span class="kr">where</span>
<span class="kr">import</span> <span class="nn">Data.Default</span>
<span class="kr">import</span> <span class="nn">Data.IntMap</span> <span class="k">as</span> <span class="n">IntMap</span>
<span class="kr">import</span> <span class="nn">Data.Sequence</span> <span class="k">as</span> <span class="n">Seq</span>
<span class="kr">type</span> <span class="kt">Address</span> <span class="o">=</span> <span class="kt">Int</span>
</code></pre></div></div>
<p>Na początku pliku deklarujemy,
które rozszerzenia kompilatora potrzebujemy.
Następnie między w nawiasie przed <code class="language-plaintext highlighter-rouge">where</code> określamy,
które funkcje będą publiczne.</p>
<h3 id="abstrakcja-oparta-na-klasie-typu">Abstrakcja oparta na klasie typu</h3>
<p>Teraz abstrakcja:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">load</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Integral</span> <span class="n">a</span><span class="p">,</span> <span class="kt">Default</span> <span class="n">s</span><span class="p">,</span> <span class="kt">RAM</span> <span class="n">s</span> <span class="n">m</span><span class="p">)</span> <span class="o">=></span> <span class="n">m</span> <span class="o">-></span> <span class="n">a</span> <span class="o">-></span> <span class="n">s</span>
<span class="n">load</span> <span class="n">memory</span> <span class="n">address</span> <span class="o">=</span> <span class="n">index'</span> <span class="n">memory</span> <span class="p">(</span><span class="n">fromIntegral</span> <span class="n">address</span><span class="p">)</span> <span class="o">?:</span> <span class="n">def</span>
<span class="n">store</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Integral</span> <span class="n">a</span><span class="p">,</span> <span class="kt">Default</span> <span class="n">s</span><span class="p">,</span> <span class="kt">RAM</span> <span class="n">s</span> <span class="n">m</span><span class="p">)</span> <span class="o">=></span> <span class="n">a</span> <span class="o">-></span> <span class="n">s</span> <span class="o">-></span> <span class="n">m</span> <span class="o">-></span> <span class="n">m</span>
<span class="n">store</span> <span class="n">address</span> <span class="o">=</span> <span class="n">insert'</span> <span class="p">(</span><span class="n">fromIntegral</span> <span class="n">address</span><span class="p">)</span>
<span class="kr">class</span> <span class="p">(</span><span class="kt">Default</span> <span class="n">s</span><span class="p">,</span> <span class="kt">Semigroup</span> <span class="n">m</span><span class="p">)</span> <span class="o">=></span> <span class="kt">RAM</span> <span class="n">s</span> <span class="n">m</span> <span class="kr">where</span>
<span class="n">fromList</span> <span class="o">::</span> <span class="p">[</span><span class="n">s</span><span class="p">]</span> <span class="o">-></span> <span class="n">m</span>
<span class="n">empty</span> <span class="o">::</span> <span class="n">m</span>
<span class="n">index'</span> <span class="o">::</span> <span class="n">m</span> <span class="o">-></span> <span class="kt">Address</span> <span class="o">-></span> <span class="kt">Maybe</span> <span class="n">s</span>
<span class="n">insert'</span> <span class="o">::</span> <span class="kt">Address</span> <span class="o">-></span> <span class="n">s</span> <span class="o">-></span> <span class="n">m</span> <span class="o">-></span> <span class="n">m</span>
</code></pre></div></div>
<p>Funkcje <code class="language-plaintext highlighter-rouge">load</code> i <code class="language-plaintext highlighter-rouge">store</code> są tutaj normalnymi funkcjami,
jednak w OOP ich odpowiednikiem byłyby metody finalne w klasie bazowej.
Klasa typu <code class="language-plaintext highlighter-rouge">RAM</code> posiada cztery <em>metody abstrakcyjne</em>.
Ale chyba najważniejszą rzeczą jest to,
że jest to klasa typów zdefiniowana dla dwóch parametrów.
Pierwszy <code class="language-plaintext highlighter-rouge">s</code> to będzie <code class="language-plaintext highlighter-rouge">Symbol</code>,
a drugi <code class="language-plaintext highlighter-rouge">m</code> konkretna struktura będąca pamięcią.</p>
<h3 id="implementacja">Implementacja</h3>
<p>Ostatnia część pliku to implementacja:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">instance</span> <span class="p">(</span><span class="kt">Default</span> <span class="n">s</span><span class="p">)</span> <span class="o">=></span> <span class="kt">RAM</span> <span class="n">s</span> <span class="p">[</span><span class="n">s</span><span class="p">]</span> <span class="kr">where</span>
<span class="n">fromList</span> <span class="o">=</span> <span class="n">id</span>
<span class="n">empty</span> <span class="o">=</span> <span class="kt">[]</span>
<span class="n">index'</span> <span class="o">=</span> <span class="p">(</span><span class="o">!!?</span><span class="p">)</span>
<span class="n">insert'</span> <span class="mi">0</span> <span class="n">symbol</span> <span class="kt">[]</span> <span class="o">=</span> <span class="p">[</span><span class="n">symbol</span><span class="p">]</span>
<span class="n">insert'</span> <span class="mi">0</span> <span class="n">symbol</span> <span class="p">(</span><span class="kr">_</span><span class="o">:</span><span class="n">xs</span><span class="p">)</span> <span class="o">=</span> <span class="n">symbol</span> <span class="o">:</span> <span class="n">xs</span>
<span class="n">insert'</span> <span class="n">address</span> <span class="n">symbol</span> <span class="kt">[]</span> <span class="o">=</span> <span class="n">def</span> <span class="o">:</span> <span class="n">insert'</span> <span class="p">(</span><span class="n">address</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="n">symbol</span> <span class="kt">[]</span>
<span class="n">insert'</span> <span class="n">address</span> <span class="n">symbol</span> <span class="p">(</span><span class="n">x</span><span class="o">:</span><span class="n">xs</span><span class="p">)</span> <span class="o">=</span> <span class="n">x</span> <span class="o">:</span> <span class="n">insert'</span> <span class="p">(</span><span class="n">address</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="n">symbol</span> <span class="n">xs</span>
<span class="kr">instance</span> <span class="p">(</span><span class="kt">Default</span> <span class="n">s</span><span class="p">)</span> <span class="o">=></span> <span class="kt">RAM</span> <span class="n">s</span> <span class="p">(</span><span class="kt">Seq</span> <span class="n">s</span><span class="p">)</span> <span class="kr">where</span>
<span class="n">fromList</span> <span class="o">=</span> <span class="kt">Seq</span><span class="o">.</span><span class="n">fromList</span>
<span class="n">empty</span> <span class="o">=</span> <span class="kt">Seq</span><span class="o">.</span><span class="n">fromList</span> <span class="kt">[]</span>
<span class="n">index'</span> <span class="o">=</span> <span class="p">(</span><span class="kt">Seq</span><span class="o">.!?</span><span class="p">)</span>
<span class="n">insert'</span> <span class="n">address</span> <span class="n">symbol</span> <span class="n">memory</span> <span class="o">=</span> <span class="n">insert''</span> <span class="p">(</span><span class="kt">Seq</span><span class="o">.</span><span class="n">length</span> <span class="n">memory</span><span class="p">)</span> <span class="kr">where</span>
<span class="n">insert''</span> <span class="n">l</span>
<span class="o">|</span> <span class="n">address</span> <span class="o"><</span> <span class="n">l</span> <span class="o">=</span> <span class="kt">Seq</span><span class="o">.</span><span class="n">update</span> <span class="n">address</span> <span class="n">symbol</span> <span class="n">memory</span>
<span class="o">|</span> <span class="n">otherwise</span> <span class="o">=</span> <span class="n">memory</span> <span class="o"><></span> <span class="kt">Seq</span><span class="o">.</span><span class="n">replicate</span> <span class="p">(</span><span class="n">address</span> <span class="o">-</span> <span class="n">l</span><span class="p">)</span> <span class="n">def</span> <span class="o">|></span> <span class="n">symbol</span>
<span class="kr">instance</span> <span class="p">(</span><span class="kt">Default</span> <span class="n">s</span><span class="p">)</span> <span class="o">=></span> <span class="kt">RAM</span> <span class="n">s</span> <span class="p">(</span><span class="kt">IntMap</span> <span class="n">s</span><span class="p">)</span> <span class="kr">where</span>
<span class="n">fromList</span> <span class="n">list</span> <span class="o">=</span> <span class="kt">IntMap</span><span class="o">.</span><span class="n">fromList</span> <span class="o">$</span> <span class="kt">Prelude</span><span class="o">.</span><span class="n">zip</span> <span class="p">[</span><span class="mi">0</span><span class="o">..</span><span class="p">]</span> <span class="n">list</span>
<span class="n">empty</span> <span class="o">=</span> <span class="kt">IntMap</span><span class="o">.</span><span class="n">empty</span>
<span class="n">index'</span> <span class="o">=</span> <span class="p">(</span><span class="kt">IntMap</span><span class="o">.!?</span><span class="p">)</span>
<span class="n">insert'</span> <span class="o">=</span> <span class="kt">IntMap</span><span class="o">.</span><span class="n">insert</span>
</code></pre></div></div>
<p>Mamy trzy implementacje klasy typu dla trzech różnych struktur.
W przypadku języka OOP byłyby to trzy klasy konkretne opakowujące struktury.
W <strong><a href="/langs/haskell">Haskellu</a></strong> nie ma potrzeby opakowywania struktur przy definiowaniu nowych zachowań.</p>
<h2 id="użycie-klasy-typu">Użycie klasy typu</h2>
<p>Nowy kod klasy typu <code class="language-plaintext highlighter-rouge">Evaluator</code> dla eso języka <strong><a href="/eso/subleq">SubLeq</a></strong> wygląda następująco:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">batchSimpleEval</span> <span class="o">::</span> <span class="kt">Source</span> <span class="o">-></span> <span class="kt">Output</span>
<span class="n">batchSimpleEval</span> <span class="o">=</span> <span class="n">flip</span> <span class="n">simpleEval</span> <span class="n">emptyInput</span>
<span class="n">batchSimpleEvalIL</span> <span class="o">::</span> <span class="kt">SymbolList</span> <span class="o">-></span> <span class="kt">Output</span>
<span class="n">batchSimpleEvalIL</span> <span class="o">=</span> <span class="n">flip</span> <span class="n">simpleEvalIL</span> <span class="n">emptyInput</span>
<span class="n">simpleEval</span> <span class="o">::</span> <span class="kt">Evaluator</span> <span class="n">r</span> <span class="o">=></span> <span class="kt">Source</span> <span class="o">-></span> <span class="n">r</span>
<span class="n">simpleEval</span> <span class="n">source</span> <span class="o">=</span> <span class="n">eval</span> <span class="n">source</span> <span class="n">defaultRAMType</span>
<span class="n">simpleEvalIL</span> <span class="o">::</span> <span class="kt">Evaluator</span> <span class="n">r</span> <span class="o">=></span> <span class="kt">SymbolList</span> <span class="o">-></span> <span class="n">r</span>
<span class="n">simpleEvalIL</span> <span class="n">il</span> <span class="o">=</span> <span class="n">evalIL</span> <span class="n">il</span> <span class="n">defaultRAMType</span>
<span class="n">eval</span> <span class="o">::</span> <span class="kt">Evaluator</span> <span class="n">r</span> <span class="o">=></span> <span class="kt">Source</span> <span class="o">-></span> <span class="kt">RAMType</span> <span class="o">-></span> <span class="n">r</span>
<span class="n">eval</span> <span class="n">source</span> <span class="o">=</span> <span class="n">evalIL</span> <span class="o">$</span> <span class="n">tokenize</span> <span class="n">source</span>
<span class="n">evalIL</span> <span class="o">::</span> <span class="kt">Evaluator</span> <span class="n">r</span> <span class="o">=></span> <span class="kt">SymbolList</span> <span class="o">-></span> <span class="kt">RAMType</span> <span class="o">-></span> <span class="n">r</span>
<span class="n">evalIL</span> <span class="n">il</span> <span class="kt">ListRAMType</span> <span class="o">=</span> <span class="n">start</span> <span class="p">(</span><span class="kt">RAM</span><span class="o">.</span><span class="n">fromList</span> <span class="n">il</span><span class="o">::</span><span class="kt">SymbolList</span><span class="p">)</span>
<span class="n">evalIL</span> <span class="n">il</span> <span class="kt">SeqRAMType</span> <span class="o">=</span> <span class="n">start</span> <span class="p">(</span><span class="kt">RAM</span><span class="o">.</span><span class="n">fromList</span> <span class="n">il</span><span class="o">::</span><span class="kt">Seq</span> <span class="kt">Symbol</span><span class="p">)</span>
<span class="n">evalIL</span> <span class="n">il</span> <span class="kt">IntMapRAMType</span> <span class="o">=</span> <span class="n">start</span> <span class="p">(</span><span class="kt">RAM</span><span class="o">.</span><span class="n">fromList</span> <span class="n">il</span><span class="o">::</span><span class="kt">IntMap</span> <span class="kt">Symbol</span><span class="p">)</span>
<span class="n">start</span> <span class="o">::</span><span class="p">(</span><span class="kt">RAM</span> <span class="kt">Symbol</span> <span class="n">m</span><span class="p">,</span> <span class="kt">Evaluator</span> <span class="n">r</span><span class="p">)</span> <span class="o">=></span> <span class="n">m</span> <span class="o">-></span> <span class="n">r</span>
<span class="n">start</span> <span class="o">=</span> <span class="n">doInstruction</span> <span class="mi">0</span>
<span class="kr">class</span> <span class="kt">Evaluator</span> <span class="n">r</span> <span class="kr">where</span>
<span class="n">doInstruction</span> <span class="o">::</span> <span class="kt">RAM</span> <span class="kt">Symbol</span> <span class="n">m</span> <span class="o">=></span> <span class="kt">Symbol</span> <span class="o">-></span> <span class="n">m</span> <span class="o">-></span> <span class="n">r</span>
<span class="n">doInstruction</span> <span class="n">ic</span> <span class="n">memory</span>
<span class="o">|</span> <span class="n">ic</span> <span class="o"><</span> <span class="mi">0</span> <span class="o">=</span> <span class="n">doEnd</span>
<span class="o">|</span> <span class="n">src</span> <span class="o"><</span> <span class="mi">0</span> <span class="o">=</span> <span class="n">doInputChar</span> <span class="n">dst</span> <span class="n">ic</span> <span class="n">memory</span>
<span class="o">|</span> <span class="n">dst</span> <span class="o"><</span> <span class="mi">0</span> <span class="o">=</span> <span class="n">doOutputChar</span> <span class="n">src</span> <span class="n">ic</span> <span class="n">memory</span>
<span class="o">|</span> <span class="n">otherwise</span> <span class="o">=</span> <span class="n">doInstruction</span> <span class="n">ic'</span> <span class="o">$</span> <span class="n">store</span> <span class="n">dst</span> <span class="n">diff</span> <span class="n">memory</span>
<span class="kr">where</span>
<span class="n">src</span> <span class="o">=</span> <span class="n">load</span> <span class="n">memory</span> <span class="n">ic</span>
<span class="n">dst</span> <span class="o">=</span> <span class="n">load</span> <span class="n">memory</span> <span class="o">$</span> <span class="n">ic</span> <span class="o">+</span> <span class="mi">1</span>
<span class="n">diff</span> <span class="o">=</span> <span class="n">load</span> <span class="n">memory</span> <span class="n">dst</span> <span class="o">-</span> <span class="n">load</span> <span class="n">memory</span> <span class="n">src</span> <span class="o">::</span> <span class="kt">Symbol</span>
<span class="n">ic'</span>
<span class="o">|</span> <span class="n">diff</span> <span class="o"><=</span> <span class="mi">0</span> <span class="o">=</span> <span class="p">(</span><span class="n">load</span> <span class="n">memory</span> <span class="o">$</span> <span class="n">ic</span> <span class="o">+</span> <span class="mi">2</span><span class="p">)</span> <span class="o">::</span> <span class="kt">Symbol</span>
<span class="o">|</span> <span class="n">otherwise</span> <span class="o">=</span> <span class="n">ic</span> <span class="o">+</span> <span class="mi">3</span>
<span class="n">doEnd</span> <span class="o">::</span> <span class="n">r</span>
<span class="n">doInputChar</span> <span class="o">::</span> <span class="kt">RAM</span> <span class="kt">Symbol</span> <span class="n">m</span> <span class="o">=></span> <span class="kt">Symbol</span> <span class="o">-></span> <span class="kt">Symbol</span> <span class="o">-></span> <span class="n">m</span> <span class="o">-></span> <span class="n">r</span>
<span class="n">doOutputChar</span> <span class="o">::</span> <span class="kt">RAM</span> <span class="kt">Symbol</span> <span class="n">m</span> <span class="o">=></span> <span class="kt">Symbol</span> <span class="o">-></span> <span class="kt">Symbol</span> <span class="o">-></span> <span class="n">m</span> <span class="o">-></span> <span class="n">r</span>
</code></pre></div></div>
<p>Czy w powyższym kodzie jest jeszcze hermatyzacja?
Czy nie zgubiliśmy jej gdzieś?
Uważam, że nie.
Dalej mamy dostęp do <code class="language-plaintext highlighter-rouge">RAM</code> tylko za pomocą funkcji <code class="language-plaintext highlighter-rouge">load</code> i <code class="language-plaintext highlighter-rouge">store</code>.
Dodatkowo możemy wybierać implementację na etapie działania programu,
a nie na etapie kompilacji.</p>
<p>Ponieważ odkryłem zapis <code class="language-plaintext highlighter-rouge">instance TypeClassa1 t => TypeClassa2 t where</code>,
teraz możemy przy okazji przepisać implementacje <a href="/tags/type-class">klasy typu</a> <code class="language-plaintext highlighter-rouge">Evaluator</code>:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">instance</span> <span class="p">(</span><span class="kt">WrapperIO</span> <span class="n">m</span><span class="p">)</span> <span class="o">=></span> <span class="kt">Evaluator</span> <span class="p">(</span><span class="n">m</span> <span class="nb">()</span><span class="p">)</span> <span class="kr">where</span>
<span class="n">doEnd</span> <span class="o">=</span> <span class="n">pass</span>
<span class="n">doInputChar</span> <span class="n">address</span> <span class="n">ic</span> <span class="n">memory</span> <span class="o">=</span> <span class="kr">do</span>
<span class="n">value</span> <span class="o"><-</span> <span class="n">wGetInt</span>
<span class="n">doInstruction</span> <span class="p">(</span><span class="n">ic</span><span class="o">+</span><span class="mi">3</span><span class="p">)</span> <span class="o">$</span> <span class="n">store</span> <span class="n">address</span> <span class="n">value</span> <span class="n">memory</span>
<span class="n">doOutputChar</span> <span class="n">address</span> <span class="n">ic</span> <span class="n">memory</span> <span class="o">=</span> <span class="kr">do</span>
<span class="n">wPutInt</span> <span class="p">(</span><span class="n">load</span> <span class="n">memory</span> <span class="n">address</span> <span class="o">::</span> <span class="kt">Symbol</span><span class="p">)</span>
<span class="n">doInstruction</span> <span class="p">(</span><span class="n">ic</span><span class="o">+</span><span class="mi">3</span><span class="p">)</span> <span class="n">memory</span>
</code></pre></div></div>
<p>Potrzebujemy jeszcze [kostruktury] (enuma) dającą możliwość wyboru implementacji:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">module</span> <span class="nn">HelVM.HelCam.Common.Types.RAMType</span> <span class="kr">where</span>
<span class="kr">data</span> <span class="kt">RAMType</span> <span class="o">=</span> <span class="kt">ListRAMType</span> <span class="o">|</span> <span class="kt">SeqRAMType</span> <span class="o">|</span> <span class="kt">IntMapRAMType</span> <span class="kr">deriving</span> <span class="p">(</span><span class="kt">Eq</span><span class="p">,</span> <span class="kt">Read</span><span class="p">,</span> <span class="kt">Show</span><span class="p">)</span>
<span class="n">ramTypes</span> <span class="o">::</span> <span class="p">[</span><span class="kt">RAMType</span><span class="p">]</span>
<span class="n">ramTypes</span> <span class="o">=</span> <span class="p">[</span><span class="kt">ListRAMType</span><span class="p">,</span> <span class="kt">SeqRAMType</span><span class="p">,</span> <span class="kt">IntMapRAMType</span><span class="p">]</span>
<span class="n">defaultRAMType</span> <span class="o">::</span> <span class="kt">RAMType</span>
<span class="n">defaultRAMType</span> <span class="o">=</span> <span class="kt">IntMapRAMType</span>
<span class="n">parseRAMType</span> <span class="o">::</span> <span class="kt">String</span> <span class="o">-></span> <span class="kt">RAMType</span>
<span class="n">parseRAMType</span> <span class="n">raw</span> <span class="o">=</span> <span class="n">valid</span> <span class="o">$</span> <span class="n">readMaybe</span> <span class="n">raw</span> <span class="kr">where</span>
<span class="n">valid</span> <span class="p">(</span><span class="kt">Just</span> <span class="n">value</span><span class="p">)</span> <span class="o">=</span> <span class="n">value</span>
<span class="n">valid</span> <span class="kt">Nothing</span> <span class="o">=</span> <span class="n">error</span> <span class="o">$</span> <span class="s">"RAMType '"</span> <span class="o"><></span> <span class="n">toText</span> <span class="n">raw</span> <span class="o"><></span> <span class="s">"' is not valid RAMType. Valid ramTypes are : "</span> <span class="o"><></span> <span class="n">show</span> <span class="n">ramTypes</span>
</code></pre></div></div>
<p>Funkcji <code class="language-plaintext highlighter-rouge">parseRAMType</code> potrzebujemy,
ponieważ <code class="language-plaintext highlighter-rouge">RAMType</code> będzie wybierany z poziomu linii poleceń</p>
<h2 id="podsumowanie">Podsumowanie</h2>
<p>Czy rozwiązanie oparte na type klasach jest dobre?
Jest na pewno proste.
Nie musieliśmy definiować żadnych dodatkowych typów ani za pomocą <code class="language-plaintext highlighter-rouge">newtype</code> ani tym bardziej za pomocą <code class="language-plaintext highlighter-rouge">data</code>.</p>
<p>Czy jest jakaś alternatywa?
Tak,
są to <em><a href="https://wiki.haskell.org/GHC/Type_families">rodziny typów</a></em> (ang. <em>Type Families</em>).</p>
<p>Czy jest to rozwiązanie najlepsze? Tego nie wiem.
Podobno pozwalają uzyskać lepsze komunikaty o błędach,
Ja jednak chciałem ograniczyć do minimum tworzenie własnych typów.</p>
<p>Kod interpretera <strong><a href="/projects/helma">Helcam</a></strong> po zmianach znajduje się na <a href="https://github.com/helvm/helma/tree/v0.6.3.0">githubie</a>.</p>TheKamilAdamPo Hermetyzacji pora na Abstrakcje. Abstrakcja ma w programowaniu wiele znaczeń. Jednak w tym artykule będzie mi chodzić o abstrakcję spotykaną w OOP, czyli interfejsy (w Javie), traity (w Scali) czy klasy czysto abstrakcyjne (w C++). Czy Haskell ma odpowiednik interfejsów/traitów? Tak są to klasy typów (ang. Type Classy). Dzięki nim możemy wybierać implementację podczas działania programu.Hermetyzacja w Haskellu2021-02-03T00:00:00+01:002021-02-03T00:00:00+01:00http://writeonly.pl/haskell-eta/encapsulation<p>Hermetyzacja to cecha charakterystyczna obiektowych języków programowania.
Co jednak nie znaczy,
że hermetyzacja nie istnieje w <strong><a href="/langs/haskell">Haskellu</a></strong>.
Hermetyzację podobną do klas z języków OOP osiąga się w <strong><a href="/langs/haskell">Haskellu</a></strong> za pomocą modułów i jawnego eksportowania funkcji i typów.</p>
<p>Jako przykład przedstawię trzy implementacje modułu emulującego pamięć o dostępie swobodnym (RAM) dla interpreterów ezoterycznych języków programowania.
Moduły są użyte w interpreterach języków <strong><a href="/eso/subleq">SubLeq</a></strong> i <strong><a href="/eso/whitespace">WhiteSpace</a></strong>.</p>
<h2 id="implementacje">Implementacje</h2>
<p>Implementacje są przedstawione w takiej kolejnosci jak powstawały wraz z poznawaniem <strong><a href="/langs/haskell">Haskella</a></strong>.
Oparte są na Liście, Sekwencji i IntMapie.</p>
<h3 id="implementacja-oparta-na-liście">Implementacja oparta na liście</h3>
<p>Najpierw implementacja oparta na liście.
Była to dla mnie naturalna implementacja,
ponieważ list jest domyślną (jedyną dostępną w standardowej bibliotece) kolekcją danych w <strong><a href="/langs/haskell">Haskellu</a></strong>.</p>
<p>Najpierw eksportujemy typ <code class="language-plaintext highlighter-rouge">RAM</code> oraz nazwy czterech funkcji pracujących na tym typie.
Dwa konstruktory oraz <em>mutatory</em>.</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">{-# Language GeneralizedNewtypeDeriving #-}</span>
<span class="kr">module</span> <span class="nn">HelVM.HelCam.Common.RAM.ListRAM</span> <span class="p">(</span>
<span class="kt">RAM</span><span class="p">,</span>
<span class="nf">empty</span><span class="p">,</span>
<span class="nf">fromList</span><span class="p">,</span>
<span class="nf">load</span><span class="p">,</span>
<span class="nf">store</span>
<span class="p">)</span> <span class="kr">where</span>
<span class="kr">import</span> <span class="nn">HelVM.HelCam.Common.Util</span>
<span class="kr">import</span> <span class="nn">Data.Default</span>
</code></pre></div></div>
<p>Ponieważ dla wszystkich implementacji ten kod jest prawie identyczny,
nie będę go już powtarzać.</p>
<p>Następnie definiujemy oba konstruktory:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">import</span> <span class="nn">Prelude</span> <span class="k">hiding</span> <span class="p">(</span><span class="nf">empty</span><span class="p">,</span> <span class="nf">fromList</span><span class="p">)</span>
<span class="kr">newtype</span> <span class="kt">RAM</span> <span class="n">s</span> <span class="o">=</span> <span class="kt">MakeRAM</span> <span class="p">[</span><span class="n">s</span><span class="p">]</span> <span class="kr">deriving</span> <span class="p">(</span><span class="kt">Foldable</span><span class="p">)</span>
<span class="kr">type</span> <span class="kt">DRAM</span> <span class="n">s</span> <span class="o">=</span> <span class="kt">D</span> <span class="p">(</span><span class="kt">RAM</span> <span class="n">s</span><span class="p">)</span>
<span class="c1">-- Constructors</span>
<span class="n">empty</span> <span class="o">::</span> <span class="kt">Default</span> <span class="n">s</span> <span class="o">=></span> <span class="kt">RAM</span> <span class="n">s</span>
<span class="n">empty</span> <span class="o">=</span> <span class="kt">MakeRAM</span> <span class="kt">[]</span>
<span class="n">fromList</span> <span class="o">::</span> <span class="kt">Default</span> <span class="n">s</span> <span class="o">=></span> <span class="p">[</span><span class="n">s</span><span class="p">]</span> <span class="o">-></span> <span class="kt">RAM</span> <span class="n">s</span>
<span class="n">fromList</span> <span class="o">=</span> <span class="kt">MakeRAM</span>
</code></pre></div></div>
<p>Funkcja <code class="language-plaintext highlighter-rouge">empty</code> tworzy pusty <code class="language-plaintext highlighter-rouge">RAM</code> a <code class="language-plaintext highlighter-rouge">fromList</code> - ram wstępnie załadowany danymi.</p>
<p>Następnie definiujemy dwa <em>mutatory</em>,
które niestety będziemy musieli przekopiowywać między implementacjami
(Do poprawy w przyszłości):</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- Mutators</span>
<span class="n">load</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Integral</span> <span class="n">a</span><span class="p">,</span> <span class="kt">Default</span> <span class="n">s</span><span class="p">)</span> <span class="o">=></span> <span class="kt">RAM</span> <span class="n">s</span> <span class="o">-></span> <span class="n">a</span> <span class="o">-></span> <span class="n">s</span>
<span class="n">load</span> <span class="p">(</span><span class="kt">MakeRAM</span> <span class="n">m</span><span class="p">)</span> <span class="n">address</span> <span class="o">=</span> <span class="n">index'</span> <span class="n">m</span> <span class="p">(</span><span class="n">fromIntegral</span> <span class="n">address</span><span class="p">)</span> <span class="o">?:</span> <span class="n">def</span>
<span class="n">store</span> <span class="o">::</span> <span class="p">(</span><span class="kt">Integral</span> <span class="n">a</span><span class="p">,</span> <span class="kt">Default</span> <span class="n">s</span><span class="p">)</span> <span class="o">=></span> <span class="n">a</span> <span class="o">-></span> <span class="n">s</span> <span class="o">-></span> <span class="kt">DRAM</span> <span class="n">s</span>
<span class="n">store</span> <span class="n">address</span> <span class="n">symbol</span> <span class="p">(</span><span class="kt">MakeRAM</span> <span class="n">m</span><span class="p">)</span> <span class="o">=</span> <span class="kt">MakeRAM</span> <span class="o">$</span> <span class="n">insert'</span> <span class="p">(</span><span class="n">fromIntegral</span> <span class="n">address</span><span class="p">)</span> <span class="n">symbol</span> <span class="n">m</span>
</code></pre></div></div>
<p>I teraz niskopoziomowe funkcje,
wymagane przez <em>mutatory</em>,
zależne od implementacji</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- Private</span>
<span class="n">index'</span> <span class="o">::</span> <span class="p">[</span><span class="n">s</span><span class="p">]</span> <span class="o">-></span> <span class="kt">Int</span> <span class="o">-></span> <span class="kt">Maybe</span> <span class="n">s</span>
<span class="n">index'</span> <span class="o">=</span> <span class="p">(</span><span class="o">!!?</span><span class="p">)</span>
<span class="n">insert'</span> <span class="o">::</span> <span class="kt">Default</span> <span class="n">s</span> <span class="o">=></span> <span class="kt">Int</span> <span class="o">-></span> <span class="n">s</span> <span class="o">-></span> <span class="p">[</span><span class="n">s</span><span class="p">]</span> <span class="o">-></span> <span class="p">[</span><span class="n">s</span><span class="p">]</span>
<span class="n">insert'</span> <span class="mi">0</span> <span class="n">symbol</span> <span class="kt">[]</span> <span class="o">=</span> <span class="p">[</span><span class="n">symbol</span><span class="p">]</span>
<span class="n">insert'</span> <span class="mi">0</span> <span class="n">symbol</span> <span class="p">(</span><span class="kr">_</span><span class="o">:</span><span class="n">xs</span><span class="p">)</span> <span class="o">=</span> <span class="n">symbol</span> <span class="o">:</span> <span class="n">xs</span>
<span class="n">insert'</span> <span class="n">address</span> <span class="n">symbol</span> <span class="kt">[]</span> <span class="o">=</span> <span class="n">def</span> <span class="o">:</span> <span class="n">insert'</span> <span class="p">(</span><span class="n">address</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="n">symbol</span> <span class="kt">[]</span>
<span class="n">insert'</span> <span class="n">address</span> <span class="n">symbol</span> <span class="p">(</span><span class="n">x</span><span class="o">:</span><span class="n">xs</span><span class="p">)</span> <span class="o">=</span> <span class="n">x</span> <span class="o">:</span> <span class="n">insert'</span> <span class="p">(</span><span class="n">address</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="n">symbol</span> <span class="n">xs</span>
</code></pre></div></div>
<p>O ile odczyt elementów z listy jest prosty,
o tyle zapis elementu w liście wymaga trochę kodu.
Wymaga także przekopiowania fragmentu listy.
W przypadku dodania nowego elementu na koniec listy wymaga przekopiowania całej zawartości listy.
Czyli czas wstawiania to <code class="language-plaintext highlighter-rouge">O(n)</code>,
gdzie <code class="language-plaintext highlighter-rouge">n</code> to wartość adresu.
Nie jest to dobra implementacja,
ale jej zaletą jest to,
że nie zależy od zewnętrznych bibliotek.</p>
<h3 id="implementacja-oparta-na-sekwencji">Implementacja oparta na Sekwencji</h3>
<p>Importujemy sekwencje jako <code class="language-plaintext highlighter-rouge">Seq</code> oraz definiujemy typ i dwa konstruktory do niego:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">import</span> <span class="nn">Data.Sequence</span> <span class="k">as</span> <span class="n">Seq</span>
<span class="kr">newtype</span> <span class="kt">RAM</span> <span class="n">s</span> <span class="o">=</span> <span class="kt">MakeRAM</span> <span class="p">(</span><span class="kt">Seq</span> <span class="n">s</span><span class="p">)</span> <span class="kr">deriving</span> <span class="p">(</span><span class="kt">Foldable</span><span class="p">)</span>
<span class="kr">type</span> <span class="kt">DRAM</span> <span class="n">s</span> <span class="o">=</span> <span class="kt">D</span> <span class="p">(</span><span class="kt">RAM</span> <span class="n">s</span><span class="p">)</span>
<span class="c1">-- Constructors</span>
<span class="n">empty</span> <span class="o">::</span> <span class="kt">Default</span> <span class="n">s</span> <span class="o">=></span> <span class="kt">RAM</span> <span class="n">s</span>
<span class="n">empty</span> <span class="o">=</span> <span class="kt">MakeRAM</span> <span class="kt">Seq</span><span class="o">.</span><span class="n">empty</span>
<span class="n">fromList</span> <span class="o">::</span> <span class="kt">Default</span> <span class="n">s</span> <span class="o">=></span> <span class="p">[</span><span class="n">s</span><span class="p">]</span> <span class="o">-></span> <span class="kt">RAM</span> <span class="n">s</span>
<span class="n">fromList</span> <span class="o">=</span> <span class="kt">MakeRAM</span> <span class="o">.</span> <span class="kt">Seq</span><span class="o">.</span><span class="n">fromList</span>
</code></pre></div></div>
<p>Następnie przekopiowujemy kod,
który powinien być wspólny,
dwa <em>mutatory</em> <code class="language-plaintext highlighter-rouge">load</code> i <code class="language-plaintext highlighter-rouge">store</code>.
(Do poprawy w przyszłości)</p>
<p>Na koniec definiujemy funkcje zależne od implementacji:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- Private</span>
<span class="n">index'</span> <span class="o">::</span> <span class="kt">Seq</span> <span class="n">s</span> <span class="o">-></span> <span class="kt">Int</span> <span class="o">-></span> <span class="kt">Maybe</span> <span class="n">s</span>
<span class="n">index'</span> <span class="o">=</span> <span class="p">(</span><span class="o">!?</span><span class="p">)</span>
<span class="n">insert'</span> <span class="o">::</span> <span class="kt">Default</span> <span class="n">s</span> <span class="o">=></span> <span class="kt">Int</span> <span class="o">-></span> <span class="n">s</span> <span class="o">-></span> <span class="kt">Seq</span> <span class="n">s</span> <span class="o">-></span> <span class="kt">Seq</span> <span class="n">s</span>
<span class="n">insert'</span> <span class="n">address</span> <span class="n">symbol</span> <span class="n">m</span> <span class="o">=</span> <span class="n">insert''</span> <span class="p">(</span><span class="kt">Seq</span><span class="o">.</span><span class="n">length</span> <span class="n">m</span><span class="p">)</span> <span class="kr">where</span>
<span class="n">insert''</span> <span class="n">l</span>
<span class="o">|</span> <span class="n">address</span> <span class="o"><</span> <span class="n">l</span> <span class="o">=</span> <span class="kt">Seq</span><span class="o">.</span><span class="n">update</span> <span class="n">address</span> <span class="n">symbol</span> <span class="n">m</span>
<span class="o">|</span> <span class="n">otherwise</span> <span class="o">=</span> <span class="n">m</span> <span class="o"><></span> <span class="kt">Seq</span><span class="o">.</span><span class="n">replicate</span> <span class="p">(</span><span class="n">address</span> <span class="o">-</span> <span class="n">l</span><span class="p">)</span> <span class="n">def</span> <span class="o">|></span> <span class="n">symbol</span>
</code></pre></div></div>
<p>Także tutaj kod zapisu to pare linii.
Jednak tym razem nie musimy kopiować żadnych struktur.
Sekwencja zajmie się tym za nas.
Jednocześnie gwarantując wydajność stawiania nowego elementu na poziomie <code class="language-plaintext highlighter-rouge">O(log n)</code>.</p>
<h3 id="implementacja-oparta-na-mapie">Implementacja oparta na Mapie</h3>
<p>Nie będzie to jednak zwykła mapa a IntMapa dedykowana do adresowania jej typem <code class="language-plaintext highlighter-rouge">Int</code>.
Ograniczenie to wynika z tego,
że zarówno Lista,
jak i Sekwencja,
także są indeksowane typem <code class="language-plaintext highlighter-rouge">Int</code>.</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">import</span> <span class="nn">Data.IntMap</span> <span class="k">as</span> <span class="n">IntMap</span>
<span class="kr">newtype</span> <span class="kt">RAM</span> <span class="n">s</span> <span class="o">=</span> <span class="kt">MakeRAM</span> <span class="p">(</span><span class="kt">IntMap</span> <span class="n">s</span><span class="p">)</span> <span class="kr">deriving</span> <span class="p">(</span><span class="kt">Foldable</span><span class="p">)</span>
<span class="kr">type</span> <span class="kt">DRAM</span> <span class="n">s</span> <span class="o">=</span> <span class="kt">D</span> <span class="p">(</span><span class="kt">RAM</span> <span class="n">s</span><span class="p">)</span>
<span class="c1">-- Constructors</span>
<span class="n">empty</span> <span class="o">::</span> <span class="kt">Default</span> <span class="n">s</span> <span class="o">=></span> <span class="kt">RAM</span> <span class="n">s</span>
<span class="n">empty</span> <span class="o">=</span> <span class="kt">MakeRAM</span> <span class="kt">IntMap</span><span class="o">.</span><span class="n">empty</span>
<span class="n">fromList</span> <span class="o">::</span> <span class="kt">Default</span> <span class="n">s</span> <span class="o">=></span> <span class="p">[</span><span class="n">s</span><span class="p">]</span> <span class="o">-></span> <span class="kt">RAM</span> <span class="n">s</span>
<span class="n">fromList</span> <span class="n">list</span> <span class="o">=</span> <span class="kt">MakeRAM</span> <span class="o">$</span> <span class="kt">IntMap</span><span class="o">.</span><span class="n">fromList</span> <span class="o">$</span> <span class="n">zip</span> <span class="p">[</span><span class="mi">0</span><span class="o">..</span><span class="p">]</span> <span class="n">list</span>
</code></pre></div></div>
<p>Dla funkcji <code class="language-plaintext highlighter-rouge">fromList</code> trzeba napisać trochę kodu.
Wynika to z tego,
że <code class="language-plaintext highlighter-rouge">IntMap.fromList</code> przyjmuje pary indeks, wartość.</p>
<p>Implementacje <code class="language-plaintext highlighter-rouge">index'</code> i <code class="language-plaintext highlighter-rouge">insert'</code> są banalnie proste:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- Private</span>
<span class="n">index'</span> <span class="o">::</span> <span class="kt">IntMap</span> <span class="n">s</span> <span class="o">-></span> <span class="kt">Int</span> <span class="o">-></span> <span class="kt">Maybe</span> <span class="n">s</span>
<span class="n">index'</span> <span class="o">=</span> <span class="p">(</span><span class="o">!?</span><span class="p">)</span>
<span class="n">insert'</span> <span class="o">::</span> <span class="kt">Int</span> <span class="o">-></span> <span class="n">s</span> <span class="o">-></span> <span class="kt">IntMap</span> <span class="n">s</span> <span class="o">-></span> <span class="kt">IntMap</span> <span class="n">s</span>
<span class="n">insert'</span> <span class="o">=</span> <span class="n">insert</span>
</code></pre></div></div>
<p>Czy implementacja oparta na <code class="language-plaintext highlighter-rouge">IntMap</code> jest lepsza niż na <code class="language-plaintext highlighter-rouge">Seq</code>?
Tego nie wiem.
Typ <code class="language-plaintext highlighter-rouge">IntMap</code> nie ma podanych teoretycznych wartości w notacji dużego O.
Z drugiej strony nawet jakby posiadał,
to może się to różnie przekładać na rzeczywiste rezultaty.
Wszystko też zależy od ilości danych.
Potrzebny byłby duży program w języku <strong><a href="/eso/subleq">SubLeq</a></strong>.
I porządne testy wydajnościowe.</p>
<p>Ważne jest to, że udało się z hermetyzować użycie Listy, Sekwencji i IntMapy</p>
<h2 id="użycie">Użycie</h2>
<p>Jako przykład interpreter języka <strong><a href="/eso/subleq">SubLeq</a></strong>:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">import</span> <span class="nn">HelVM.HelCam.Machines.SubLeq.Lexer</span>
<span class="kr">import</span> <span class="nn">HelVM.HelCam.Machines.SubLeq.Symbol</span>
<span class="kr">import</span> <span class="nn">HelVM.HelCam.Common.RAM.IntMapRAM</span> <span class="k">as</span> <span class="n">RAM</span>
<span class="kr">import</span> <span class="nn">HelVM.HelCam.Common.MockIO</span>
<span class="kr">import</span> <span class="nn">HelVM.HelCam.Common.Util</span>
<span class="kr">import</span> <span class="nn">HelVM.HelCam.Common.WrapperIO</span>
<span class="kr">type</span> <span class="kt">Memory</span> <span class="o">=</span> <span class="kt">RAM</span> <span class="kt">Symbol</span>
<span class="kr">class</span> <span class="kt">Evaluator</span> <span class="n">r</span> <span class="kr">where</span>
<span class="n">eval</span> <span class="o">::</span> <span class="kt">Source</span> <span class="o">-></span> <span class="n">r</span>
<span class="n">eval</span> <span class="o">=</span> <span class="n">evalIL</span> <span class="o">.</span> <span class="n">tokenize</span>
<span class="n">evalIL</span> <span class="o">::</span> <span class="kt">SymbolList</span> <span class="o">-></span> <span class="n">r</span>
<span class="n">evalIL</span> <span class="n">il</span> <span class="o">=</span> <span class="n">doInstruction</span> <span class="mi">0</span> <span class="o">$</span> <span class="kt">RAM</span><span class="o">.</span><span class="n">fromList</span> <span class="n">il</span>
<span class="n">doInstruction</span> <span class="o">::</span> <span class="kt">Symbol</span> <span class="o">-></span> <span class="kt">Memory</span> <span class="o">-></span> <span class="n">r</span>
<span class="n">doInstruction</span> <span class="n">ic</span> <span class="n">m</span>
<span class="o">|</span> <span class="n">ic</span> <span class="o"><</span> <span class="mi">0</span> <span class="o">=</span> <span class="n">doEnd</span>
<span class="o">|</span> <span class="n">src</span> <span class="o"><</span> <span class="mi">0</span> <span class="o">=</span> <span class="n">doInputChar</span> <span class="n">dst</span> <span class="n">ic</span> <span class="n">m</span>
<span class="o">|</span> <span class="n">dst</span> <span class="o"><</span> <span class="mi">0</span> <span class="o">=</span> <span class="n">doOutputChar</span> <span class="n">src</span> <span class="n">ic</span> <span class="n">m</span>
<span class="o">|</span> <span class="n">otherwise</span> <span class="o">=</span> <span class="n">doInstruction</span> <span class="n">ic'</span> <span class="o">$</span> <span class="n">store</span> <span class="n">dst</span> <span class="n">diff</span> <span class="n">m</span>
<span class="kr">where</span>
<span class="n">src</span> <span class="o">=</span> <span class="n">load</span> <span class="n">m</span> <span class="n">ic</span>
<span class="n">dst</span> <span class="o">=</span> <span class="n">load</span> <span class="n">m</span> <span class="o">$</span> <span class="n">ic</span> <span class="o">+</span> <span class="mi">1</span>
<span class="n">diff</span> <span class="o">=</span> <span class="n">load</span> <span class="n">m</span> <span class="n">dst</span> <span class="o">-</span> <span class="n">load</span> <span class="n">m</span> <span class="n">src</span>
<span class="n">ic'</span>
<span class="o">|</span> <span class="n">diff</span> <span class="o"><=</span> <span class="mi">0</span> <span class="o">=</span> <span class="n">load</span> <span class="n">m</span> <span class="o">$</span> <span class="n">ic</span> <span class="o">+</span> <span class="mi">2</span>
<span class="o">|</span> <span class="n">otherwise</span> <span class="o">=</span> <span class="n">ic</span> <span class="o">+</span> <span class="mi">3</span>
<span class="n">doEnd</span> <span class="o">::</span> <span class="n">r</span>
<span class="n">doInputChar</span> <span class="o">::</span> <span class="kt">Symbol</span> <span class="o">-></span> <span class="kt">Symbol</span> <span class="o">-></span> <span class="kt">Memory</span> <span class="o">-></span> <span class="n">r</span>
<span class="n">doOutputChar</span> <span class="o">::</span> <span class="kt">Symbol</span> <span class="o">-></span> <span class="kt">Symbol</span> <span class="o">-></span> <span class="kt">Memory</span> <span class="o">-></span> <span class="n">r</span>
<span class="c1">----</span>
<span class="n">interactEval</span> <span class="o">::</span> <span class="kt">Source</span> <span class="o">-></span> <span class="kt">IO</span> <span class="nb">()</span>
<span class="n">interactEval</span> <span class="kr">_</span> <span class="o">=</span> <span class="n">pass</span>
<span class="n">batchEval</span> <span class="o">::</span> <span class="kt">Source</span> <span class="o">-></span> <span class="kt">Output</span>
<span class="n">batchEval</span> <span class="n">source</span> <span class="o">=</span> <span class="n">eval</span> <span class="n">source</span> <span class="p">(</span><span class="kt">[]</span><span class="o">::</span><span class="kt">String</span><span class="p">)</span>
<span class="n">batchEvalIL</span> <span class="o">::</span> <span class="kt">SymbolList</span> <span class="o">-></span> <span class="kt">Output</span>
<span class="n">batchEvalIL</span> <span class="n">memory</span> <span class="o">=</span> <span class="n">evalIL</span> <span class="n">memory</span> <span class="p">(</span><span class="kt">[]</span><span class="o">::</span><span class="kt">String</span><span class="p">)</span>
<span class="kr">instance</span> <span class="kt">Evaluator</span> <span class="p">(</span><span class="kt">Input</span> <span class="o">-></span> <span class="kt">Output</span><span class="p">)</span> <span class="kr">where</span>
<span class="n">doEnd</span> <span class="kr">_</span> <span class="o">=</span> <span class="kt">[]</span>
<span class="n">doInputChar</span> <span class="kr">_</span> <span class="kr">_</span> <span class="kr">_</span> <span class="kt">[]</span> <span class="o">=</span> <span class="n">error</span> <span class="s">"Empty input"</span>
<span class="n">doInputChar</span> <span class="n">address</span> <span class="n">ic</span> <span class="n">m</span> <span class="p">(</span><span class="n">value</span><span class="o">:</span><span class="n">input</span><span class="p">)</span> <span class="o">=</span> <span class="n">doInstruction</span> <span class="p">(</span><span class="n">ic</span><span class="o">+</span><span class="mi">3</span><span class="p">)</span> <span class="p">(</span><span class="n">store</span> <span class="n">address</span> <span class="p">(</span><span class="n">ord</span> <span class="n">value</span><span class="p">)</span> <span class="n">m</span><span class="p">)</span> <span class="n">input</span>
<span class="n">doOutputChar</span> <span class="n">address</span> <span class="n">ic</span> <span class="n">memory</span> <span class="n">input</span> <span class="o">=</span> <span class="n">chr</span> <span class="p">(</span><span class="n">load</span> <span class="n">memory</span> <span class="n">address</span><span class="p">)</span> <span class="o">:</span> <span class="n">doInstruction</span> <span class="p">(</span><span class="n">ic</span><span class="o">+</span><span class="mi">3</span><span class="p">)</span> <span class="n">memory</span> <span class="n">input</span>
<span class="c1">----</span>
<span class="n">monadicEval</span> <span class="o">::</span> <span class="kt">Source</span> <span class="o">-></span> <span class="kt">IO</span> <span class="nb">()</span>
<span class="n">monadicEval</span> <span class="o">=</span> <span class="n">eval</span>
<span class="kr">instance</span> <span class="kt">Evaluator</span> <span class="p">(</span><span class="kt">IO</span> <span class="nb">()</span><span class="p">)</span> <span class="kr">where</span>
<span class="n">doEnd</span> <span class="o">=</span> <span class="n">pass</span>
<span class="n">doInputChar</span> <span class="n">address</span> <span class="n">ic</span> <span class="n">m</span> <span class="o">=</span> <span class="kr">do</span>
<span class="n">value</span> <span class="o"><-</span> <span class="n">wGetInt</span>
<span class="n">doInstruction</span> <span class="p">(</span><span class="n">ic</span><span class="o">+</span><span class="mi">3</span><span class="p">)</span> <span class="o">$</span> <span class="n">store</span> <span class="n">address</span> <span class="n">value</span> <span class="n">m</span>
<span class="n">doOutputChar</span> <span class="n">address</span> <span class="n">ic</span> <span class="n">memory</span> <span class="o">=</span> <span class="kr">do</span>
<span class="n">wPutInt</span> <span class="o">$</span> <span class="n">load</span> <span class="n">memory</span> <span class="n">address</span>
<span class="n">doInstruction</span> <span class="p">(</span><span class="n">ic</span><span class="o">+</span><span class="mi">3</span><span class="p">)</span> <span class="n">memory</span>
<span class="c1">----</span>
<span class="kr">instance</span> <span class="kt">Evaluator</span> <span class="p">(</span><span class="kt">MockIO</span> <span class="nb">()</span><span class="p">)</span> <span class="kr">where</span>
<span class="n">doEnd</span> <span class="o">=</span> <span class="n">pass</span>
<span class="n">doInputChar</span> <span class="n">address</span> <span class="n">ic</span> <span class="n">m</span> <span class="o">=</span> <span class="kr">do</span>
<span class="n">value</span> <span class="o"><-</span> <span class="n">mockGetInt</span>
<span class="n">doInstruction</span> <span class="p">(</span><span class="n">ic</span><span class="o">+</span><span class="mi">3</span><span class="p">)</span> <span class="o">$</span> <span class="n">store</span> <span class="n">address</span> <span class="n">value</span> <span class="n">m</span>
<span class="n">doOutputChar</span> <span class="n">address</span> <span class="n">ic</span> <span class="n">memory</span> <span class="o">=</span> <span class="kr">do</span>
<span class="n">mockPutInt</span> <span class="o">$</span> <span class="n">load</span> <span class="n">memory</span> <span class="n">address</span>
<span class="n">doInstruction</span> <span class="p">(</span><span class="n">ic</span><span class="o">+</span><span class="mi">3</span><span class="p">)</span> <span class="n">memory</span>
</code></pre></div></div>
<h2 id="podsumowanie">Podsumowanie</h2>
<p>Udało się ukryć szczegóły implementacyjne wewnątrz modułu oraz możemy dokonać wyboru między implementacjami zmieniając jedną linie:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">import</span> <span class="nn">HelVM.HelCam.Common.RAM.ListRAM</span> <span class="k">as</span> <span class="n">RAM</span>
<span class="kr">import</span> <span class="nn">HelVM.HelCam.Common.RAM.SeqRAM</span> <span class="k">as</span> <span class="n">RAM</span>
<span class="kr">import</span> <span class="nn">HelVM.HelCam.Common.RAM.IntMapRAM</span> <span class="k">as</span> <span class="n">RAM</span>
</code></pre></div></div>
<p>Nie jest to jednak rozwiązanie idealne.
Nie możemy zmieniać implementacji podczas działania programu.
Także wstrzykiwanie różnych implementacji na potrzeby testów jest utrudnione.</p>
<p>Kod interpretera <a href="/projects/helma">Helcam</a> po zmianach znajduje się <a href="https://github.com/helvm/helma/tree/v0.6.2.0">tutaj</a>.</p>TheKamilAdamHermetyzacja to cecha charakterystyczna obiektowych języków programowania. Co jednak nie znaczy, że hermetyzacja nie istnieje w Haskellu. Hermetyzację podobną do klas z języków OOP osiąga się w Haskellu za pomocą modułów i jawnego eksportowania funkcji i typów.Alternatywy dla Prelude w Haskellu2021-01-06T00:00:00+01:002021-01-06T00:00:00+01:00http://writeonly.pl/haskell-eta/relude<p><strong><a href="/langs/haskell">Haskell</a></strong> jest pięknym językiem programowania,
ale nie jest doskonały.
Haskell posiada wiele błędów projektowych.
Biblioteka standardowa <strong><a href="/langs/haskell">Haskella</a></strong> też nie jest idealna.
Prelude jest zbiorem domyślnie importowanych modułów w <strong><a href="/langs/haskell">Haskellu</a></strong> z biblioteki standardowej.
Niestety prelude importuje wiele niebezpiecznych oraz powolnych funkcji.
Jednocześnie nie importuje wielu użytecznych funkcji.</p>
<p>Dlatego alternatywy dla <code class="language-plaintext highlighter-rouge">prelude</code> próbują to poprawić.
Są to między innymi biblioteki:</p>
<ul>
<li><a href="https://hackage.haskell.org/package/relude">relude</a></li>
<li><a href="https://hackage.haskell.org/package/protolude">protolude</a></li>
<li><a href="https://hackage.haskell.org/package/classy-prelude">classy-prelude</a></li>
</ul>
<p>Jednak którą bibliotekę wybrać?</p>
<ul>
<li>Tą, która ma najwięcej gwiazdek na githubie?</li>
<li>Tą, która ma najlepszą dokumentację?</li>
<li>Tą, która ma najlepszą stronę?</li>
</ul>
<p>Nie jest to ważne.
I tak we wszystkich trzech konkurencjach wygrywa <code class="language-plaintext highlighter-rouge">relude</code>.</p>
<h2 id="biblioteka-relude">Biblioteka <code class="language-plaintext highlighter-rouge">relude</code></h2>
<p>Biblioteka <code class="language-plaintext highlighter-rouge">relude</code> posiada wiele zalet, są to między innymi:</p>
<ul>
<li>Programowanie totalne</li>
<li>Wydajność</li>
<li>Wygoda</li>
<li>Minimalizm</li>
</ul>
<h3 id="programowanie-totalne">Programowanie totalne</h3>
<p>Funkcje powinny być totalne.
To znaczy dla każdego zestawu parametrów zwracać jakąś wartość.
Jeśli funkcja dla jakichś argumentów nie zwraca wartości,
tylko rzuca wyjątek,
to jest częściowa,
a nie totalna.
Wiele funkcji z Prelude rzuca wyjątkami.
Biblioteka <code class="language-plaintext highlighter-rouge">relude</code> posiada totalne wersje tych funkcji.</p>
<p>Osiąga to za pomocą:</p>
<ul>
<li>funkcji zwracających <code class="language-plaintext highlighter-rouge">Maybe</code> lub <code class="language-plaintext highlighter-rouge">Either</code> zamiast zgłaszających błędy.</li>
<li>typu <code class="language-plaintext highlighter-rouge">NonEmpty</code> zamiast <code class="language-plaintext highlighter-rouge">List</code> tam,
gdzie ma to sens.</li>
<li>nie importowania niebezpiecznych funkcji ja <code class="language-plaintext highlighter-rouge">!!</code>x.</li>
</ul>
<p>Dalej można korzystać z niebezpiecznych funkcji,
jeśli zaimportuje się moduł <code class="language-plaintext highlighter-rouge">Unsafe</code>:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">import</span> <span class="k">qualified</span> <span class="nn">Relude.Unsafe</span> <span class="k">as</span> <span class="n">Unsafe</span>
</code></pre></div></div>
<h3 id="wydajność">Wydajność</h3>
<p>Domyślnie w <strong><a href="/langs/haskell">Haskellu</a></strong> literały tekstowe są typu <code class="language-plaintext highlighter-rouge">String</code>,
gdzie <code class="language-plaintext highlighter-rouge">String</code> to lista znaków:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">type</span> <span class="kt">String</span> <span class="o">=</span> <span class="p">[</span><span class="kt">Char</span><span class="p">]</span>
</code></pre></div></div>
<p>Jest to powolna implementacja napisów i zamiast <code class="language-plaintext highlighter-rouge">String</code> należy używać typu <code class="language-plaintext highlighter-rouge">Text</code>.
Można to zmienić za pomocą przełącznika dla kompilatora <code class="language-plaintext highlighter-rouge">ghc-options: -XOverloadedStrings</code>.
Można też użyć pragmy (dyrektywy?) kompilatora <code class="language-plaintext highlighter-rouge">{-# LANGUAGE OverloadedStrings #-}</code>,
ale przełącznik jest lepszym rozwiązaniem,
ponieważ działa we wszystkich plikach.</p>
<p>Następnie należy zastąpić wszystkie ewentualne łączenia Stringów za pomocą operatora <code class="language-plaintext highlighter-rouge">++</code>, operatorem <code class="language-plaintext highlighter-rouge"><></code>.
W zasadzie to wszelkie łączenia list operatorem <code class="language-plaintext highlighter-rouge">++</code> można zastąpić operatorem łączenia monoidów <code class="language-plaintext highlighter-rouge"><></code>.</p>
<p>Niestety <code class="language-plaintext highlighter-rouge">Text</code> znajduje się w module zewnętrznym <code class="language-plaintext highlighter-rouge">text</code> a nie module <code class="language-plaintext highlighter-rouge">base</code> będącym biblioteką standardową.
<code class="language-plaintext highlighter-rouge">Text</code> jest zalecany,
ale mimo to w <strong><a href="/langs/haskell">Haskellu</a></strong> wiele funkcji z biblioteki standardowej pobiera lub zwraca typ <code class="language-plaintext highlighter-rouge">String</code>.</p>
<p>Biblioteka <code class="language-plaintext highlighter-rouge">relude</code> naprawia ten problem i tak:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">show</code> jest polimorficzny ze względu na typ zwracany,
czyli może wracać zarówno <code class="language-plaintext highlighter-rouge">String</code> jak i <code class="language-plaintext highlighter-rouge">Text</code>.</li>
<li>funkcja <code class="language-plaintext highlighter-rouge">error</code> przyjmuje <code class="language-plaintext highlighter-rouge">Text</code>.</li>
</ul>
<p>Dodatkowo mamy miły zestaw funkcji (<code class="language-plaintext highlighter-rouge">toText|toLText|toString</code>) do konwersji między typami.
Dla typu <code class="language-plaintext highlighter-rouge">ByteString</code> jest o wiele prostsza do zapamiętania para funkcji <code class="language-plaintext highlighter-rouge">encodeUtf8|decodeUtf8</code>.</p>
<p>Połączenie tego wszystkiego (przełącznik <code class="language-plaintext highlighter-rouge">-XOverloadedStrings</code>, funkcja <code class="language-plaintext highlighter-rouge">show</code>, funkcja <code class="language-plaintext highlighter-rouge">toText</code> lub <code class="language-plaintext highlighter-rouge">toString</code>) może sprawić,
że czasem <strong><a href="/langs/haskell">Haskell</a></strong> nie będzie w stanie wywnioskować typu.
Wszędzie tam,
gdzie typ jest obojętny powinniśmy używać typu <code class="language-plaintext highlighter-rouge">Text</code>.
A wszędzie tam gdzie typ jest trudny do wywnioskowania dla kompilatora <strong><a href="/langs/haskell">Haskella</a></strong> musimy jawnie podać typ:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">"Awesome relude!"</span><span class="o">::</span><span class="kt">Text</span>
</code></pre></div></div>
<p>Niestety funkcje <code class="language-plaintext highlighter-rouge">readMaybe</code> i <code class="language-plaintext highlighter-rouge">readEither</code> przyjmują dalej <code class="language-plaintext highlighter-rouge">String</code> zamiast <code class="language-plaintext highlighter-rouge">Text</code>.
Powoduje to,
że w niektórych przypadkach dalej lepiej używać <code class="language-plaintext highlighter-rouge">Stringa</code>:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">readOrError</span> <span class="o">::</span> <span class="kt">Read</span> <span class="n">a</span> <span class="o">=></span> <span class="kt">String</span> <span class="o">-></span> <span class="n">a</span>
<span class="n">readOrError</span> <span class="n">raw</span> <span class="o">=</span> <span class="n">check</span> <span class="o">$</span> <span class="n">readEither</span> <span class="n">raw</span> <span class="kr">where</span>
<span class="n">check</span> <span class="p">(</span><span class="kt">Right</span> <span class="n">result</span><span class="p">)</span> <span class="o">=</span> <span class="n">result</span>
<span class="n">check</span> <span class="p">(</span><span class="kt">Left</span> <span class="n">message</span><span class="p">)</span> <span class="o">=</span> <span class="n">error</span> <span class="o">$</span> <span class="n">message</span> <span class="o"><></span> <span class="s">" ["</span> <span class="o"><></span> <span class="n">toText</span> <span class="n">raw</span> <span class="o"><></span> <span class="s">"]"</span>
</code></pre></div></div>
<h3 id="wygoda">Wygoda</h3>
<p>Biblioteka <code class="language-plaintext highlighter-rouge">relude</code> skraca czas pisania aplikacji,
ponieważ wiele importów dzieje się automatycznie.
Są to:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">base</code></li>
<li><code class="language-plaintext highlighter-rouge">bytestring</code></li>
<li><code class="language-plaintext highlighter-rouge">containers</code></li>
<li><code class="language-plaintext highlighter-rouge">deepseq</code></li>
<li><code class="language-plaintext highlighter-rouge">ghc-prim</code></li>
<li><code class="language-plaintext highlighter-rouge">hashable</code></li>
<li><code class="language-plaintext highlighter-rouge">mtl</code></li>
<li><code class="language-plaintext highlighter-rouge">stm</code></li>
<li><code class="language-plaintext highlighter-rouge">text</code></li>
<li><code class="language-plaintext highlighter-rouge">transformers</code></li>
<li><code class="language-plaintext highlighter-rouge">unordered-containers</code></li>
</ul>
<p>Modułów tych nie należy dodawać do zależności samodzielnie,
tylko używać ich za pośrednictwem <code class="language-plaintext highlighter-rouge">relude</code>.
Dzięki temu można usunąć z projektu importy wielu modułów.
W moim przypadku było to:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">import</span> <span class="nn">Control.Applicative</span>
<span class="kr">import</span> <span class="nn">Control.Monad.Except</span>
<span class="kr">import</span> <span class="nn">Data.Char</span>
<span class="kr">import</span> <span class="nn">Data.Map.Strict</span>
<span class="kr">import</span> <span class="nn">Data.Maybe</span>
<span class="kr">import</span> <span class="nn">Data.Text</span>
<span class="kr">import</span> <span class="nn">Numeric.Natural</span>
<span class="kr">import</span> <span class="nn">Text.Read</span>
</code></pre></div></div>
<p>Dodatkowo kolejne funkcjonalności można importować za pomocą <code class="language-plaintext highlighter-rouge">import Relude.Extra</code>.
Mnie przydały się funkcje <code class="language-plaintext highlighter-rouge">lookup</code>, <code class="language-plaintext highlighter-rouge">next</code> i <code class="language-plaintext highlighter-rouge">prev</code>.</p>
<h3 id="minimalizm">Minimalizm</h3>
<p>Biblioteka <code class="language-plaintext highlighter-rouge">relude</code> stara się być biblioteką minimalistyczną,
czyli zależącą od minimalnej ilości modułów zewnętrznych.
Jest to trudne,
ponieważ <strong><a href="/langs/haskell">Haskell</a></strong> posiada bardzo małą bibliotekę standardową <code class="language-plaintext highlighter-rouge">base</code>.
O wiele za małą.</p>
<p>Zależności używane przez <code class="language-plaintext highlighter-rouge">relude</code> znajdują się na <a href="https://raw.githubusercontent.com/kowainik/relude/main/relude-dependency-graph.png">wykresie</a>.
Początkowo może się wydawać,
że wykres zawiera pół internetu,
ale zależności można podzielić na cztery grupy:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">text</code> i <code class="language-plaintext highlighter-rouge">bytestring</code>,
czyli obsługa tekstu.</li>
<li><code class="language-plaintext highlighter-rouge">unordered-containers</code> i <code class="language-plaintext highlighter-rouge">containers</code>, czyli kontenery (kolekcje).
Przy czym <code class="language-plaintext highlighter-rouge">containers</code> zostało już dodane przez <code class="language-plaintext highlighter-rouge">text</code>.</li>
<li><code class="language-plaintext highlighter-rouge">mtl</code> czyli transformatory monad (<code class="language-plaintext highlighter-rouge">ContT</code>, <code class="language-plaintext highlighter-rouge">ExceptT</code>, <code class="language-plaintext highlighter-rouge">ListT</code>, <code class="language-plaintext highlighter-rouge">RWST</code>, <code class="language-plaintext highlighter-rouge">ReaderT</code>, <code class="language-plaintext highlighter-rouge">StateT</code>, <code class="language-plaintext highlighter-rouge">WriterT</code>) potrzebne każdemu,
kto chce się zmierzyć z zagnieżdżonymi monadami i stanem.</li>
<li><code class="language-plaintext highlighter-rouge">stm</code> czyli legendarna Software Transactional Memory to pamięć transakcyjna używana przy współbieżności,
która zainspirowała między innymi twórcę języka <strong><a href="/langs/clojure">Clojure</a></strong>.</li>
</ul>
<p>Czy to dużo?
W porównaniu z biblioteką standardową wielu innych języków programowania powiedziałbym,
że to bardzo mało.</p>
<h3 id="dokumentacja">Dokumentacja</h3>
<p>Biblioteka <code class="language-plaintext highlighter-rouge">relude</code> posiada bardzo dobrą dokumentację i przewodnik przeprowadzający przez podmianę biblioteki.</p>
<p>Dodatkową zaletą jest linter <code class="language-plaintext highlighter-rouge">hlint</code>.
Linter <code class="language-plaintext highlighter-rouge">hlint</code> jest narzędziem magicznym.
Pozwala napisać kod,
którego nie jesteśmy w stanie zrozumieć,
przynajmniej na początku.
Piszemy kod prosty i długi przy użyciu tylko trywialnych funkcji,
a <code class="language-plaintext highlighter-rouge">hlint</code> podpowiada jak go skrócić i jednocześnie go skomplikować.</p>
<h2 id="podsumowanie">Podsumowanie</h2>
<p>Czy obietnice składane przez twórców <code class="language-plaintext highlighter-rouge">relude</code> są spełnione?
Jeszcze nie poznałem wszystkich zalet <code class="language-plaintext highlighter-rouge">relude</code>,
ale uważam,
że tak.</p>
<p>Na githubie znajduje się kod projektów [Helcam] i <a href="https://github.com/helvm/helpa/tree/v0.3.1">Helpa</a> po użyciu biblioteki <code class="language-plaintext highlighter-rouge">relude</code>.</p>TheKamilAdamHaskell jest pięknym językiem programowania, ale nie jest doskonały. Haskell posiada wiele błędów projektowych. Biblioteka standardowa Haskella też nie jest idealna. Prelude jest zbiorem domyślnie importowanych modułów w Haskellu z biblioteki standardowej. Niestety prelude importuje wiele niebezpiecznych oraz powolnych funkcji. Jednocześnie nie importuje wielu użytecznych funkcji.5 języków programowania, które warto znać będąc już programistą2020-12-02T00:00:00+01:002020-12-02T00:00:00+01:00http://writeonly.pl/programming/5-programming-languages<p>Jakiś czas temu na <a href="https://4programmers.net/">https://4programmers.net</a> pojawiło się stwierdzenie:</p>
<blockquote>
<p>Ostatnio oglądałem wypowiedź twórcy języka C++,
gdzie Bjarne Stroustrup powiedział,
że zna ponad 20 języków programowania w przynajmniej podstawowym stopniu,
dodał, że dobry programista powinien znać bardzo dobrze przynajmniej 5 języków,
aby potrafił spojrzeć na problem z różnych perspektyw.
Jaką piątkę wy byście wybrali, w jakiej kolejności i dlaczego?</p>
</blockquote>
<p>Przygotowałem listę 5 języków programowania,
które warto znać,
będąc już programistą.
Jeśli jeszcze nie jesteś programistą,
spójrz na cztery inne artykuły,
które opisują jaki język programowania warto poznać na początek:</p>
<ol>
<li><a href="/jezyk-skryptowy">dynamicznie typowany język skryptowy ogólnego przeznaczenia</a></li>
<li><a href="/jezyk-korporacyjny">statycznie typowany język korporacyjny używany do pisania długowiecznych aplikacji klasy <em>enterprise</em></a></li>
<li><a href="/jezyk-fullstackowy">fullstack język, który można używać do pisania frontendu i backendu</a></li>
<li><a href="/jezyk-natywny">szybki język natywny działający bez maszyny wirtualnej i interpretera</a></li>
</ol>
<p>Języki programowania do listy wybierałem według zasady,
że większość programistów zna języki <a href="/tags/oop">OOP</a>.
Więc lista zawiera tylko języki nie będące <a href="/tags/oop">OOP</a>,
ale jednak zawierające polimorfizm.</p>
<h2 id="haskell-język-bez-mutowalności"><strong><a href="/langs/haskell">Haskell</a></strong>, język bez mutowalności</h2>
<p>I to wcale nie dlatego że nie ma mutowalności,
czyli zmiennego stanu.
Tylko dlatego, że jest:</p>
<ul>
<li>Lazy.
Haskell jest leniwie ewaluowany.
Jest to dość niestandardowe zachowanie i dobrze znać co najmniej jeden język,
który domyślnie jest leniwy,
żeby zobaczyć jakie powoduje to zalety jak i problemy.</li>
<li>Fulltyped.
W <strong><a href="/langs/haskell">Haskellu</a></strong> typowanie jest o wiele silniejsze niż Javie czy C++.
Niektórzy mówią, że to zaleta.
Inni, że to wada.</li>
<li>To język z polimorfizmem bez <a href="/tags/oop">OOP</a>.
Dokładniej jest to polimorfizm <a href="/tags/ad-hoc">ad hoc</a>.
Większość topowych języków programowania,
jeśli ma polimorfizm to jest to polimorfizm związany z <a href="/tags/oop">OOP</a>.
Warto wiedzieć,
że istnieje alternatywa.</li>
<li>Programowanie reaktywne.
Mówi się, że programowanie reaktywne jest trudne.
Ale w <strong><a href="/langs/haskell">Haskellu</a></strong> jest naturalne.
Ponieważ większość konstrukcji reaktywnych jak Mono ze Springa to tak naprawdę uproszczona monada <strong><a href="/tags/io">IO</a></strong>.</li>
</ul>
<h2 id="prosty-lisp-a-dokładniej-scheme">Prosty <strong><a href="/langs/lisp">LISP</a></strong> a dokładniej <strong><a href="/langs/scheme">Scheme</a></strong></h2>
<p><strong><a href="/langs/haskell">Haskell</a></strong> ma opinię języka skomplikowanego.
Głownie przez monady, które umożliwiają zapis operacji imperatywnych w języku deklaratywnym.
Przez to opinię trudnych mają wszystkie języki funkcyjne.
Dlatego warto poznać <strong><a href="/langs/scheme">Scheme</a></strong>,
żeby poznać minimalistyczny język funkcyjny.
Ogólnie jest to jeden z najbardziej minimalistycznych języków programowania,
Dlatego bardzo łatwo można napisać swój własny interpreter Scheme.
Jest to myśl przewodnia książki <a href="/books/sicp">Struktura i interpretacja programów komputerowych</a>.
Implementacja własnego interpretera <strong><a href="/langs/scheme">Scheme</a></strong> jest także myślą przewodnią wielu kursów programowania jak np. <a href="https://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours">Write Scheme in 48 h</a>.</p>
<p>Jednak to, że rdzeń <strong><a href="/langs/scheme">Scheme</a></strong> jest mały,
nie znaczy,
że biblioteka jest mała.
Istnieje przenośna biblioteka standardowa <strong><a href="/langs/scheme">Scheme</a></strong> niezależna od implementacji.</p>
<p>Inną z ciekawych rzeczy istniejącą w <strong><a href="/langs/scheme">Scheme</a></strong> są <a href="/tags/hygienic-macro">makra higieniczne</a>,
które pozwalają dodawać nowe konstrukcje programistyczne.</p>
<p>Jeśli jednak ktoś chciałby implementację <strong><a href="/langs/scheme">Scheme</a></strong> z dużą biblioteką standardową to polecam <strong><a href="/langs/racket">Racket</a></strong>.</p>
<h3 id="racket-scheme-na-sterydach"><strong><a href="/langs/racket">Racket</a></strong>, <strong><a href="/langs/scheme">Scheme</a></strong> na sterydach</h3>
<p>Ludzie nie są zgodni czy <strong><a href="/langs/racket">Racket</a></strong> jest kolejną implementacją <strong><a href="/langs/scheme">Scheme</a></strong>,
czy już osobnym językiem programowania z powodu dużej ilości rozszerzeń.</p>
<p><strong><a href="/langs/racket">Racket</a></strong> posiada też dużą ilość bibliotek oraz możliwość pisania frontów do kompilatora <strong><a href="/langs/racket">Racket</a></strong>.
Dzięki temu udało się stworzyć osobny język <strong><a href="/langs/typed-racket">Typed Racket</a></strong>.</p>
<p>Największą wadą <strong><a href="/langs/racket">Racketa</a></strong> jest to,
że jest to język programowania dla naukowców i w ogóle nie da się w nim znaleźć pracy dla normalnego programisty.
Problemu tego nie posiada <strong><a href="/langs/clojure">Clojure</a></strong>.</p>
<h3 id="clojure-lisp-na-jvm"><strong><a href="/langs/clojure">Clojure</a></strong>, <strong><a href="/langs/lisp">Lisp</a></strong> na <a href="/tools/jvm">JVM</a></h3>
<p>Prawdopodobnie najpopularniejszy dialekt <strong><a href="/langs/lisp">Lispa</a></strong> w Polsce,
a może nawet w Europie (patrząc po ofertach pracy).
Jego ogromną zaletą jest to,
że działa na <a href="/tools/jvm">JVM</a> i dobrze integruje się z klasami Javowymi.
Przez co jest prawdopodobnie najbardziej klasycznie obiektowym dialektem języka <strong><a href="/langs/lisp">Lisp</a></strong>.
Inne dialekty <strong><a href="/langs/lisp">Lispa</a></strong> także posiadają polimorfizm i możliwość programowania obiektowego,
ale zwykle w niestandardowy sposób.</p>
<p>Jeśli chcesz programować zawodowo w <strong><a href="/langs/lisp">Lispie</a></strong>,
ucz się <strong><a href="/langs/clojure">Clojure</a></strong>.</p>
<h2 id="niskopoziomowy-rust">Niskopoziomowy <strong><a href="/langs/rust">Rust</a></strong></h2>
<p>Niskopoziomowy język programowania,
który chce zastąpić C++.</p>
<p>Jak na niego patrzę to widze uproszczonego <strong><a href="/langs/haskell">Haskella</a></strong>,
ponieważ rustowe <a href="/tags/trait">Traity</a> są bardzo podobne do haskellowcyh <a href="/tags/type-class">Klas Typów</a>.
Rust posiada także <a href="/tags/hygienic-macro">makra higieniczne</a> (podobnie jak <strong><a href="/langs/scheme">Scheme</a></strong>),
jednak umożliwiające typowanie.</p>
<p>Dla mnie ten język to fenomen.
Nie stoi za nim żadna wielka korporacja,
a momentalnie wbił się do 20 najpopularniejszych języków programowania według indeksu <a href="https://www.tiobe.com/tiobe-index/">Tiobe</a>.
Jest w nim także praca dla programistów.</p>
<h2 id="idris-z-typami-zależnymi"><strong><a href="/langs/idris">Idris</a></strong> z typami zależnymi</h2>
<p>Poprawiony <strong><a href="/langs/haskell">Haskell</a></strong> z typami zależnymi.
Jeszcze nie wiem co to typy zależne i czemu to potrzebuję.
Niby wiem,
że <code class="language-plaintext highlighter-rouge">Typy zależne są potrzebne do dowodzenia poprawności programu</code>,
ale nie wiem,
co z tego wynika.</p>
<p>W przeciwieństwie do innych języków z typami zależnymi,
jak <strong><a href="/langs/agda">Agda</a></strong> czy <strong><a href="/langs/coq">Coq</a></strong>,
<strong><a href="/langs/idris">Idris</a></strong> jest zaprojektowany jako język ogólnego przeznaczenia,
a nie tylko do dowodzenia twierdzeń.</p>
<p>Jego największą wadą jest to,
że to nisza niszy.
Jest rozwijany przez jednego człowieka.</p>
<p>Z drugiej strony im popularniejszy jest <strong><a href="/langs/idris">Idris</a></strong> tym bardziej prawdopodobne,
że eksperymentalne funkcjonalności tego języka zostaną zaimplementowane także w <strong><a href="/langs/haskell">Haskellu</a></strong>.</p>
<h2 id="inny-język-programowania">Inny język programowania</h2>
<p>Zostało jedno wolne miejsce,
które jednak nie wiem,
na co przeznaczyć.</p>
<h3 id="purescript-gorliwie-ewaluaowany-haskell-dedykowany-do-pisania-frontu"><strong><a href="/langs/purescript">PureScript</a></strong>, gorliwie ewaluaowany <strong><a href="/langs/haskell">Haskell</a></strong> dedykowany do pisania frontu.</h3>
<p>Odrzuciłem z listy,
ponieważ to tylko minimalnie poprawiony <strong><a href="/langs/haskell">Haskell</a></strong>.
(Zresztą większość tych poprawek <strong><a href="/langs/haskell">Haskell</a></strong> 8 także już zawiera)
Oraz dlatego że wszystkie powyższe języki są też kompilowane do weba.
Większość jest kompilowana do <strong><a href="/langs/javascript">JavaScriptu</a></strong>, <strong><a href="/langs/rust">Rust</a></strong> do <strong><a href="/langs/webassembly">WebAssembly</a></strong>.</p>
<h3 id="ocaml-pierwszy-obiektowo-funkcyjny-język-programowania"><strong><a href="/langs/ocaml">OCaml</a></strong>, pierwszy obiektowo funkcyjny język programowania</h3>
<p><strong><a href="/langs/ocaml">OCaml</a></strong> to obiektowy potomek <strong><a href="/langs/meta-language">Meta Language</a></strong> i <strong><a href="/langs/standard-ml">Standard ML</a></strong>.
<strong><a href="/langs/standard-ml">Standard ML</a></strong> jest uważany za ostatniego wspólnego przodka <strong><a href="/langs/haskell">Haskella</a></strong> i <strong><a href="/langs/ocaml">OCamla</a></strong>.
Jakiś czas temu został przeportowany do nowych środowisk jako nowe języki F# i <strong><a href="/langs/reasonml">ReasonML</a></strong>.</p>
<p>Odrzuciłem go z listy,
ponieważ nie uczy niczego nowego w stosunku do wymienionych powyżej języków programowania.</p>
<ul>
<li>Ma gorszy system typów niż <strong><a href="/langs/haskell">Haskell</a></strong>.</li>
<li>Jest mniej niskopoziomowy niż <strong><a href="/langs/rust">Rust</a></strong>.</li>
<li>Ma trudniejszą składnię niż <strong><a href="/langs/typed-racket">Typed Racket</a></strong>.</li>
</ul>
<p>W dodatku posiada klasyczny polimorfizm obiektowy,
co mnie trochę odrzuca.</p>
<h3 id="scala-czyli-skrzyżowanie-javy-i-ocamla"><strong><a href="/langs/scala">Scala</a></strong>, czyli skrzyżowanie Javy i <strong><a href="/langs/ocaml">OCamla</a></strong>.</h3>
<p>Tak właśnie z <strong><a href="/langs/ocaml">OCamlem</a></strong>.
Przez lata nie było tego widać,
ale teraz gdy <strong><a href="/langs/scala">Scala</a></strong> 3 posiada znaczące wcięcia widać to jak na dłoni.
Nie jest to język idealny.
W zasadzie,
jeśli chodzi o czystość składni,
jest nawet gorszy niż <strong><a href="/langs/ocaml">OCaml</a></strong>,
ale ma jedną dużą zaletę.</p>
<ul>
<li>Można w nim pisać <a href="/tags/type-class">Klasy Typów</a></li>
<li>Można w nim zarabiać.</li>
</ul>
<p>I tak dochodzimy do ostatniego proponowanego przeze mnie języka.</p>
<h3 id="język-w-którym-można-zarabiać">Język, w którym można zarabiać.</h3>
<p>Powyższe języki mają dużo wad,
z czego największą jest mała ilość pracy w nich.
Dlatego piąte miejsce zostawiam dla języka z klasycznym obiektowym polimorfizmem,
w którym można pracować i zarabiać.
Te klasyczne OOP języki programowania także cały czas się rozwijają.
I także trzeba się ich cały czas uczyć.
Jeśli ktoś tego zaniedba,
to często po latach okazuje się,
że pracuje w języku,
którego już nie zna.</p>
<h2 id="podsumowanie">Podsumowanie</h2>
<p>Mój plan nauki języków to:</p>
<ol>
<li><strong><a href="/langs/haskell">Haskell</a></strong> i dokończyć eso-assembler <strong><a href="/langs/webassembly">WebAssembly</a></strong> do języków ezoterycznych.</li>
<li><strong><a href="/langs/scheme">Scheme</a></strong> i napisać kompilator <a href="/eso/eso-c">eso C</a> do języków ezoterycznych.</li>
<li><strong><a href="/langs/rust">Rust</a></strong> i przepisać interpretery języków ezoterycznych.</li>
<li><strong><a href="/langs/idris">Idris</a></strong>,
jeszcze nie wiem,
do czego mi to potrzebne,
ale jak się dowiem,
to powiem o tym wszystkim.</li>
<li><strong><a href="/langs/scala">Scala</a></strong> 3 i dostać w niej pracę.
Może reaktywuję swojego toola do formatowania <a href="/tags/json">jsona</a>?</li>
</ol>TheKamilAdamJakiś czas temu na https://4programmers.net pojawiło się stwierdzenie: Ostatnio oglądałem wypowiedź twórcy języka C++, gdzie Bjarne Stroustrup powiedział, że zna ponad 20 języków programowania w przynajmniej podstawowym stopniu, dodał, że dobry programista powinien znać bardzo dobrze przynajmniej 5 języków, aby potrafił spojrzeć na problem z różnych perspektyw. Jaką piątkę wy byście wybrali, w jakiej kolejności i dlaczego?Leniwe ładowanie komentarzy z Disqus2020-11-04T00:00:00+01:002020-11-04T00:00:00+01:00http://writeonly.pl/jekyll/lazy-disqus<p>Ponieważ korzystam z bloga opartego na generatorze statycznych stron <strong><a href="/tools/jekyll">Jekyll</a></strong>
i nie mam żadnego backendu,
nie mam możliwości samodzielnego zaimplementowania komentarzy.
Jednak <strong><a href="/libs/minimal-mistakes">Minimal Mistakes</a></strong>,
używany przeze mnie szablon,
<a href="https://mmistakes.github.io/minimal-mistakes/docs/configuration/#comments">proponuje kilka rozwiązań tego problemu</a>.
Są to zewnętrzne systemy komentarzy wystawiające publiczne <strong><a href="/tags/api">API</a></strong> jak:</p>
<ul>
<li><strong><a href="/tools/disqus">Disqus</a></strong></li>
<li>Discourse</li>
<li>komentarze Facebooka</li>
<li>komentarze utterances</li>
<li>Staticman</li>
</ul>
<p>Ja wybrałem <strong><a href="/tools/disqus">Disqus</a></strong>.
Głównie dlatego,
że jest pierwszy na liście i prawdopodobnie jest najpopularniejszy.
Jednak Disqus posiada jeden poważny problem.
Ładuje ogromną ilość <strong><a href="/langs/javascript">JavaScriptu</a></strong> oraz <strong><a href="/tags/css">CSSa</a></strong>,
przez co artykuł posiadający komentarze posiada bardzo zły wynik po analizowaniu w <a href="https://developers.google.com/speed/pagespeed/insights">PageSpeed Insights</a>.</p>
<p>Jest na to jednak rozwiązanie.
Jest nim leniwe ładowanie komentarzy z <strong><a href="/tools/disqus">Disqus</a></strong>.</p>
<h2 id="leniwe-ładowanie-komentarzy-disqus">Leniwe ładowanie komentarzy Disqus</h2>
<p>Są co najmniej dwa sposoby, żeby leniwie ładować komentarze:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">on click</code> - po kliknięciu przycisku o nazwie np. <code class="language-plaintext highlighter-rouge">pokaż komentarze</code>.</li>
<li><code class="language-plaintext highlighter-rouge">on scroll</code> - po przewinięciu na dół strony, gdzie zwykle są komentarze.</li>
</ul>
<h3 id="leniwe-ładowanie-komentarzy-disqus-po-kliknięciu-przycisku">Leniwe ładowanie komentarzy Disqus po kliknięciu przycisku</h3>
<p>Po pierwsze musimy zmodyfikować html.
Do pliku <code class="language-plaintext highlighter-rouge">comments.html</code>,
pod częścią odpowiedzialną za klasyczny <code class="language-plaintext highlighter-rouge">disqus</code>,
dodajemy <code class="language-plaintext highlighter-rouge">disqus_loader</code></p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code> {% when "disqus" %}
<span class="nt"><h4</span> <span class="na">class=</span><span class="s">"page__comments-title"</span><span class="nt">></span>{{ comments_label }}<span class="nt"></h4></span>
<span class="nt"><section</span> <span class="na">id=</span><span class="s">"disqus_thread"</span><span class="nt">></span>
<span class="nt"></section></span>
{% when "disqus_loader" %}
<span class="nt"><h4</span> <span class="na">class=</span><span class="s">"page__comments-title"</span><span class="nt">></span>{{ comments_label }}<span class="nt"></h4></span>
<span class="nt"><section</span> <span class="na">id=</span><span class="s">"disqus_thread"</span><span class="nt">></span>
<span class="nt"><button</span> <span class="na">id=</span><span class="s">"disqus_loader"</span><span class="nt">></span>{{ comments_title }}<span class="nt"></button></span>
<span class="nt"></section></span>
</code></pre></div></div>
<p>Nasza wersja różni się tym,
że mamy przycisk.
Oczywiście przycisk należałoby jeszcze ostylować,
żeby dobrze wyglądał.</p>
<p>Teraz potrzebujemy skryptu <strong><a href="/langs/javascript">JavaScript</a></strong>, który załaduje nam <strong><a href="/tools/disqus">Disqus</a></strong> na żądanie.
Oczywiście skrypt jest oparty o nieśmiertelne <strong><a href="/libs/jquery">jQuery</a></strong>:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">$</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">$disqus_loader</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">#disqus_loader</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">$disqus_loader</span><span class="p">.</span><span class="nx">click</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">$disqus_thread</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">#disqus_thread</span><span class="dl">'</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="nx">$disqus_thread</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">$</span><span class="p">.</span><span class="nx">ajaxSetup</span><span class="p">({</span> <span class="na">cache</span><span class="p">:</span><span class="kc">true</span> <span class="p">});</span>
<span class="nx">$</span><span class="p">.</span><span class="nx">getScript</span><span class="p">(</span><span class="dl">'</span><span class="s1">//{{site.comments.disqus.shortname}}.disqus.com/embed.js</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">$</span><span class="p">.</span><span class="nx">ajaxSetup</span><span class="p">({</span> <span class="na">cache</span><span class="p">:</span><span class="kc">false</span> <span class="p">});</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Loaded Disqus.</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">});</span>
</code></pre></div></div>
<p>Teraz jeszcze należałoby ten skrypt <strong><a href="/langs/javascript">JS</a></strong> dołączyć do każdej strony.
Ja wszystkie swoje skrypty łącze w jeden plik <code class="language-plaintext highlighter-rouge">app.js</code> za pomocą dyrektywy <code class="language-plaintext highlighter-rouge">include</code> w Jekyllu.
Tutaj jednak potrzebujemy dołączania warunkowego za pomocą instrukcji <code class="language-plaintext highlighter-rouge">case</code>:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="o">%</span> <span class="k">case</span> <span class="nx">site</span><span class="p">.</span><span class="nx">comments</span><span class="p">.</span><span class="nx">provider</span> <span class="o">%</span><span class="p">}</span>
<span class="p">{</span><span class="o">%</span> <span class="nx">when</span> <span class="dl">"</span><span class="s2">disqus_loader</span><span class="dl">"</span> <span class="o">%</span><span class="p">}</span>
<span class="p">{</span><span class="o">%-</span> <span class="nx">include</span> <span class="nx">js</span><span class="o">/</span><span class="nx">disqus</span><span class="o">-</span><span class="nx">loader</span><span class="p">.</span><span class="nx">js</span> <span class="o">-%</span><span class="p">}</span>
<span class="p">{</span><span class="o">%</span> <span class="nx">endcase</span> <span class="o">%</span><span class="p">}</span>
</code></pre></div></div>
<h3 id="leniwe-ładowanie-disqus-po-przewinięciu-strony-na-dół">Leniwe ładowanie Disqus po przewinięciu strony na dół</h3>
<p>Po pierwsze musimy zmodyfikować html.
Do pliku <code class="language-plaintext highlighter-rouge">comments.html</code>, pod częścią odpowiedzialny za klasyczny <code class="language-plaintext highlighter-rouge">disqus</code> dodajemy <code class="language-plaintext highlighter-rouge">disqus_empty</code></p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code> {% when "disqus" %}
<span class="nt"><h4</span> <span class="na">class=</span><span class="s">"page__comments-title"</span><span class="nt">></span>{{ comments_label }}<span class="nt"></h4></span>
<span class="nt"><section</span> <span class="na">id=</span><span class="s">"disqus_thread"</span><span class="nt">></span>
<span class="nt"></section></span>
{% when "disqus_empty" %}
<span class="nt"><h4</span> <span class="na">class=</span><span class="s">"page__comments-title"</span><span class="nt">></span>{{ comments_label }}<span class="nt"></h4></span>
<span class="nt"><section</span> <span class="na">id=</span><span class="s">"disqus_thread"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">"disqus_empty"</span><span class="nt">></div></span>
<span class="nt"></section></span>
</code></pre></div></div>
<p>Nasza wersja różni się tym, że mamy pusty tag <code class="language-plaintext highlighter-rouge">div</code>.
Dzięki niemu będziemy wiedzieć,
czy już załadowaliśmy komentarze,
czy jeszcze nie.</p>
<p>Teraz potrzebujemy skryptu <strong><a href="/langs/javascript">JavaScript</a></strong>, który załaduje nam <strong><a href="/tools/disqus">Disqus</a></strong> na żądanie.
Oczywiście ten skrypt też jest oparty o nieśmiertelne <strong><a href="/libs/jquery">jQuery</a></strong>:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">$</span><span class="p">(</span><span class="nb">document</span><span class="p">).</span><span class="nx">scroll</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Scrolled.</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">$disqus_empty</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">#disqus_empty</span><span class="dl">'</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">$disqus_empty</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">$window</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="nb">window</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">$disqus_thread</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">#disqus_thread</span><span class="dl">'</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">$disqus_thread</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">getBoundingClientRect</span><span class="p">().</span><span class="nx">top</span> <span class="o">-</span> <span class="mi">150</span> <span class="o"><</span> <span class="nx">$window</span><span class="p">.</span><span class="nx">scrollTop</span><span class="p">())</span> <span class="p">{</span>
<span class="nx">$</span><span class="p">.</span><span class="nx">ajaxSetup</span><span class="p">({</span> <span class="na">cache</span><span class="p">:</span><span class="kc">true</span> <span class="p">});</span>
<span class="nx">$</span><span class="p">.</span><span class="nx">getScript</span><span class="p">(</span><span class="dl">'</span><span class="s1">//{{site.comments.disqus.shortname}}.disqus.com/embed.js</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">$</span><span class="p">.</span><span class="nx">ajaxSetup</span><span class="p">({</span> <span class="na">cache</span><span class="p">:</span><span class="kc">false</span> <span class="p">});</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Loaded Disqus.</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>
<p>Teraz jeszcze należałoby ten skrypt JS dołączyć do każdej strony.
Znów robimy to za pomocą dyrektywy <code class="language-plaintext highlighter-rouge">include</code> w Jekyllu zawartej w instrukcji <code class="language-plaintext highlighter-rouge">case</code>:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="o">%</span> <span class="k">case</span> <span class="nx">site</span><span class="p">.</span><span class="nx">comments</span><span class="p">.</span><span class="nx">provider</span> <span class="o">%</span><span class="p">}</span>
<span class="p">{</span><span class="o">%</span> <span class="nx">when</span> <span class="dl">"</span><span class="s2">disqus_empty</span><span class="dl">"</span> <span class="o">%</span><span class="p">}</span>
<span class="p">{</span><span class="o">%-</span> <span class="nx">include</span> <span class="nx">js</span><span class="o">/</span><span class="nx">disqus</span><span class="o">-</span><span class="nx">empty</span><span class="p">.</span><span class="nx">js</span> <span class="o">-%</span><span class="p">}</span>
<span class="p">{</span><span class="o">%</span> <span class="nx">endcase</span> <span class="o">%</span><span class="p">}</span>
</code></pre></div></div>
<h2 id="podsumowanie">Podsumowanie</h2>
<p>Który ze sposobów jest lepszy?
Dla mnie <code class="language-plaintext highlighter-rouge">scroll</code> ponieważ nie chciało mi się stylować przycisku ładowania komentarzy :D</p>
<p>Czy warto się tak męczyć dla <strong><a href="/tools/disqus">Disqus</a></strong>?
Jeszcze nie wiem.
Mam zamiar w najbliższym czasie przetestować też innych dostawców silników do komentarzy.</p>TheKamilAdamPonieważ korzystam z bloga opartego na generatorze statycznych stron Jekyll i nie mam żadnego backendu, nie mam możliwości samodzielnego zaimplementowania komentarzy. Jednak Minimal Mistakes, używany przeze mnie szablon, proponuje kilka rozwiązań tego problemu. Są to zewnętrzne systemy komentarzy wystawiające publiczne API jak: Disqus Discourse komentarze Facebooka komentarze utterances StaticmanFunktor, Monada i Aplikatywa w Haskellu2020-10-07T00:00:00+02:002020-10-07T00:00:00+02:00http://writeonly.pl/haskell-eta/functor-monad-applicative<p>Trzy <strong><a href="/tags/type-class">klasy typów</a></strong> <strong><a href="/tags/functor">Funktor</a></strong>, <strong><a href="/tags/monad">Monada</a></strong> i <strong><a href="/tags/applicative">Aplikatywa</a></strong> są to prawdopodobnie trzy najpopularniejsze klasy typów do przetwarzania danych.</p>
<h3 id="funktor-ang-functor">Funktor (ang. Functor)</h3>
<p><a href="https://hackage.haskell.org/package/base-4.14.0.0/docs/Data-Functor.html#t:Functor">Funktor w Haskellu</a>
jest prawdopodobnie najprostszą <strong><a href="/tags/type-class">klasą typu</a></strong> do przetwarzania danych.</p>
<p>Podstawą <strong><a href="/tags/functor">Funktora</a></strong> jest metoda <code class="language-plaintext highlighter-rouge">fmap</code>:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">fmap</span> <span class="o">::</span> <span class="p">(</span><span class="n">a</span> <span class="o">-></span> <span class="n">b</span><span class="p">)</span> <span class="o">-></span> <span class="p">(</span><span class="n">f</span> <span class="n">a</span> <span class="o">-></span> <span class="n">f</span> <span class="n">b</span><span class="p">)</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">fmap</code> może wydawać się bezsensowny,
ponieważ pobiera funkcję i zwraca funkcję.
Jeśli jednak zapiszemy tę sygnaturę inaczej,
to nabierze to większego sensu:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">fmap</span> <span class="o">::</span> <span class="p">(</span><span class="n">a</span> <span class="o">-></span> <span class="n">b</span><span class="p">)</span> <span class="o">-></span> <span class="n">f</span> <span class="n">a</span> <span class="o">-></span> <span class="n">f</span> <span class="n">b</span>
<span class="p">(</span><span class="o"><$></span><span class="p">)</span> <span class="o">::</span> <span class="p">(</span><span class="n">a</span> <span class="o">-></span> <span class="n">b</span><span class="p">)</span> <span class="o">-></span> <span class="n">f</span> <span class="n">a</span> <span class="o">-></span> <span class="n">f</span> <span class="n">b</span>
<span class="p">(</span><span class="o"><&></span><span class="p">)</span> <span class="o">::</span> <span class="n">f</span> <span class="n">a</span> <span class="o">-></span> <span class="p">(</span><span class="n">a</span> <span class="o">-></span> <span class="n">b</span><span class="p">)</span> <span class="o">-></span> <span class="n">f</span> <span class="n">b</span>
</code></pre></div></div>
<p>Teraz:</p>
<ul>
<li>Funkcja <code class="language-plaintext highlighter-rouge">fmap</code> pobiera dwa argumenty, funkcję mapującą i funktor do przemapowania.</li>
<li>Operator <code class="language-plaintext highlighter-rouge">(<$>)</code> podobnie jak operator <code class="language-plaintext highlighter-rouge">($)</code> pozwala pomijać nawiasy.</li>
<li>Operator <code class="language-plaintext highlighter-rouge">(<&>)</code> podobnie jak operator <code class="language-plaintext highlighter-rouge">(&)</code> pozwala pisać kod w stylu bardziej obiektowym.</li>
</ul>
<p>Przykłady użycia:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">fmap</span> <span class="p">(</span><span class="n">function1</span> <span class="n">data1</span><span class="p">)</span>
<span class="n">fmap</span> <span class="o">$</span> <span class="n">function1</span> <span class="n">data1</span>
<span class="n">function1</span> <span class="o"><$></span> <span class="n">data1</span>
<span class="n">data1</span> <span class="o"><&></span> <span class="n">function1</span>
</code></pre></div></div>
<p>W moim parserze języka <strong><a href="/eso/eta">ETA</a></strong> preferuje operator <code class="language-plaintext highlighter-rouge">(<$>)</code>:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">unescapedStringParser</span> <span class="o">::</span> <span class="kt">Parser</span> <span class="kt">Instruction</span>
<span class="n">unescapedStringParser</span> <span class="o">=</span> <span class="kt">U</span> <span class="o"><$></span> <span class="n">stringParser</span>
<span class="n">labelDefinitionParser</span> <span class="o">::</span> <span class="kt">Parser</span> <span class="kt">Instruction</span>
<span class="n">labelDefinitionParser</span> <span class="o">=</span> <span class="kt">L</span> <span class="o"><$></span> <span class="p">(</span><span class="n">char</span> <span class="sc">'>'</span> <span class="o">*></span> <span class="n">identifierParser</span> <span class="o"><*</span> <span class="n">char</span> <span class="sc">':'</span><span class="p">)</span>
<span class="n">includeFileParser</span> <span class="o">::</span> <span class="kt">Parser</span> <span class="kt">Instruction</span>
<span class="n">includeFileParser</span> <span class="o">=</span> <span class="kt">D</span> <span class="o"><$></span> <span class="p">(</span><span class="n">char</span> <span class="sc">'*'</span> <span class="o">*></span> <span class="n">fileNameParser</span> <span class="o"><*</span> <span class="n">char</span> <span class="sc">'</span><span class="se">\n</span><span class="sc">'</span><span class="p">)</span>
</code></pre></div></div>
<p>Są jeszcze dwa pomocnicze operatory różniące się kolejnością argumentów:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="o">$></span><span class="p">)</span> <span class="o">::</span> <span class="n">f</span> <span class="n">a</span> <span class="o">-></span> <span class="n">b</span> <span class="o">-></span> <span class="n">f</span> <span class="n">b</span>
<span class="p">(</span><span class="o"><$</span><span class="p">)</span> <span class="o">::</span> <span class="n">a</span> <span class="o">-></span> <span class="n">f</span> <span class="n">b</span> <span class="o">-></span> <span class="n">f</span> <span class="n">a</span>
</code></pre></div></div>
<p>Przykład użycia:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">>>></span> <span class="kt">Nothing</span> <span class="o">$></span> <span class="s">"Haskell"</span>
<span class="kt">Nothing</span>
<span class="o">>>></span> <span class="kt">Just</span> <span class="s">"Scala"</span> <span class="o">$></span> <span class="s">"Haskell"</span>
<span class="kt">Just</span> <span class="s">"haskell"</span>
<span class="o">>>></span> <span class="s">"Haskell"</span> <span class="o"><$</span> <span class="kt">Nothing</span>
<span class="kt">Nothing</span>
<span class="o">>>></span> <span class="s">"Haskell"</span> <span class="o"><$</span> <span class="kt">Just</span> <span class="s">"Scala"</span>
<span class="kt">Just</span> <span class="s">"Haskell"</span>
</code></pre></div></div>
<p>W moim <strong><a href="/tags/parser">parserze</a></strong> języka <strong><a href="/eso/eta">ETA</a></strong> preferuje operator <code class="language-plaintext highlighter-rouge">(<$)</code>:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">zeroOperandInstructionParser</span> <span class="o">::</span> <span class="kt">Parser</span> <span class="kt">Instruction</span>
<span class="n">zeroOperandInstructionParser</span> <span class="o">=</span>
<span class="n">zeroOperandInstruction</span> <span class="kt">E</span> <span class="p">[</span><span class="s">"E"</span><span class="p">,</span> <span class="s">"dividE"</span><span class="p">]</span>
<span class="o"><|></span> <span class="n">zeroOperandInstruction</span> <span class="kt">T</span> <span class="p">[</span><span class="s">"T"</span><span class="p">,</span> <span class="s">"Transfer"</span><span class="p">]</span>
<span class="o"><|></span> <span class="n">zeroOperandInstruction</span> <span class="kt">A</span> <span class="p">[</span><span class="s">"A"</span><span class="p">,</span> <span class="s">"Address"</span><span class="p">]</span>
<span class="o"><|></span> <span class="n">zeroOperandInstruction</span> <span class="kt">O</span> <span class="p">[</span><span class="s">"O"</span><span class="p">,</span> <span class="s">"Output"</span><span class="p">]</span>
<span class="o"><|></span> <span class="n">zeroOperandInstruction</span> <span class="kt">I</span> <span class="p">[</span><span class="s">"I"</span><span class="p">,</span> <span class="s">"Input"</span><span class="p">]</span>
<span class="o"><|></span> <span class="n">zeroOperandInstruction</span> <span class="kt">S</span> <span class="p">[</span><span class="s">"S"</span><span class="p">,</span> <span class="s">"Subtract"</span><span class="p">]</span>
<span class="o"><|></span> <span class="n">zeroOperandInstruction</span> <span class="kt">H</span> <span class="p">[</span><span class="s">"H"</span><span class="p">,</span> <span class="s">"Halibut"</span><span class="p">]</span>
<span class="kr">where</span> <span class="n">zeroOperandInstruction</span> <span class="n">i</span> <span class="n">ts</span> <span class="o">=</span> <span class="n">i</span> <span class="o"><$</span> <span class="p">(</span><span class="n">asciiCIChoices</span> <span class="n">ts</span> <span class="o">*></span> <span class="n">endWordParser</span><span class="p">)</span>
</code></pre></div></div>
<h3 id="monada-ang-monad">Monada (ang. Monad)</h3>
<p><a href="https://hackage.haskell.org/package/base-4.14.0.0/docs/Control-Monad.html#t:Monad">Monada w Haskellu</a>
jest bazą dla całej rodziny klas typów.
To właśnie nią straszy się niegrzeczne dzieci.
W praktyce <strong><a href="/tags/monad">Monada</a></strong> jest bardzo prosta i sprowadza się do zdefiniowania jednego operatora,
który występuje w dwóch wersjach:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="o">>>=</span><span class="p">)</span> <span class="o">::</span> <span class="n">m</span> <span class="n">a</span> <span class="o">-></span> <span class="p">(</span><span class="n">a</span> <span class="o">-></span> <span class="n">m</span> <span class="n">b</span><span class="p">)</span> <span class="o">-></span> <span class="n">m</span> <span class="n">b</span>
<span class="p">(</span><span class="o">=<<</span><span class="p">)</span> <span class="o">::</span> <span class="p">(</span><span class="n">a</span> <span class="o">-></span> <span class="n">m</span> <span class="n">b</span><span class="p">)</span> <span class="o">-></span> <span class="n">m</span> <span class="n">a</span> <span class="o">-></span> <span class="n">m</span> <span class="n">b</span>
</code></pre></div></div>
<p>Pozwalają one na łączenie dwóch monad w jedną monadę.</p>
<p>Tu można się zdziwić,
bo dokumentacja <strong><a href="/langs/haskell">Haskella</a></strong> preferuje operator <code class="language-plaintext highlighter-rouge">(>>=)</code>.
Mnie jednak bardziej pasuje operator <code class="language-plaintext highlighter-rouge">(=<<)</code>,
ponieważ pozwala czytać kod od prawej do lewej,
podobnie jak operatory <code class="language-plaintext highlighter-rouge">($)</code> i <code class="language-plaintext highlighter-rouge">(<$>)</code>.</p>
<p>Przykład z asemblera języka <strong><a href="/eso/eta">ETA</a></strong> wygląda następująco:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">replaceStrings</span> <span class="o">::</span> <span class="kt">InstructionList</span> <span class="o">-></span> <span class="kt">InstructionList</span>
<span class="n">replaceStrings</span> <span class="n">il</span> <span class="o">=</span> <span class="n">replaceString</span> <span class="o">=<<</span> <span class="n">il</span>
<span class="n">replaceString</span> <span class="o">::</span> <span class="kt">Instruction</span> <span class="o">-></span> <span class="kt">InstructionList</span>
</code></pre></div></div>
<h3 id="aplikatywa-ang-applicative">Aplikatywa (ang. Applicative)</h3>
<p><a href="https://hackage.haskell.org/package/base-4.14.0.0/docs/Control-Applicative.html#t:Applicative">Aplikatywa w Haskellu</a> jest klasą pośrednią między Funktorem a Monadą.</p>
<p><strong><a href="/tags/applicative">Aplikatywa</a></strong> dziedziczy z <strong><a href="/tags/functor">Funktora</a></strong>,
a <strong><a href="/tags/monad">Monada</a></strong> dziedziczy z <strong><a href="/tags/applicative">Aplikatywy</a></strong>.
W zasadzie jest to <strong><a href="/tags/monad">Monada</a></strong> bez operatorów <code class="language-plaintext highlighter-rouge">(>>=)</code> i <code class="language-plaintext highlighter-rouge">(=<<)</code>,
czyli bez możliwości składania.</p>
<p><strong><a href="/tags/applicative">Aplikatywa</a></strong> posiada następujące operacje:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">pure</span> <span class="o">::</span> <span class="n">a</span> <span class="o">-></span> <span class="n">f</span> <span class="n">a</span>
<span class="p">(</span><span class="o"><*></span><span class="p">)</span> <span class="o">::</span> <span class="n">f</span> <span class="p">(</span><span class="n">a</span> <span class="o">-></span> <span class="n">b</span><span class="p">)</span> <span class="o">-></span> <span class="n">f</span> <span class="n">a</span> <span class="o">-></span> <span class="n">f</span> <span class="n">b</span>
<span class="p">(</span><span class="o"><**></span><span class="p">)</span> <span class="o">::</span> <span class="n">f</span> <span class="n">a</span> <span class="o">-></span> <span class="n">f</span> <span class="p">(</span><span class="n">a</span> <span class="o">-></span> <span class="n">b</span><span class="p">)</span> <span class="o">-></span> <span class="n">f</span> <span class="n">b</span>
</code></pre></div></div>
<p>Gdzie:</p>
<ul>
<li>Funkcja <code class="language-plaintext highlighter-rouge">pure</code> pozwala utworzyć <strong><a href="/tags/applicative">Aplikatywę</a></strong>.</li>
<li>Operator <code class="language-plaintext highlighter-rouge">(<*>)</code> jest bardzo podobny do <code class="language-plaintext highlighter-rouge">(<$>)</code>, ale tutaj także funkcja jest opakowana w kontekst.</li>
<li>Operator <code class="language-plaintext highlighter-rouge">(<**>)</code> podobnie jak operator <code class="language-plaintext highlighter-rouge">(<&)</code> pozwala na zapis w stylu bardziej obiektowym.</li>
</ul>
<p>Są jeszcze dwa operatory pomocnicze:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="o">*></span><span class="p">)</span> <span class="o">::</span> <span class="n">f</span> <span class="n">a</span> <span class="o">-></span> <span class="n">f</span> <span class="n">b</span> <span class="o">-></span> <span class="n">f</span> <span class="n">b</span>
<span class="p">(</span><span class="o"><*</span><span class="p">)</span> <span class="o">::</span> <span class="n">f</span> <span class="n">a</span> <span class="o">-></span> <span class="n">f</span> <span class="n">b</span> <span class="o">-></span> <span class="n">f</span> <span class="n">a</span>
</code></pre></div></div>
<p>Gdzie:</p>
<ul>
<li>Operator <code class="language-plaintext highlighter-rouge">(*>)</code> pozwala odrzucić pierwszy argument.</li>
<li>Operator <code class="language-plaintext highlighter-rouge">(<*)</code> pozwala odrzucić drugi argument.</li>
</ul>
<p>Tu kolejność jest ważna.
Pierwszy ignoruje pierwszą wartość.
Drugi operator ignoruje drugą wartość.</p>
<p>W przeciwieństwie do poprzednich par operatorów,
oba te operatory są potrzebne.</p>
<p>Przykład z asemblera języka <strong><a href="/eso/eta">ETA</a></strong> wygląda następująco:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">labelDefinitionParser</span> <span class="o">::</span> <span class="kt">Parser</span> <span class="kt">Instruction</span>
<span class="n">labelDefinitionParser</span> <span class="o">=</span> <span class="kt">L</span> <span class="o"><$></span> <span class="p">(</span><span class="n">char</span> <span class="sc">'>'</span> <span class="o">*></span> <span class="n">identifierParser</span> <span class="o"><*</span> <span class="n">char</span> <span class="sc">':'</span><span class="p">)</span>
<span class="n">includeFileParser</span> <span class="o">::</span> <span class="kt">Parser</span> <span class="kt">Instruction</span>
<span class="n">includeFileParser</span> <span class="o">=</span> <span class="kt">D</span> <span class="o"><$></span> <span class="p">(</span><span class="n">char</span> <span class="sc">'*'</span> <span class="o">*></span> <span class="n">fileNameParser</span> <span class="o"><*</span> <span class="n">char</span> <span class="sc">'</span><span class="se">\n</span><span class="sc">'</span><span class="p">)</span>
</code></pre></div></div>
<h3 id="problemy-z-aplikatywą">Problemy z Aplikatywą</h3>
<p>Nie zawsze było tak,
że <strong><a href="/tags/applicative">Aplikatywa</a></strong> był klasą bazową dla <strong><a href="/tags/monad">Monady</a></strong>.
Aplikatywa została dodany później w związku z pracami nad <strong><a href="/tags/parser">parserami</a></strong> w <strong><a href="/langs/haskell">Haskell</a></strong>.</p>
<p>Z tego powodu niektóre operacje z <strong><a href="/tags/applicative">Aplikatywy</a></strong> są zdupkiowane w <strong><a href="/tags/monad">Monadzie</a></strong>.
Są to:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">pure</span> <span class="o">=</span> <span class="n">return</span>
<span class="p">(</span><span class="o"><*></span><span class="p">)</span> <span class="o">=</span> <span class="n">ap</span>
<span class="p">(</span><span class="o">>></span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="o">*></span><span class="p">)</span>
</code></pre></div></div>
<p>Zawsze należy dbać o to,
żeby operacje te posiadały te same implementacje.</p>
<h3 id="szczegóły-implementacyjne-w-standardowej-bibliotece-haskella">Szczegóły implementacyjne w standardowej bibliotece Haskella</h3>
<p>Nie wszystkie podane przeze mnie powyżej metody są zdefiniowane w <strong><a href="/tags/type-class">klasach typów</a></strong>.</p>
<ul>
<li><strong><a href="/tags/functor">Funktor</a></strong> posiada tylko metody <code class="language-plaintext highlighter-rouge">fmap</code> i <code class="language-plaintext highlighter-rouge">(<$)</code>,
przy czym do minimalnej definicji wystarczy tylko implementacja <code class="language-plaintext highlighter-rouge">fmap</code>.
<code class="language-plaintext highlighter-rouge">(<$>)</code> i <code class="language-plaintext highlighter-rouge">($>)</code> są funkcjami przyjmującymi <strong><a href="/tags/functor">Funktor</a></strong>.</li>
<li><strong><a href="/tags/applicative">Aplikatywa</a></strong> posiada tylko metody <code class="language-plaintext highlighter-rouge">pure</code>, <code class="language-plaintext highlighter-rouge">(<*>)</code>, <code class="language-plaintext highlighter-rouge">lift</code>, <code class="language-plaintext highlighter-rouge">(*>)</code> oraz <code class="language-plaintext highlighter-rouge">(*>)</code>.
przy czym do minimalnej definicji wystarczy tylko implementacja <code class="language-plaintext highlighter-rouge">pure</code> oraz <code class="language-plaintext highlighter-rouge">(<*>)</code> lub <code class="language-plaintext highlighter-rouge">liftA2</code>.
<code class="language-plaintext highlighter-rouge">(<**>)</code> jest dostarczana jako funkcja przyjmująca <strong><a href="/tags/applicative">Aplikatywę</a></strong>.</li>
<li><strong><a href="/tags/monad">Monad</a></strong> posiada tylko metody <code class="language-plaintext highlighter-rouge">(>>=)</code>, <code class="language-plaintext highlighter-rouge">(>>)</code>, <code class="language-plaintext highlighter-rouge">return</code> i <code class="language-plaintext highlighter-rouge">fail</code> ,
przy czym do minimalnej definicji wystarczy tylko implementacja <code class="language-plaintext highlighter-rouge">(>>=)</code>.
<code class="language-plaintext highlighter-rouge">(=<<)</code>, <code class="language-plaintext highlighter-rouge">ap</code> i wiele innych są dostarczane jako funkcje przyjmujące <strong><a href="/tags/monad">Monadę</a></strong></li>
</ul>
<h3 id="inne-problemy-i-inne-implementacje">Inne problemy i inne implementacje</h3>
<p>Nie tylko hierarchia dziedziczenia między <strong><a href="/tags/applicative">Aplikatywą</a></strong> a <strong><a href="/tags/monad">Monadą</a></strong> jest zepsuta w <strong><a href="/langs/haskell">Haskellu</a></strong>.
Ogólnie cała hierarchia dziedziczenia jest popsuta (nie zawiera odpowiednio dużo kroków pośrednich).
Na szczęście jest biblioteka <a href="https://hackage.haskell.org/package/semigroupoids">semigroupoids</a>,
która rozwiązuje ten problem.
Implementuje ona wszystkie teoretycznie istniejące kroki pośrednie:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Foldable ----> Traversable <--- Functor ------> Alt ---------> Plus Semigroupoid
| | | | |
v v v v v
Foldable1 ---> Traversable1 Apply --------> Applicative -> Alternative Category
| | | |
v v v v
Bind ---------> Monad -------> MonadPlus Arrow
</code></pre></div></div>
<p>Jest tylko jeden problem.
Posiada ona własną definicję <a href="https://hackage.haskell.org/package/semigroupoids/docs/Data-Functor-Apply.html#t:Functor">Funktora</a>.</p>
<h2 id="podsumowanie">Podsumowanie</h2>
<p><strong><a href="/langs/haskell">Haskell</a></strong> jest wspaniałym językiem programowania,
ale niestety nie wszystko w nim jest idealne.
Zmiany są jednak wprowadzane stopniowo,
nawet jeśli wiąże się to ze złamaniem kompatybilności.</p>TheKamilAdamTrzy klasy typów Funktor, Monada i Aplikatywa są to prawdopodobnie trzy najpopularniejsze klasy typów do przetwarzania danych.Problem wywołań cebulowych w Haskellu2020-09-02T00:00:00+02:002020-09-02T00:00:00+02:00http://writeonly.pl/haskell-eta/pipe-operators<p>Przez <code class="language-plaintext highlighter-rouge">Problem wywołań cebulowych</code> rozumiem sytuację zagnieżdżonego wywoływania różnego rodzaju funkcji jak:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">someFunction</span> <span class="n">someData</span> <span class="o">=</span> <span class="n">thirdFunction</span> <span class="p">(</span><span class="n">secondFunction</span> <span class="p">(</span><span class="n">firstFunction</span> <span class="n">someData</span><span class="p">))</span>
</code></pre></div></div>
<p>Duża ilość wywołań powoduje dużą ilość nawiasów na końcu:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">f</span> <span class="n">x</span> <span class="o">=</span> <span class="n">f9</span> <span class="p">(</span><span class="n">f8</span> <span class="p">(</span><span class="n">f7</span> <span class="p">(</span><span class="n">f6</span> <span class="p">(</span><span class="n">f5</span> <span class="p">(</span><span class="n">f4</span> <span class="p">(</span><span class="n">f3</span> <span class="p">(</span><span class="n">f2</span> <span class="p">(</span><span class="n">f1</span> <span class="p">(</span><span class="n">f0</span> <span class="n">x</span><span class="p">)))))))))</span>
</code></pre></div></div>
<p>co jest mało czytelne.</p>
<p>W <strong><a href="/langs/haskell">Haskellu</a></strong> można zapisać to trochę czytelniej za pomocą klauzuli <code class="language-plaintext highlighter-rouge">where</code>:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">someFunction</span> <span class="n">someData</span> <span class="o">=</span> <span class="n">thirdFunction</span> <span class="n">secondDate</span> <span class="kr">where</span>
<span class="n">secondData</span> <span class="o">=</span> <span class="n">secondFunction</span> <span class="n">firstData</span>
<span class="n">firstData</span> <span class="o">=</span> <span class="n">firstFunction</span> <span class="n">someData</span>
</code></pre></div></div>
<p>Niestety klauzula <code class="language-plaintext highlighter-rouge">where</code> może powodować dużą rozwlekłość kodu:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">f</span> <span class="n">x</span> <span class="o">=</span> <span class="n">f9</span> <span class="n">x8</span> <span class="kr">where</span>
<span class="n">x8</span> <span class="o">=</span> <span class="n">f8</span> <span class="n">x7</span>
<span class="n">x7</span> <span class="o">=</span> <span class="n">f7</span> <span class="n">x6</span>
<span class="n">x6</span> <span class="o">=</span> <span class="n">f6</span> <span class="n">x5</span>
<span class="n">x5</span> <span class="o">=</span> <span class="n">f5</span> <span class="n">x4</span>
<span class="n">x4</span> <span class="o">=</span> <span class="n">f4</span> <span class="n">x3</span>
<span class="n">x3</span> <span class="o">=</span> <span class="n">f3</span> <span class="n">x2</span>
<span class="n">x2</span> <span class="o">=</span> <span class="n">f2</span> <span class="n">x1</span>
<span class="n">x1</span> <span class="o">=</span> <span class="n">f1</span> <span class="n">x</span>
</code></pre></div></div>
<p>Dodatkowo tworzymy dużą ilość pośrednich zmiennych,
które tylko zaciemniają kod.</p>
<p>W językach obiektowych (lub wspierających notację obiektową jak <strong><a href="/langs/rust">Rust</a></strong>) mamy szansę na lepszy zapis,
jeśli <code class="language-plaintext highlighter-rouge">firstFunction</code> jest metodą obiektu <code class="language-plaintext highlighter-rouge">firstData</code>,
<code class="language-plaintext highlighter-rouge">secondFunction</code> jest metodą obiektu <code class="language-plaintext highlighter-rouge">secondData</code>,
a <code class="language-plaintext highlighter-rouge">thirdFunction</code> jest metodą obiektu <code class="language-plaintext highlighter-rouge">thirdData</code>:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">someFunction</span><span class="o">(</span><span class="n">someDate</span><span class="k">:</span> <span class="kt">SomeDate</span><span class="o">)</span><span class="k">:</span> <span class="kt">SomeResult</span> <span class="o">=</span> <span class="n">someData</span>
<span class="o">.</span><span class="py">firstFunction</span><span class="o">()</span>
<span class="o">.</span><span class="py">secondFunction</span><span class="o">()</span>
<span class="o">.</span><span class="py">thirdFunction</span><span class="o">()</span>
</code></pre></div></div>
<p>Niestety nie zawsze mamy takie szczęście i wtedy kod wygląda mniej więcej tak:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">someFunction</span><span class="o">(</span><span class="n">someData</span><span class="k">:</span> <span class="kt">SomeDate</span><span class="o">)</span><span class="k">:</span> <span class="kt">SomeResult</span> <span class="o">=</span> <span class="o">{</span>
<span class="k">val</span> <span class="nv">firstData</span> <span class="k">=</span> <span class="nf">firstFunction</span><span class="o">(</span><span class="n">someData</span><span class="o">)</span>
<span class="k">val</span> <span class="nv">secondDate</span> <span class="k">=</span> <span class="nf">secondFunction</span><span class="o">(</span><span class="n">firstData</span><span class="o">)</span>
<span class="nf">third_function</span><span class="o">(</span><span class="n">secondDate</span><span class="o">)</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Lub w krótszej formie bez zmiennych pośrednich tak:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">someFunction</span><span class="o">(</span><span class="n">someData</span><span class="k">:</span> <span class="kt">SomeDate</span><span class="o">)</span><span class="k">:</span> <span class="kt">SomeResult</span> <span class="o">=</span> <span class="nf">third_function</span><span class="o">(</span><span class="nf">secondFunction</span><span class="o">(</span><span class="nf">firstData</span><span class="o">(</span><span class="n">someData</span><span class="o">)))</span>
</code></pre></div></div>
<p>W <strong><a href="/langs/scala">Scali</a></strong> można jeszcze próbować dodać metodę do obiektu za pomocą konwersji <code class="language-plaintext highlighter-rouge">implicit</code>,
jednak może wymagać to dużej ilości kodu,
a zysk jest raczej mały.
Chociaż czasem jest to używane w bibliotekach jak <a href="/libs/scalaz">Scalaz</a> czy <a href="/libs/zio">Zio</a>.</p>
<p>Na szczęście można rozwiązać to za pomocą operatora potoku,
zwanego też w <strong><a href="/langs/scala">Scali</a></strong> operatorem drozda:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">someFunction</span><span class="o">(</span><span class="n">someData</span><span class="k">:</span> <span class="kt">SomeDate</span><span class="o">)</span><span class="k">:</span> <span class="kt">someData</span> <span class="kt">|></span> <span class="kt">firstFunction</span> <span class="kt">|></span> <span class="kt">secondFunction</span> <span class="kt">|></span> <span class="kt">thirdFunction</span>
</code></pre></div></div>
<p>Pisałem o tym w artykule <a href="/scalapipe">Problem wywołań cebulowych w Scali</a>.</p>
<h2 id="operatory-aplikacji-i-kombinacji">Operatory aplikacji (i kombinacji)</h2>
<p><strong><a href="/langs/haskell">Haskell</a></strong> także posiada swoje operatory potoku.
Jednak dla <em>utrudnienia</em> nazywają się one operatorami aplikacji.
Znajdują się one w module <a href="https://hackage.haskell.org/package/base-4.14.0.0/docs/Data-Function.html">Data.Function</a>.</p>
<p>Podobnie jak w przypadku języków <strong><a href="/langs/scala">Scala</a></strong> czy <strong><a href="/langs/ocaml">OCaml</a></strong>,
w <strong><a href="/langs/haskell">Haskell</a></strong> operatory te nie są częścią składni języka,
tylko zdefiniowane są tak jak funkcje.
Dzięki temu,
że te języki programowania mają elastyczną składnię można definiować własne operatory.</p>
<p>W dalszej części artykułu będę poszukiwać czytelniejszej formy dla funkcji:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">f</span> <span class="n">x</span> <span class="o">=</span> <span class="n">f3</span> <span class="p">(</span><span class="n">f2</span> <span class="p">(</span><span class="n">f1</span> <span class="n">x</span><span class="p">))</span>
</code></pre></div></div>
<h3 id="operator-dolara-ang-dollar-operator">Operator dolara (ang. <em>Dollar operator</em>)</h3>
<p>Pierwszym operatorem aplikacji jest operator dolara <code class="language-plaintext highlighter-rouge">($)</code>.
Operator ten robi “apply forward” zwane też “pipe into”.
Definicja:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="o">$</span><span class="p">)</span> <span class="o">::</span> <span class="p">(</span><span class="n">a</span> <span class="o">-></span> <span class="n">b</span><span class="p">)</span> <span class="o">-></span> <span class="p">(</span><span class="n">a</span> <span class="o">-></span> <span class="n">b</span><span class="p">)</span>
</code></pre></div></div>
<p>Użycie operatora dolara wygląda następująco:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">f</span> <span class="n">d</span> <span class="o">=</span> <span class="n">f3</span> <span class="o">$</span> <span class="n">f2</span> <span class="o">$</span> <span class="n">f1</span> <span class="n">d</span>
</code></pre></div></div>
<p>Jest to odpowiednik z innych języków programowania:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">f</span> <span class="n">d</span> <span class="o">=</span> <span class="n">f3</span> <span class="o"><|</span> <span class="n">f2</span> <span class="o"><|</span> <span class="n">f1</span> <span class="o"><|</span> <span class="n">d</span>
</code></pre></div></div>
<p>Powyższy zapis jest czytelniejszy niż zapis nawiasowy,
jednak dalej wymaga czytania od prawej do lewej,
co jest nienaturalne.</p>
<h3 id="operator-et-ang-ampersand-operator">Operator et (ang. <em>Ampersand operator</em>)</h3>
<p>Drugim operatorem aplikacji jest operator et <code class="language-plaintext highlighter-rouge">(&)</code>.
Operator ten robi “apply backward” zwane też “pipe from”.
Definicja:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">a</span> <span class="o">-></span> <span class="p">(</span><span class="n">a</span> <span class="o">-></span> <span class="n">b</span><span class="p">)</span> <span class="o">-></span> <span class="n">b</span>
</code></pre></div></div>
<p>Użycie operatora et wygląda następująco:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">f</span> <span class="n">d</span> <span class="o">=</span> <span class="n">d</span> <span class="o">&</span> <span class="n">f1</span> <span class="o">&</span> <span class="n">f2</span> <span class="o">&</span> <span class="n">f3</span>
</code></pre></div></div>
<p>Jest to odpowiednik z innych języków programowania:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">f</span> <span class="n">d</span> <span class="o">=</span> <span class="n">d</span> <span class="o">|></span> <span class="n">f1</span> <span class="o">|></span> <span class="n">f2</span> <span class="o">|></span> <span class="n">f3</span>
</code></pre></div></div>
<p>Jest to czytelniejsza postać dla użytkowników języków obiektowych oraz języka <strong><a href="/langs/rust">Rust</a></strong>,
jednak w Haskellu rzadko używana.</p>
<h3 id="operator-kropki-ang-dot-operator">Operator kropki (ang. <em>Dot operator</em>)</h3>
<p>Operator kropki <code class="language-plaintext highlighter-rouge">(.)</code> służy do składania funkcji (ang. <em><a href="https://wiki.haskell.org/Function_composition">Function composition</a></em>).
Jest to operator kompozycji.
Definicja:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="o">.</span><span class="p">)</span> <span class="o">::</span> <span class="p">(</span><span class="n">b</span> <span class="o">-></span> <span class="n">c</span><span class="p">)</span> <span class="o">-></span> <span class="p">(</span><span class="n">a</span> <span class="o">-></span> <span class="n">b</span><span class="p">)</span> <span class="o">-></span> <span class="n">a</span> <span class="o">-></span> <span class="n">c</span>
</code></pre></div></div>
<p>I użycie w <strong><a href="/langs/haskell">Haskellu</a></strong> jakiego moglibyśmy się spodziewać:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">f</span> <span class="n">d</span> <span class="o">=</span> <span class="p">(</span><span class="n">f3</span> <span class="o">.</span> <span class="n">f2</span> <span class="o">.</span> <span class="n">f1</span><span class="p">)</span> <span class="n">d</span>
</code></pre></div></div>
<p>Jednak nie jest to to,
czego można by spodziewać się po programistach <strong><a href="/langs/haskell">Haskella</a></strong>.
Haskell pozwala na <em><a href="https://wiki.haskell.org/Pointfree">PointFree</a> Style</em>,
czyli możliwość niezapisywania argumentów:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">f</span> <span class="o">=</span> <span class="n">f3</span> <span class="o">.</span> <span class="n">f2</span> <span class="o">.</span> <span class="n">f1</span>
</code></pre></div></div>
<p>Styl PointFree bywa też nazywany stylem Pointless,
ponieważ jest oskarżany o zaciemnianie kodu.</p>
<h2 id="pakiet-flow">Pakiet Flow</h2>
<p>Pakiet <a href="https://hackage.haskell.org/package/flow-1.0.21/docs/Flow.html">Flow</a> pozwala używać operatorów znanych z innych języków programowania,
takich jak <strong><a href="/langs/scala">Scala</a></strong>, <strong><a href="/langs/ocaml">OCaml</a></strong> czy <strong><a href="/langs/elixir">Elixir</a></strong>.
Definiuje on dwa operatory aplikacji oraz dwa operatory kompozycji,
które w uproszczeniu można wyjaśnić jako:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="o"><|</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="o">$</span><span class="p">)</span> <span class="c1">-- "apply forward" or "pipe into"</span>
<span class="p">(</span><span class="o">|></span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="o">&</span><span class="p">)</span> <span class="c1">-- "apply backward" or "pipe from"</span>
<span class="p">(</span><span class="o"><.</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="o">.</span><span class="p">)</span> <span class="c1">-- "compose backward" or "but first"</span>
<span class="p">(</span><span class="o">.></span><span class="p">)</span> <span class="o">=</span> <span class="n">flip</span> <span class="p">(</span><span class="o">.</span><span class="p">)</span> <span class="c1">-- "compose forward" or "and then"</span>
</code></pre></div></div>
<p>Dzięki tym operatorom możemy zdefiniować złożenie funkcji na cztery różne czytelne sposoby:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">f</span> <span class="n">x</span> <span class="o">=</span> <span class="n">f3</span> <span class="p">(</span><span class="n">f2</span> <span class="p">(</span><span class="n">f1</span> <span class="n">x</span><span class="p">))</span> <span class="c1">-- normalny zapis</span>
<span class="n">f</span> <span class="n">x</span> <span class="o">=</span> <span class="n">f3</span> <span class="o"><|</span> <span class="n">f2</span> <span class="o"><|</span> <span class="n">f1</span> <span class="o"><|</span> <span class="n">x</span> <span class="c1">-- apply forward -- jak w Elixirze</span>
<span class="n">f</span> <span class="n">x</span> <span class="o">=</span> <span class="n">x</span> <span class="o">|></span> <span class="n">f1</span> <span class="o">|></span> <span class="n">f2</span> <span class="o">|></span> <span class="n">f3</span> <span class="c1">-- apply backward</span>
<span class="n">f</span> <span class="o">=</span> <span class="n">f3</span> <span class="o"><.</span> <span class="n">f2</span> <span class="o"><.</span> <span class="n">f1</span> <span class="c1">-- compose backward -- bardziej po Haskelowemu</span>
<span class="n">f</span> <span class="o">=</span> <span class="n">f1</span> <span class="o">.></span> <span class="n">f2</span> <span class="o">.></span> <span class="n">f3</span> <span class="c1">-- compose forward</span>
</code></pre></div></div>
<h2 id="podsumowanie">Podsumowanie</h2>
<p><strong><a href="/langs/haskell">Haskell</a></strong> posiada wiele operatorów pozwalających na redukcję nawiasów podczas wywoływania funkcji.
Samodzielnie (lub w zespole) należy ustalić,
który ze styli jest najbardziej czytelny dla nas.</p>TheKamilAdamPrzez Problem wywołań cebulowych rozumiem sytuację zagnieżdżonego wywoływania różnego rodzaju funkcji jak: someFunction someData = thirdFunction (secondFunction (firstFunction someData))