221 lines
22 KiB
HTML
221 lines
22 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width">
|
|
<title>Caught in the Net (old posts, page 19) | Caught in the Net</title>
|
|
<link rel="stylesheet" href="assets/blog/fonts/opensans.css">
|
|
<link href="assets/blog/css/normalize.css" rel="stylesheet" type="text/css">
|
|
<link href="assets/blog/css/cayman.css" rel="stylesheet" type="text/css">
|
|
<meta name="theme-color" content="#5670d4">
|
|
<meta name="generator" content="Nikola (getnikola.com)">
|
|
<link rel="alternate" type="application/rss+xml" title="RSS" hreflang="en" href="rss.xml">
|
|
<link rel="canonical" href="francescomecca.eu/index-19.html">
|
|
<link rel="prev" href="." type="text/html">
|
|
<link rel="next" href="index-18.html" type="text/html">
|
|
</head>
|
|
<body>
|
|
<div id="container">
|
|
|
|
<section class="page-header"><h1 class="project-name">
|
|
Caught in the Net
|
|
</h1>
|
|
<h2 class="project-tagline">La rete ti cattura ma libera il pensiero</h2>
|
|
<a class="btn" href=".">Home</a>
|
|
<a class="btn" href="pages/about/">About me</a>
|
|
<a class="btn" href="pages/contattami/">Contact me</a>
|
|
<a class="btn" href="archiveall.html">Archive</a>
|
|
<a class="btn" href="rss.xml">RSS</a>
|
|
<a class="btn" href="http://francescomecca.eu/git/pesceWanda">Personal Git</a>
|
|
<a class="btn" href="https://github.com/FraMecca">Github</a>
|
|
<a class="btn" href="https://lezzo.org/git/public/Curriculum_vitae/raw/master/latex.dir/francesco_mecca_cv_eng.pdf">Curriculum</a>
|
|
</section><section class="main-content"><div class="posts">
|
|
<article class="post"><header><h1 class="post-title"><a href="blog/2018/07/27/addio-reddit/" class="u-url">Un articolo per r/italyinformatica</a></h1>
|
|
</header><div>
|
|
<span class="post-date">27 July 2018</span>
|
|
</div>
|
|
<br><div class="e-content entry-content">
|
|
<p>Questo articolo è stato originalmente scritto per il <a href="https://tldr.italyinformatica.org">blog</a> di <a href="https://reddit.com/r/italyinformatica">r/italyinformatica</a>.</p>
|
|
<p>Negli ultimi anni abbiamo assistito all'ascesa di un gran numero di linguaggi di programmazione, in particolare <a href="https://golang.rg">Go</a> (2009), <a href="https://www.rust-lang.org/en-US/">Rust</a> (2010), <a href="https://kotlinlang.org/">Kotlin</a> (2011), <a href="https://elixir-lang.org/">Elixir</a> (2011), <a href="https://crystal-lang.org/">Crystal</a> (2014), <a href="https://www.ponylang.or">Pony</a> (2014).</p>
|
|
<p>Questa esplosione di nuovi linguaggi è dovuta, fra le molte motivazioni, alla necessità di adottare paradigmi di programmazione non immediatamente recenti come cittadini di primo tipo.</p>
|
|
<p>Rispetto ai più maturi C, C++ o Java, Python o Ruby questi linguaggi offrono "out of the box" supporto per:</p>
|
|
<ul>
|
|
<li>una visione moderna delle concorrenze (le goroutines di Go o il modello ad attori di Pony ed Elixir)</li>
|
|
<li>Memory safeness, in particolare:<ul>
|
|
<li>assenza di NULL (Pony, Rust, Kotlin)</li>
|
|
<li>gestione automatica della memoria, il cosiddetto [Garbage Collector](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science) (o <a href="https://en.wikipedia.org/wiki/Reference_counting">Reference Counting</a> per Rust)</li>
|
|
<li>assenza di puntatori</li>
|
|
<li>assenza di deadlocks</li>
|
|
</ul>
|
|
</li>
|
|
<li>Supporto ad HTTP nella standard library</li>
|
|
<li>Management delle dipendenze (ad eccezione di Kotlin)</li>
|
|
<li>Namespaces</li>
|
|
</ul>
|
|
<p>Chiaramente nessuno di questi linguaggi è oggettivamente superiore agli altri, sono tutti <a href="https://en.wikipedia.org/wiki/Turing_completeness">turing completi</a> e la scelta del programmatore ricade su motivazioni del tutto personali (stile, programmazione ad oggetti, familiarità con altri linguaggi).</p>
|
|
<h3>Un pò di contesto</h3>
|
|
<p>Ho scritto la mie prime due righe di codice nel 2013 per il corso di Computer Science del Politecnico di Torino.</p>
|
|
<p>Per mia fortuna, al Politecnico le cose si muovono ancora lentamente e ci hanno fatto usare C per tutta la durata della triennale. Si è aggiunto un corso di principi della programmazione ad oggetti al terzo anno, in Java.</p>
|
|
<p>Personalmente ho imparato durante l'estate di quel primo anno le basi di C++ e Python poco dopo.</p>
|
|
<p>Sono stati con i miei primi <a href="https://github.com/framecca">progetti</a> che ho capito che non si può avere un buon linguaggio senza un buon tooling ed una community vivace e soprattutto tanta, tanta documentazione. Per questo, per molto tempo C è stata la mia prima scelta, dato che il mio target è sempre Linux.</p>
|
|
<h3>Gli obbiettivi prima del linguaggio</h3>
|
|
<p>Questo post vuole essere una raccolta più o meno organizzata delle motivazioni per cui mi sono dovuto muovere oltre la frontiera di C nel mio ultimo progetto, ovvero un backend per la raccolta e presentazione di pubblicazione di ricerca e materiale didattico per più di 80 centri di ricerca.</p>
|
|
<p>Per il Centro <a href="http://nexa.polito.it/">Nexa</a> del Politecnico di Torino mi sono ritrovato per la prima volta responsabile del codice che dovevo scrivere e dell'uso che se ne sarebbe fatto.</p>
|
|
<p>Ho dovuto tenere in considerazione, oltre chiaramente alla funzionalità della piattaforma, in ordine di priorità:</p>
|
|
<ol>
|
|
<li>Sicurezza</li>
|
|
<li>Performance e scalabilità</li>
|
|
<li>Separazione dal frontend</li>
|
|
<li>Facilità di deploy in un server che non controllo</li>
|
|
</ol>
|
|
<p>Fortunatamente non mi è stata imposta nessuna limitazione sulla scelta del linguaggio, altrimenti Python sarebbe stato la scelta più adeguata se avessi dovuto tener conto anche di altri programmatori.</p>
|
|
<h3>Benvenuto D</h3>
|
|
<p>La mia scelta è caduta su <a href="https://dlang.org">D</a>.</p>
|
|
<p>Voglio provare ad affrontare ad uno ad uno i motivi di questa scelta magari inusuale.</p>
|
|
<h5>Sicurezza</h5>
|
|
<p>Nessuno vuole davvero vantarsi di usare un backend scritto in C/C++. Il <a href="https://en.wikipedia.org/wiki/Buffer_overflow">buffer overflow</a> può essere considerato il bug più comune e ci sono <a href="https://techaeris.com/2017/09/27/buzz-fuzzing-find-uncommon-vulnerability/">situazioni</a> in cui non appaiono affatto in maniera ovvia.</p>
|
|
<p>Inoltre per un applicativo distribuito il Garbage Collector è la scelta più performante, <a href="https://www.usenix.org/node/189882">specialmente se coordinato fra le varie istanze</a>.</p>
|
|
<p>D offre questo di design, e benchè il suo GC sia frutto di numerose <a href="https://www.quora.com/Which-language-has-the-brightest-future-in-replacement-of-C-between-D-Go-and-Rust-And-Why/answer/Andrei-Alexandrescu">discussioni</a>, offre in maniera del tutto innovativa, robustezza e <a href="http://www.walterbright.com/gonewild.pdf">sicurezza</a>.</p>
|
|
<p>In particolare, D presenta:</p>
|
|
<ul>
|
|
<li>Array che sono slices (o ranges) ma non puntatori (e neanche oggetti)</li>
|
|
<li>Bound checking durante la fase di compilazione.</li>
|
|
<li>Inizializzazione automatica delle variabili.</li>
|
|
<li>Safe Casting (chiaramente come eredità di C++).</li>
|
|
<li>Restricted pointers: si può passare una funzione per referenza dichiarandola <code>ref</code>, ma solo quando passata come parametro o di ritorno. Inoltre non c'è nessuna pointer arithmetic.</li>
|
|
<li>
|
|
<a href="https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization">RAII</a>, ovvero l'acquisizione delle risorse equivale alla loro assegnazione e Scopes: le variabili hanno una lifetime limitata allo scope di dichiarazione. Nessun dangling pointer come in C.</li>
|
|
<li>Strutture immutabili: come nelle specifiche di molti linguaggi funzionali, si può dichiarare una variabile come <code>immutable</code> e quindi può essere facilmente condivisa fra threads.</li>
|
|
<li>@safe, @trusted: le specifiche del linguaggio permettono di annotare delle funzioni come sicure o affidabili affinchè il compilatore controlli che non gestiscano puntatori (ad esempio interfacciandosi con C) ed utilizzino il subset "sicuro" del linguaggio (maggiori dettagli in seguito).</li>
|
|
<li>funzioni pure: le funzioni inoltre possono essere dichiarate pure, prive di effetti collaterali e sempre <a href="https://en.wikipedia.org/wiki/Reentrancy_(computing)">rientranti</a>. Questo permette di evitare <a href="https://en.wikipedia.org/wiki/Deadlock">deadlocks</a> e un controllo totale sul risultato delle funzioni.</li>
|
|
</ul>
|
|
<h5>Performance</h5>
|
|
<p>Ci sono moltissime soluzioni per scrivere un applicativo che si interfaccia con il web, ma hanno tutte la loro origine nel famoso <a href="http://www.kegel.com/c10k.html">C10K problem</a>.</p>
|
|
<p>Nel mio caso ho deciso di utilizzare un approccio <a href="https://stackoverflow.com/questions/10960998/how-different-async-programming-is-from-threads">asincrono</a> con coroutines (anche detti <a href="https://en.wikipedia.org/wiki/Green_threads">threads leggeri</a>).</p>
|
|
<p>Benchè D abbia <a href="https://dlang.org/library/core/thread/fiber.html">supporto nativo</a> alle coroutines, ho deciso di appoggiarmi al framework più comune per web dev in D: <a href="blog/2018/07/27/addio-reddit/vibed.org">vibe.d</a>.</p>
|
|
<p>Ogni volta che Vibe accetta una richiesta dall'esterno ed esegue una funzione bloccante (che interrompe l'esecuzione del programma fino al ritorno della funzione), questa viene messa in una pool di azioni da eseguire e Vibe controlla periodicamente che almeno una di queste sia pronta a ritornare un risultato e continuare con l'esecuzione di questa.</p>
|
|
<p>Inoltre, benchè questo meccanismo funzioni interamente su un solo thread, è elementare coordinare una <a href="https://en.wikipedia.org/wiki/Thread_pool">thread pool</a> che distribuisa il carico fra i vari core che eseguono migliaia di threads leggeri concorrentemente.</p>
|
|
<h5>Contratti e Tests</h5>
|
|
<p>Non amo scrivere commenti sui programmi. Penso sia assolutamente necessario commentare il codice di librerie ma al di fuori di queste il codice (buon codice) dovrebbe essere autoesplicativo.</p>
|
|
<p>Inoltre, nelle mie recenti esperienze, il comportamento del programma era chiaro a partire dai tests.</p>
|
|
<p>In D questo concetto viene portato agli estremi applicando il <a href="https://en.wikipedia.org/wiki/Design_by_contract">"Design by Contract programming</a>.</p>
|
|
<p>Un contratto è la divisione di una funzione in:</p>
|
|
<ul>
|
|
<li>Precondizione, ovvero le condizioni che devono essersi verificate prima della chiamata della funzione;</li>
|
|
<li>Postcondizione, ovvero le condizioni che devono essere rispettate all'uscita della funzione (solitamente applicate al risultato);</li>
|
|
<li>Invarianti, ovvero le specifiche di una struttura dati che devono rimanere verificate in ogni funzione;</li>
|
|
<li>Corpo della funzione</li>
|
|
</ul>
|
|
<p>Un esempio:</p>
|
|
<pre class="code literal-block"><span class="n">struct</span><span class="w"> </span><span class="n">Clock</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
|
|
|
|
<span class="w"> </span><span class="n">short</span><span class="w"> </span><span class="n">time</span><span class="p">;</span><span class="w"></span>
|
|
|
|
<span class="w"> </span><span class="n">invariant</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
|
|
|
|
<span class="w"> </span><span class="nb">assert</span><span class="w"> </span><span class="p">(</span><span class="n">time</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
|
|
|
|
<span class="w"> </span><span class="p">}</span><span class="w"></span>
|
|
|
|
<span class="p">}</span><span class="w"></span>
|
|
|
|
<span class="n">short</span><span class="w"> </span><span class="n">addReturnTime</span><span class="p">(</span><span class="n">Clock</span><span class="w"> </span><span class="n">c</span><span class="p">,</span><span class="w"> </span><span class="n">short</span><span class="w"> </span><span class="n">n</span><span class="p">)</span><span class="w"> </span>
|
|
|
|
<span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
|
|
<span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
|
|
|
|
<span class="w"> </span><span class="p">}</span><span class="w"></span>
|
|
|
|
<span class="w"> </span><span class="n">body</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
|
|
|
|
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">c</span><span class="o">-></span><span class="n">time</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">t</span><span class="p">;</span><span class="w"></span>
|
|
|
|
<span class="w"> </span><span class="p">}</span><span class="w"></span>
|
|
|
|
<span class="w"> </span><span class="n">out</span><span class="w"> </span><span class="p">(</span><span class="n">result</span><span class="p">){</span><span class="w"></span>
|
|
|
|
<span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="n">c</span><span class="o">-></span><span class="n">time</span><span class="p">;</span><span class="w"></span>
|
|
|
|
<span class="w"> </span><span class="p">}</span><span class="w"></span>
|
|
|
|
<span class="n">unittest</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
|
|
|
|
<span class="w"> </span><span class="n">auto</span><span class="w"> </span><span class="n">clock</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Clock</span><span class="p">(</span><span class="mi">60</span><span class="p">);</span><span class="w"></span>
|
|
|
|
<span class="w"> </span><span class="nb">assert</span><span class="w"> </span><span class="p">(</span><span class="n">addReturnTime</span><span class="p">(</span><span class="n">clock</span><span class="p">,</span><span class="w"> </span><span class="mi">10</span><span class="p">)</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">70</span><span class="p">);</span><span class="w"></span>
|
|
|
|
<span class="p">}</span><span class="w"></span>
|
|
</pre>
|
|
<p>Come si nota dall'esempio il supporto ai tests è built-in nel linguaggio e distanti solo una flag in fase di compilazione.</p>
|
|
<h5>Un approccio moderno alle concorrenze</h5>
|
|
<p>Il modello primitivo delle concorrenze in Posix è discutibilmente datato e prono ad errori per il programmatore.</p>
|
|
<p>D di default evita la condivisione di dati fra Threads.
|
|
Fra le varie motivazioni c'è il fatto che questo rifletta più realisticamente l'hardware, ma sicuramente l'obbiettivo finale è la riduzione di bug.</p>
|
|
<p>Non voglio dilungarmi nei dettagli di ogni singolo approccio, ma per completezza D offre out of the box i seguenti modelli:</p>
|
|
<ul>
|
|
<li>Message passing e <a href="http://dist-prog-book.com/chapter/3/message-passing.html">attori</a>, ovvero tutti i dati che vogliono essere condivisi fra thread sono incapsulati in <a href="https://en.wikipedia.org/wiki/Remote_procedure_call">RPC</a>;</li>
|
|
<li>Green threads, come nel mio caso;</li>
|
|
<li>Multi processing, ovvero <code>man 3 fork</code>
|
|
</li>
|
|
<li>TaskPools, ovvero future e promises di Python e Javascript;</li>
|
|
<li><a href="http://moss.csc.ncsu.edu/~mueller/cluster/ps3/SDK3.0/docs/accessibility/sdkpt/cbet_1simdvector.html">SIMD vectorization</a></li>
|
|
</ul>
|
|
<p>Andrei Alexandrescu, uno dei due creatori del linguaggio, dedica un intero capitolo alle concorrenze che potete leggere liberamente <a href="http://www.informit.com/articles/article.aspx?p=1609144">qui</a>.</p>
|
|
<h5>Assenza di dogmatismi</h5>
|
|
<p>Non potrei mai pensare di scrivere un linguaggio di programmazione senza mettere al secondo posto la semplicità.</p>
|
|
<p>Ma chiaramente ancora prima di discutere di semplicità la dobbiamo definire.</p>
|
|
<p>Go e Python sono due linguaggi semplici. Lo sono per la ridotta sintassi (Go in particolare) e perchè attraverso il loro dogmatismo costringono il programmatore ad adottare dei paradigmi di programmazione scelti dai designer di quel linguaggio. E` il motivo per cui in python non abbiamo delle vere lambda e per cui Go non ha le eccezioni.</p>
|
|
<p>In D il programmatore ha libertà piena di scelta. Oltre ad un paradigma di programmazione si può ridefinire la sintassi e evitare il Garbage Collector. Si può in ultimo disattivare tutte le feature del linguaggio che sono @safe e adottare uno stile molto più vicino al C/C++, con tanto di inline asm.</p>
|
|
<h3>Dove iniziare</h3>
|
|
<p>Non posso non concludere un post propagandistico senza indirizzare i più interessati alle prime risorse per imparare D.</p>
|
|
<p>Personalmente consiglio il <a href="http://www.informit.com/store/d-programming-language-9780321635365?w_ptgrevartcl=Concurrency+in+the+D+Programming+Language_1609144">libro</a> di Andrei che offre in particolare moltissimi dettagli sulle motivazioni del design di D. Non ho ancora letto un libro che affrontasse così chiaramente il design di linguaggi di programmazione e i vari compromessi fra performance, semplicità e complessità del compilatore.</p>
|
|
<p>Inoltre il sito della community offre due intro per chi proviene da <a href="https://dlang.org/ctod.html">C</a> e <a href="https://dlang.org/cpptod.html">C++</a>, oltre al classi <a href="https://tour.dlang.org/">tour</a>.</p>
|
|
<p>Inoltre la libreria standard, <a href="https://github.com/dlang/phobos">Phobos</a>, è talmente chiara che solitamente mi trovo a mio agio a consultare direttamente il codice piuttosto che la documentazione online.</p>
|
|
</div>
|
|
</article><br><hr>
|
|
<br><article class="post"><header><h1 class="post-title"><a href="blog/2018/3/27/addio-reddit/" class="u-url">Addio Reddit</a></h1>
|
|
</header><div>
|
|
<span class="post-date">27 March 2018</span>
|
|
</div>
|
|
<br><div class="e-content entry-content">
|
|
<blockquote>
|
|
<p>It is also common ground, however, that the First Amendment does not guarantee the right to communicate one's views at all times and places or in any manner that may be desired.</p>
|
|
</blockquote>
|
|
<p>Tempo fa rimasi colpito nel leggere questo <a href="https://www.courtlistener.com/opinion/110532/heffron-v-international-soc-for-krishna-consciousness-inc/">orientamento</a> della corte degli Stati Uniti dal quale ho tratto la precedente citazione.</p>
|
|
<p>La libertà di espressione è un diritto molto potente che ha storicamente molteplici interpretazioni. Benchè ingenuamente potrebbe essere inteso come la possibilità di manifestare qualsiasi idea, ci sono delle situazioni in cui si deve intendere come il diritto di un individuo minoritario nel poter esprimersi liberamente e senza ritorsioni.</p>
|
|
<p>Con questa premessa posso inquadrare più facilmente il motivo per cui ritengo che il <a href="https://www.reddit.com/r/announcements/comments/39bpam/removing_harassing_subreddits/">ban del 2015</a> da parte degli amministratori di Reddit nei confronti di /r/FatPeopleHate e altri subreddit di minor dimensioni non sia stata un'azione ipocrita rispetto agli ideali della piattaforma.</p>
|
|
<p><img alt="reddit policies" src="wp-content/uploads/2018/reddit_freespeech.jpg"></p>
|
|
<p><a href="http://nymag.com/selectall/2017/07/angela-nagles-kill-all-normies-the-alt-right-and-4chan.html">Angela Nagle</a> ci ricorda che quegli spazi online <a href="https://hackerchick.com/the-unstoppable-power-of-leaderless-organizations/">senza leader</a> roccaforte di ideali minoritari di sinistra, hanno permesso di esprimere non un'idea, ma qualsiasi idea, anche di estrema destra.
|
|
Diventa quindi inevitabile che una piattaforma delle dimensioni di Reddit, che nello specifico raccoglie una moltitudine di argomenti e discussioni grazie ai quali è stata ritenuta un bastione della eterogeneità, prima o poi debba adottare una linea di moderazione più definita.</p>
|
|
<p><img alt="1968" src="wp-content/uploads/2018/Situationist.jpg"></p>
|
|
<p>Il <a href="https://np.reddit.com/r/announcements/comments/863xcj/new_addition_to_sitewide_rules_regarding_the_use/">recente ban</a> invece non riguarda la libertà di espressione, e mi colpisce personalmente, nonostante io non abbia mai visitato alcuno dei subreddit recentementi banditi e le mie uniche transazioni si siano limitate agli utenti di r/MechanicalKeyboards.</p>
|
|
<p>Sono approdato a Reddit inizialmente a causa del mio hobby per le tastiere meccaniche. Da lì mi sono mosso verso r/italy e altri subreddit di indirizzo informatico.
|
|
Ho interessi di nicchia e Reddit mi permetteva di riunirmi con persone dall'altra parte del globo che mi fornivano informazioni che non avrei potuto ottenere altrimenti.</p>
|
|
<p>Ora questo rimane valido solo per le persone i quali interessi sono allineati con quelle comunità che riflettono una buona immagine per Reddit e gli investitori. Benchè i miei subreddit di riferimento non siano stati banditi, riconosco che nel recente ban ci sono degli intenti politici o misteriosamente economici.</p>
|
|
<p>Con questo post dico addio a Reddit. Da spazio di discussione a spazio di intolleranza e divisione politica, dove delle comunità che non rientrano nei canoni di buona reputazione per delle agenzie pubblicitarie devono essere eliminate.</p>
|
|
<p>Non voglio che ogni mia parola sia pesata oltre i valori della comunità a cui sottoscrivo, non voglio fare esercizi di autocensura nei post che scrivo.</p>
|
|
<p>Aderivo a delle regole ben definite e delle quali gli amministratori si assumevano la responsabilità. Con il ban di marzo 2018 il sito che conoscevo ha cambiato faccia ed il post di un <a href="https://np.reddit.com/user/Reddit-Policy">utente anonimo</a> sarà l'ultimo post che downvoterò.</p>
|
|
</div>
|
|
</article><br><hr>
|
|
<br>
|
|
</div>
|
|
|
|
<div class="pagination">
|
|
|
|
<a href="." rel="prev"></a>
|
|
<a class="pagination-item newer" href=".">Newer
|
|
</a><a class="pagination-item older" href="index-18.html">Older</a>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<footer class="site-footer" id="footer"><span> CC BY-SA 4.0 International.<br></span>
|
|
<span class="site-footer-credits"><a href="https://getnikola.com">Nikola</a>, <a href="https://github.com/jasonlong/cayman-theme">Cayman theme</a>.</span>
|
|
</footer></section>
|
|
</div>
|
|
</body>
|
|
</html>
|