<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Zumi&#39;s Blog</title>
    <link>https://blog.dtth.ch/nki/</link>
    <description>Just random Zumi Zoom things</description>
    <pubDate>Mon, 20 Apr 2026 04:57:31 +0200</pubDate>
    <item>
      <title>Moving from Gmail to Fastmail</title>
      <link>https://blog.dtth.ch/nki/moving-from-gmail-to-fastmail</link>
      <description>&lt;![CDATA[I got some motivation to move away from Gmail, after a lot of procrastinating. &#xA;&#xA;!--more--&#xA;&#xA;Of course Gmail is kind of the devil&#39;s service here -- from the data processing and hoarding from users, to AI training rumors and everything else. That said I&#39;ve owned my email address from 2014 and have signed up for countless (throwaway sometimes) accounts, what should I do to deal with this elephant in the migration room? -- This question has been keeping me from doing anything with the account for several years.&#xA;&#xA;That said, I got a lot of motivation to just do things this year, after a happy vacation to Korea... So I decided, let&#39;s give it a try anyway. My Gmail inbox has always been a disaster nevertheless, with countless garbage advertising, invitation, random update emails that completely drown me from seeing actual, useful emails. If we&#39;re doing the migration, might as well do it properly.&#xA;&#xA;My plan is going to be &#xA;Import all emails from Gmail, manually categorizing and cleaning up as much as I can&#xA;Nuke all my emails from the Gmail account, to make sure I have a case against Google if they keep them&#xA;&#xA;Some statistics:&#xA;My first email in Gmail dates back as far as 2013&#xA;The number of emails in archive was about 107.000(!)&#xA;I have about 30.000 unread emails (sigh)&#xA;&#xA;These adds up to about 7GB of emails (yikes), and so if I wanted to move my emails elsewhere, I need a service that was capable of storing and indexing that much.&#xA;&#xA;Choosing Fastmail&#xA;&#xA;To be honest, not much to say about why here. I&#39;ve tried a bit of Fastmail before with my now self-hosted email address, but found them a bit expensive at the time (I was a poor student -- well I still am, but at least I got some salary...) The service was good, and they seem to be trusted by many people on this Lobste.rs thread. They also support fully importing emails from Gmail as well, which is something I really wanted to do. &#xA;&#xA;The standard single plan gives 50GB of emails, one personal inbox with multiple aliases, alongside the usual Contacts (which I don&#39;t use) and Calendar (which I do use). Costs 60CHF / year, fine, a bit expensive but it&#39;s probably worth it for the privacy.&#xA;&#xA;I pulled the plug and signed up for one year.&#xA;&#xA;Importing from Gmail&#xA;&#xA;The very first thing I wanted to perform was to pull in all my existing emails. &#xA;It was actually very simple from Fastmail: open settings, import, from Gmail, grant Fastmail all the email/contacts/calendar access to my google account. The rest happens automatically, although at not very fast speed: it took about 3 hours to move all 107.000 emails. &#xA;&#xA;Fastmail was very useful at automatically setting up forwarding future emails from Gmail, as well as letting me send emails from Fastmail as if I was sending it from the Gmail address.&#xA;&#xA;Managing this mess&#xA;&#xA;To be frank, I have always wanted to try Inbox Zero for a while, but it&#39;s a bit difficult to reach from 30.000 unread emails down to 0... Or at least, not without some dramatic measures.&#xA;&#xA;My first thought was: okay, we cannot go through all the emails, so let&#39;s just bite the bullet and archive most of them. As we are fairly close to the beginning of 2026 still, I decided that it&#39;s a good cut-off: let&#39;s just archive all emails before 2026, and store them in case I needed access to them later on. Doing that leaves me with about 400 emails(!), which I went into one by one.&#xA;&#xA;Before talking about these emails, I should mention that Fastmail has a labeling system that seems to be translated into IMAP folders: you can assign multiple labels to the same email, and they seem to be stored in the first label assigned. Furthermore, unlabeled emails (Inbox automatically gets an #inbox label) are considered &#34;archive&#34;. One neat feature: you can configure, per-label, how long the emails are going to be kept. They don&#39;t affect starred mails however, so that&#39;s an easy override for single emails.&#xA;&#xA;Of the rest:&#xA;About 75 were GitHub notification emails. I want to have them, but I don&#39;t need to keep them. I did some light filtering (Scala and research-related emails gets a persistent Scala tag), and then set a catch-all filter that automatically archives the email (remove the #inbox label) and to a #notification label, that is cleaned up after 31 days.&#xA;About 100(!) of them are LinkedIn spam. Easy, delete all and unsubscribe. Except, the notification settings of LinkedIn looks like this:&#xA;&#xA;LinkedIn Notification settings&#xA;&#xA;Yes, you have to go into each of them. Yes, you have to toggle each of them off. I hate LinkedIn with all my heart.&#xA;&#xA;The rest are mostly weekly advertising spam. I simply use them as the query to delete further emails, and click unsubscribe. Big shout out to Lenovo Japan, whose unsubscribe link leads to a 403. What do you mean, I am not allowed to opt-out?&#xA;Some emails are proper notifications of real services. As I went through each of them, I also visited the website and moved by email address to the new one.&#xA;&#xA;Finally, Inbox zero!&#xA;&#xA;What&#39;s next?&#xA;&#xA;I requested a full deletion of all my emails in Gmail. It took one evening to do so, at about 100 emails per few seconds. My Gmail is now Inbox Zero as well (and Zero in every other folder). &#xA;&#xA;I will keep the Gmail account for a while, forwarding all emails to Fastmail and slowly update all the accounts&#39; email address as needed. It&#39;s gonna take months, but I hope to fully disable Gmail some day. Looking forward to it!&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>I got some motivation to move away from Gmail, after a lot of procrastinating.</p>



<p>Of course Gmail is kind of the devil&#39;s service here — from the data processing and hoarding from users, to AI training rumors and everything else. That said I&#39;ve owned my email address from 2014 and have signed up for countless (throwaway sometimes) accounts, what should I do to deal with this elephant in the migration room? — This question has been keeping me from doing anything with the account for several years.</p>

<p>That said, I got a lot of motivation to just do things this year, after a happy vacation to Korea... So I decided, let&#39;s give it a try anyway. My Gmail inbox has always been a disaster nevertheless, with countless garbage advertising, invitation, random update emails that completely drown me from seeing actual, useful emails. If we&#39;re doing the migration, might as well do it properly.</p>

<p>My plan is going to be
– Import all emails from Gmail, manually categorizing and cleaning up as much as I can
– Nuke all my emails from the Gmail account, to make sure I have a case against Google if they keep them</p>

<p>Some statistics:
– My first email in Gmail dates back as far as 2013
– The number of emails in archive was about 107.000(!)
– I have about 30.000 unread emails (sigh)</p>

<p>These adds up to about 7GB of emails (yikes), and so if I wanted to move my emails elsewhere, I need a service that was capable of storing and indexing that much.</p>

<h2 id="choosing-fastmail">Choosing Fastmail</h2>

<p>To be honest, not much to say about why here. I&#39;ve tried a bit of Fastmail before with my now self-hosted email address, but found them a bit expensive at the time (I was a poor student — well I still am, but at least I got some salary...) The service was good, and they seem to be trusted by many people on <a href="https://lobste.rs/s/hcpnrj/who_are_people_currently_using_as_hosting" rel="nofollow">this Lobste.rs thread</a>. They also support fully importing emails from Gmail as well, which is something I really wanted to do.</p>

<p>The standard single plan gives 50GB of emails, one personal inbox with multiple aliases, alongside the usual Contacts (which I don&#39;t use) and Calendar (which I do use). Costs 60CHF / year, fine, a bit expensive but it&#39;s probably worth it for the privacy.</p>

<p>I pulled the plug and signed up for one year.</p>

<h2 id="importing-from-gmail">Importing from Gmail</h2>

<p>The very first thing I wanted to perform was to pull in all my existing emails.
It was actually very simple from Fastmail: open settings, import, from Gmail, grant Fastmail all the email/contacts/calendar access to my google account. The rest happens automatically, although at not very fast speed: it took about 3 hours to move all 107.000 emails.</p>

<p>Fastmail was very useful at automatically setting up forwarding future emails from Gmail, as well as letting me send emails from Fastmail as if I was sending it from the Gmail address.</p>

<h2 id="managing-this-mess">Managing this mess</h2>

<p>To be frank, I have always wanted to try <em>Inbox Zero</em> for a while, but it&#39;s a bit difficult to reach from 30.000 unread emails down to 0... Or at least, not without some dramatic measures.</p>

<p>My first thought was: okay, we cannot go through all the emails, so let&#39;s just bite the bullet and <em>archive</em> most of them. As we are fairly close to the beginning of 2026 still, I decided that it&#39;s a good cut-off: let&#39;s just archive all emails before 2026, and store them in case I needed access to them later on. Doing that leaves me with about 400 emails(!), which I went into one by one.</p>

<p>Before talking about these emails, I should mention that Fastmail has a labeling system that seems to be translated into IMAP folders: you can assign multiple labels to the same email, and they seem to be stored in the <em>first</em> label assigned. Furthermore, unlabeled emails (Inbox automatically gets an <a href="/nki/tag:inbox" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">inbox</span></a> label) are considered “archive”. One neat feature: you can configure, per-label, how long the emails are going to be kept. They don&#39;t affect starred mails however, so that&#39;s an easy override for single emails.</p>

<p>Of the rest:
– About 75 were GitHub notification emails. I want to have them, but I don&#39;t need to keep them. I did some light filtering (Scala and research-related emails gets a persistent Scala tag), and then set a catch-all filter that automatically archives the email (remove the <a href="/nki/tag:inbox" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">inbox</span></a> label) and to a <a href="/nki/tag:notification" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">notification</span></a> label, that is cleaned up after 31 days.
– About 100(!) of them are LinkedIn spam. Easy, delete all and unsubscribe. Except, the notification settings of LinkedIn looks like this:</p>

<p><img src="https://files.catbox.moe/iz4k8d.png" alt="LinkedIn Notification settings"></p>

<p>Yes, you have to go into each of them. Yes, you have to toggle each of them off. I hate LinkedIn with all my heart.</p>
<ul><li>The rest are mostly weekly advertising spam. I simply use them as the query to delete further emails, and click unsubscribe. Big shout out to Lenovo Japan, whose unsubscribe link leads to a 403. What do you mean, I am not allowed to opt-out?</li>
<li>Some emails are proper notifications of real services. As I went through each of them, I also visited the website and moved by email address to the new one.</li></ul>

<p>Finally, Inbox zero!</p>

<h2 id="what-s-next">What&#39;s next?</h2>

<p>I requested a full deletion of all my emails in Gmail. It took one evening to do so, at about 100 emails per few seconds. My Gmail is now Inbox Zero as well (and Zero in every other folder).</p>

<p>I will keep the Gmail account for a while, forwarding all emails to Fastmail and slowly update all the accounts&#39; email address as needed. It&#39;s gonna take months, but I hope to fully disable Gmail some day. Looking forward to it!</p>
]]></content:encoded>
      <guid>https://blog.dtth.ch/nki/moving-from-gmail-to-fastmail</guid>
      <pubDate>Sun, 18 Jan 2026 12:02:55 +0000</pubDate>
    </item>
    <item>
      <title>Hamburg - Copenhagen - Berlin trip: Day 3 + 4</title>
      <link>https://blog.dtth.ch/nki/hamburg-copenhagen-berlin-trip-day-3-4</link>
      <description>&lt;![CDATA[Welcome back to my travel log ;) &#xA;&#xA;!--more--&#xA;&#xA;Day 3: Hamburg and Train to Copenhagen&#xA;&#xA;Having stayed up until 3am writing the last part, I had a good late sleep, taking off at around 10. As it was not the best weather, we decided to just walk around until we got to a food place this time. During this walk, I did the usual: took some photos of the every day streets and places. As always, I don&#39;t try to find the best angles or scenery, just what a common pedestrian may see from a morning walk to work. &#xA;That said, I must say it was pretty enjoyable.&#xA;&#xA;We ended up getting some food at a native-looking Gasthaus. My friend and I was looking forward to trying Hamburg&#39;s traditional Labskaus, but we made a mistake reading the menu and thought they had a meat version of it (lol). &#xA;In the end, I took it anyway, while my friend got something else. We were in the end pleasantly surprised that there was (a lot of) fish on the plate. &#xA;Good dish, I must say. I liked the pickled beetroots too, despite the vegetable not being my favorite. &#xA;&#xA;In the afternoon, the weather wasn&#39;t so great, so we decided to just stay in a café. With some drinks ordered, we passed the time, until our next trains onward to Copenhagen at around 5.40pm.&#xA;I didn&#39;t pass on one last chance to get another Bratwurst, unfortunately I was too eager to munch the whole thing and didn&#39;t take any photos ;) &#xA;&#xA;Train to Copenhagen&#xA;&#xA;The &#34;standard&#34; way to go from Hamburg to Copenhagen is by an ICE directly connecting the two cities. But, being the broke student I am, we opted for a triplet of trains instead: a Deutsche Bahn train from Hamburg dropping us off at the border down of Flensburg; followed by two DSV (Denmark national train) trains towards Fredericia and Copenhagen respectively. &#xA;&#xA;Nothing spectacular about this trip: the DB train was about 10 minutes late, but as all connections were ~25 minutes apart (a concsiderate decision from DSV, I assume!), there was no trouble. We arrived at Copenhagen at the exact expected arrival time, which was a bit over midnight.&#xA;&#xA;My first impression of Denmark, which already started in Fredericia...&#xA;&#xA; &#xA;&#xA;Everything, including toilets, are spotlessly clean! And...&#xA;&#xA;7-Elevens?!&#xA;&#xA;Copenhagen, first impressions&#xA;&#xA;The main station gave me a Montreal station vibe, with a twist of red-and-white (and a lot of 7-Elevens)!&#xA;&#xA;Our ho(s)tel is about three metro stations from Copenhagen main station, and so we found ourselves there in a few minutes. One note about the station: you can walk from the tracks to the underground metro passage, but the station signs insists on you getting out of the main station and head into the Metro tracks after crossing a road. Strange decision to be sure, but it could be a time-saving measure, as I expect it would take a bit less time than crossing the tunnels.&#xA;&#xA;The metro was clean and spacious. &#xA;&#xA;... and so are the streets. Look at that  And there is a lot of 7-elevens.&#xA;&#xA;We checked in quite late, but the reception was expecting us - and checking in can be done automatically, so there was no hassle. We got a small and cozy room, time to sleep!&#xA;&#xA;Day 4: Copenhagen @ Christmas Eve&#xA;&#xA;We woke up around the not-so-early 9am. It has crossed our minds that stores will close early on this day, so we were prepared. There is a Netto) right close to our place, so we went there looking for some food and drinks. &#xA;&#xA;Potatoes-in-a-jar&#xA;&#xA;We walked down the main streets of Copenhagen, just acknowledging that museums are expected to be closed for both the 24th and 25th. I wanted a coffee (as a caffeine-holic), so we were walking towards some of the nearest cafés according to Google Maps. I was treated with a cozy atmosphere and lots of space on every street. This is a pedestrian haven, that&#39;s for sure.&#xA;&#xA;Looking around, we found a lot of Japanese tourists coming to Copenhagen. We decided to also follow the Japanese tradition, and spent our first Danish lunch... in KFC ;) &#xA;&#xA;Afterwards, more walking around. We visited the Kastellet and the streets surrounding, peeking at all 7-elevens we crossed along the route. &#xA;Feeling slightly hungry, we ook an S-Bahn back to the main station, and turned our way back to the shopping street...&#xA;&#xA;Extra photo dump&#xA;&#xA;That Dinner Story&#xA;&#xA;As you could&#39;ve guessed from your own experience, most places close early on Christmas Eve. Including restaurants. This leaves a problem for tourists: what do we have for dinner? &#xA;The answer is: restaurants run by foreigners themselves. Across the shopping street, we found the following options:&#xA;Mr. Pho, a Vietnamese-style restaurant serving Vietnamese popular dishes as the name suggests. Reviews do say they are run by Chinese residents though, and so our pride does not let us come in.&#xA;Burger King, I mean, yeah.&#xA;Wok Box, serving Chinese noodle soups and such. Was promising until we peeked inside and saw people slurping disgusting looking noodle for ~$20. &#xA;The two Shawarma places.&#xA;Instant Ramen from 7-Eleven.&#xA;&#xA;I think we ended up pondering this question for hours_, walking back and forth on the street (and between it and our hotel) about a dozen times before finally committing on one of the Shawarma places.&#xA;And this is the result:&#xA;&#xA;And it turned out, as always, ramen is better than anything.&#xA;&#xA;And that&#39;s the end of the 4th day...&#xA;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>Welcome back to my travel log ;)</p>



<h2 id="day-3-hamburg-and-train-to-copenhagen">Day 3: Hamburg and Train to Copenhagen</h2>

<p>Having stayed up until 3am writing <a href="https://blog.dtth.ch/nki/hamburg-copenhagen-berlin-travel-trip-day-1-2" rel="nofollow">the last part</a>, I had a good late sleep, taking off at around 10. As it was not the best weather, we decided to just walk around until we got to a food place this time. During this walk, I did the usual: took some photos of the every day streets and places. As always, I don&#39;t try to find the best angles or scenery, just what a common pedestrian may see from a morning walk to work.
That said, I must say it was pretty enjoyable.</p>

<p><img src="https://files.catbox.moe/p6n5k5.JPEG" alt="">
<img src="https://files.catbox.moe/lq0d3x.JPEG" alt="">
<img src="https://files.catbox.moe/fkhwlh.JPEG" alt="">
<img src="https://files.catbox.moe/28rchn.JPEG" alt="">
<img src="https://files.catbox.moe/qj9fqj.JPEG" alt="">
<img src="https://files.catbox.moe/yt9d6p.JPEG" alt="">
<img src="https://files.catbox.moe/zif24k.JPEG" alt=""></p>

<p>We ended up getting some food at a native-looking <a href="https://maps.app.goo.gl/N2g3WZqeuMYPjgYd8" rel="nofollow">Gasthaus</a>. My friend and I was looking forward to trying Hamburg&#39;s traditional <a href="https://en.wikipedia.org/wiki/Labskaus" rel="nofollow">Labskaus</a>, but we made a mistake reading the menu and thought they had a meat version of it (lol).
In the end, I took it anyway, while my friend got something else. We were in the end pleasantly surprised that there was (a lot of) fish on the plate.
Good dish, I must say. I liked the pickled beetroots too, despite the vegetable not being my favorite.</p>

<p><img src="https://files.catbox.moe/21rtgd.JPEG" alt=""></p>

<p>In the afternoon, the weather wasn&#39;t so great, so we decided to just stay in a café. With some drinks ordered, we passed the time, until our next trains onward to Copenhagen at around 5.40pm.
I didn&#39;t pass on one last chance to get another Bratwurst, unfortunately I was too eager to munch the whole thing and didn&#39;t take any photos ;)</p>

<h3 id="train-to-copenhagen">Train to Copenhagen</h3>

<p>The “standard” way to go from Hamburg to Copenhagen is by an ICE directly connecting the two cities. But, being the broke student I am, we opted for a triplet of trains instead: a Deutsche Bahn train from Hamburg dropping us off at the border down of Flensburg; followed by two DSV (Denmark national train) trains towards Fredericia and Copenhagen respectively.</p>

<p>Nothing spectacular about this trip: the DB train was about 10 minutes late, but as all connections were ~25 minutes apart (a concsiderate decision from DSV, I assume!), there was no trouble. We arrived at Copenhagen at the exact expected arrival time, which was a bit over midnight.</p>

<p>My first impression of Denmark, which already started in Fredericia...</p>

<p><img src="https://files.catbox.moe/70himo.JPEG" alt=""></p>

<p>Everything, including toilets, are spotlessly clean! And...</p>

<p><img src="https://files.catbox.moe/9kfhnk.JPEG" alt=""></p>

<p>7-Elevens?!</p>

<h3 id="copenhagen-first-impressions">Copenhagen, first impressions</h3>

<p><img src="https://files.catbox.moe/25u0fw.JPEG" alt="">
<img src="https://files.catbox.moe/0z01nv.JPEG" alt=""></p>

<p>The main station gave me a Montreal station vibe, with a twist of red-and-white (and a lot of 7-Elevens)!</p>

<p>Our ho(s)tel is about three metro stations from Copenhagen main station, and so we found ourselves there in a few minutes. One note about the station: you <em>can</em> walk from the tracks to the underground metro passage, but the station signs <em>insists</em> on you getting out of the main station and head into the Metro tracks after crossing a road. Strange decision to be sure, but it could be a time-saving measure, as I expect it would take a bit less time than crossing the tunnels.</p>

<p><img src="https://files.catbox.moe/nxp5i6.JPEG" alt=""></p>

<p>The metro was clean and spacious.</p>

<p><img src="https://files.catbox.moe/jet5ng.JPEG" alt="">
<img src="https://files.catbox.moe/7iq1rv.JPEG" alt=""></p>

<p>... and so are the streets. Look at that  And there is a lot of 7-elevens.</p>

<p>We checked in quite late, but the reception was expecting us – and checking in can be done automatically, so there was no hassle. We got a small and cozy room, time to sleep!</p>

<h2 id="day-4-copenhagen-christmas-eve">Day 4: Copenhagen @ Christmas Eve</h2>

<p>We woke up around the not-so-early 9am. It has crossed our minds that stores will close early on this day, so we were prepared. There is a <a href="https://en.wikipedia.org/wiki/Netto_(store)" rel="nofollow">Netto</a> right close to our place, so we went there looking for some food and drinks.</p>

<p><img src="https://files.catbox.moe/wpex9v.JPEG" alt=""></p>

<p>Potatoes-in-a-jar</p>

<p>We walked down the main streets of Copenhagen, just acknowledging that museums are expected to be closed for both the 24th and 25th. I wanted a coffee (as a caffeine-holic), so we were walking towards some of the nearest cafés according to Google Maps. I was treated with a cozy atmosphere and lots of space on every street. This is a pedestrian haven, that&#39;s for sure.</p>

<p><img src="https://files.catbox.moe/mxh241.JPEG" alt="">
<img src="https://files.catbox.moe/pmzsux.JPEG" alt="">
<img src="https://files.catbox.moe/gpfrdj.JPEG" alt="">
<img src="https://files.catbox.moe/310f4p.JPEG" alt="">
<img src="https://files.catbox.moe/kz1l2z.JPEG" alt="">
<img src="https://files.catbox.moe/w8ufes.JPEG" alt="">
<img src="https://files.catbox.moe/zelsh6.JPEG" alt="">
<img src="https://files.catbox.moe/b0yant.JPEG" alt="">
<img src="https://files.catbox.moe/1h9zd0.JPEG" alt=""></p>

<p>Looking around, we found a lot of Japanese tourists coming to Copenhagen. We decided to also follow the Japanese tradition, and spent our first Danish lunch... in KFC ;)</p>

<p>Afterwards, more walking around. We visited the <a href="https://maps.app.goo.gl/r4yjPraZKuRwnMDKA" rel="nofollow">Kastellet</a> and the streets surrounding, peeking at all 7-elevens we crossed along the route.
Feeling slightly hungry, we ook an S-Bahn back to the main station, and turned our way back to the shopping street...</p>

<p><img src="https://files.catbox.moe/9i05ah.JPEG" alt="">
<img src="https://files.catbox.moe/zhahml.JPEG" alt="">
<img src="https://files.catbox.moe/cgm227.JPEG" alt="">
<img src="https://files.catbox.moe/tuvfgw.JPEG" alt="">
<img src="https://files.catbox.moe/xt8hiw.JPEG" alt="">
<img src="https://files.catbox.moe/q12p9r.JPEG" alt=""></p>

<p>Extra photo dump</p>

<h3 id="that-dinner-story">That Dinner Story</h3>

<p>As you could&#39;ve guessed from your own experience, most places close early on Christmas Eve. Including restaurants. This leaves a problem for tourists: what do we have for dinner?
The answer is: restaurants run by foreigners themselves. Across the shopping street, we found the following options:
– <em>Mr. Pho</em>, a Vietnamese-style restaurant serving Vietnamese popular dishes as the name suggests. Reviews do say they are run by Chinese residents though, and so our pride does not let us come in.
– <em>Burger King</em>, I mean, yeah.
– <em>Wok Box</em>, serving Chinese noodle soups and such. Was promising until we peeked inside and saw people slurping disgusting looking noodle for ~$20.
– The two <em>Shawarma</em> places.
– Instant Ramen from 7-Eleven.</p>

<p>I think we ended up pondering this question for <em>hours</em>, walking back and forth on the street (and between it and our hotel) about a dozen times before finally committing on one of the Shawarma places.
And this is the result:</p>

<p><img src="https://files.catbox.moe/mfh6zc.png" alt=""></p>

<p>And it turned out, as always, ramen is better than anything.</p>

<p><img src="https://files.catbox.moe/a9j3r9.JPEG" alt=""></p>

<p>And that&#39;s the end of the 4th day...</p>
]]></content:encoded>
      <guid>https://blog.dtth.ch/nki/hamburg-copenhagen-berlin-trip-day-3-4</guid>
      <pubDate>Wed, 25 Dec 2024 17:51:57 +0000</pubDate>
    </item>
    <item>
      <title>Hamburg - Copenhagen - Berlin Travel Trip: Day 1+2</title>
      <link>https://blog.dtth.ch/nki/hamburg-copenhagen-berlin-travel-trip-day-1-2</link>
      <description>&lt;![CDATA[Been a while since I was last here... ;D &#xA;&#xA;It&#39;s also been a few months since I last took a vacation and went somewhere new for fun, so I decided to write some notes about it. Perhaps not the kind of thing you would post online, but eh, might as well share a bit of the fun (and the pain) with whoever that&#39;s following this blog (hehe)...&#xA;&#xA;!--more--&#xA;&#xA;For context, I&#39;ve been eyeing one of the more northern European country for a Christmas trip since I was here, but a plan has never materialized: I was in Korea, then in Japan, both times with the crew I had from Canada. This time around, the crew was not assembling, so I was back to eyeing the Scandanavians. Luckily, one of my other friends just started their Master&#39;s at the same place as mine, and they are also up for a trip. Let&#39;s go... to Copenhagen I thought. It&#39;s not too far, probably we can reach by train, I thought.&#xA;&#xA;... and that&#39;s what we planned. Now, it was not initially this way: we thought of taking a cheap flight to Copenhagen before Christmas, then slowly taking our time through Germany with a few ICE trips afterwards. Looking at the air ticket prices though:&#xA;97CHF for one way, no luggage (not even carry-on) tickets from Geneva to Copenhagen (~1.xx hours)&#xA;~110CHF for the same trip but by trains and we stop at Hamburg as a middle point (~16 hours total)&#xA;&#xA;At this point I think most people can see some reasons to take the trains, but would still think flight is the saner choice. Not me! I&#39;m a bit of a train enthusiast, and I do enjoy traveling with my small luggage, so train it went. Our plan seems nice: &#xA;&#xA;train to Hamburg (~11 hours)&#xA;train to Copenhagen (~6 hours)&#xA;train to Berlin (~8 hours)&#xA;train to home (~11 hours)&#xA;&#xA;Fitting some dates in between every trip, and we got ourselves a multi-city vacation. Sweet! We booked all the train trips (~220CHF total, not bad!) as well as all the accomodations a week before. My friend was a bit of an Airbnb hater, so we went for some of the cheaper hotels, and to be frank, the price was about the same.&#xA;&#xA;Eagerly waiting for the departure date...&#xA;&#xA;Day 1: Depart for Hamburg!&#xA;&#xA;We got on the first train at around 2pm, it was a Swiss (SBB) train. As expected, the train was on time to the minute. Despite not being able to book seats in advance on these regular cross-country trains (unless you are getting 1st class, which we students are never touching), there were enough vacancy for us to enjoy the ride all the way up until we arrived at Basel.&#xA;&#xA;From then on, we changed to one of the German (DB) trains. Nothing too peculiar: I&#39;ve been on DB ICE (high speed railway) before, and the trains they use are pretty similar. Our pre-booked seats seemed full, but that&#39;s alright, though it&#39;s a bit strange that it did not show the station we&#39;re getting off at (later on we found out from the announcements that they had to switch the actual train used on the route at the last minute and did not manage to move all the seat data over). Unlike the previous ride, this time we&#39;re sitting on the train for 4 hours straight, so we got comfortable and I dozed asleep for a little bit. &#xA;&#xA;I prepared a lot of snacks for the trip: some grapes, a pack of Zweifel, a pack of chocolate waffles, apples, bananas, coffee and apfelschorle. It was a bit overkill, but who knows what your stomach will act during a 11 hour train trip? In the end, we managed to munch over all the fruits and drinks, leaving half of the waffles and never opened the Zweifel. During this first DB train, we were sitting with a German father and his kindergarten-aged daughter. Some thoughts of sharing snacks crossed my mind, but as soon as we saw them eating pre-cut fruits and vegetables together, we kept ourselves away from unhealthy snacks as well and just shared some grapes between us and the daughter. During the trip, she was also trying to talk to us, but her German is too much for my untrained ears, and so we could only smile awkwardly. Unfortunate!&#xA;&#xA;Fulda&#xA;&#xA;Now, no Deutsche Bahn trip would be complete without a bit of spätung. It was no simple crash either: the delay built up and up and up over time, adding a few minutes each station, before reaching a crescendo in Frankfurt. There, the delays seemed to have collapsed the train with another on the same track, causing it to be sitting behind for 15(!) minutes. &#xA;&#xA;The train schedule we had&#xA;&#xA;(this image was lying to you, the schedule itself was modified from Frankfurt on)&#xA;&#xA;The DB app I had was very helpful to let us know that because of the delay we will be missing our connection, and offered a simple &#34;choose an alternative&#34; page. Well done DB app! I guess it happened often enough that having its own page is worth the hassle ;) &#xA;With all that done, we were told to get off in Fulda, and spend about half an hour there. We will arrive around 40 minutes later than schedule, but that&#39;s alright: the hotel was already informed that we would be checking in quite late - another 40 minutes will make no difference. &#xA;&#xA;Fulda is a small town, but has its own interesting Christmas market open. We didn&#39;t have time to actually buy something there, but the smell of glühwein was very inviting. Next time!&#xA;&#xA;Bahnhofstrasse in Fulda&#xA;Frog in Fulda&#xA;Glühwein Place in Fulda&#xA;&#xA;Arriving in Hamburg&#xA;&#xA;The second train had some minor delays as well, but nothing painful: we arrived in Hamburg a bit past 0:40. Not too bad -- the original schedule was 23:55~. Big city!&#xA;&#xA;(I forgot to take a photo at the time, but it was dark and I was tired)&#xA;&#xA;With little patience left for making HVV (the local transport system) accounts or dealing with ticket machines, we decided to just walk to the hotel -- should take us around 12 minutes according to Google Maps. &#xA;Of course, &#34;misfortune never comes alone&#34; (a Vietnamese idiom), we walked to the wrong branch of the hotel instead! And so another 20 minutes was spent... &#xA;&#xA;We arrived at our correct hotel and got our room at around 1:30. At this time, all I could think of was to shower and sleep. &#xA;&#xA;Day 2: Hamburg Day!&#xA;&#xA;Did I tell you we booked a visit at freaking 9am in advance? &#xA;&#xA;We passed some time on the train to Fulda looking at possible locations to visit. One of them was the Miniatur Wunderland, which (incorrectly, not sure if maliciously) informed us that all visits for Saturday from 10am to 5pm are fully booked. We had two choices: 9am, or night... And nights are for Christmas market, and so we took the 9am tickets, without knowing the incoming hit to our sleep budget...&#xA;&#xA;... and so we were up 7:30am, totalling 5.5 hours in bed. The huge coffee bottle from home proved useful, as it kept me awake for the Miniatur Wunderland visit. We walked a few minutes from the hotel, getting a Day pass from the HVV app on the way. Interesting fact: HVV iOS app requires registration before getting a pass (yuck!) but works perfectly afterwards. The Android app, you can do it as a guest, but then it bought my friend the pass for the next day... &#xA;&#xA;Miniatur Wunderland&#xA;&#xA;Miniatur Wunderland is a permanent exhibition of miniature cities, built to scale with cars, buildings and people. And moving trains! They got various cities around the world: from Swiss cities, Italian places, Monaco race tracks, French farms, Hamburg itself... to the further Norwegian ports, the American landscape, Mexican desserts and even Antartica! &#xA;&#xA;Did I tell you trains run through all of them?&#xA;&#xA;I only took stills, so perhaps a YouTube clip would describe so much better...&#xA;&#xA;Needless to say I was ecstatic about the models. They say it&#39;s a total of ~1.2 million man-hours to create the props, and I think it&#39;s so much worth it. For the price of 17EUR (student discount applied) I think you&#39;re going to have a great time. I highly recommend!&#xA;&#xA;Also, not in the photos: you can see the operator room watching over the model simulation and the train network in real time! How cool is that!!&#xA;&#xA;Another extra: one of the techniques that was used to &#34;simulate&#34; strong waves was to build a non-even surface, project a moving wave on top and synchronizing the ship&#39;s movement to the waves moving... Impressive!&#xA;&#xA;Hamburg Kunsthalle&#xA;&#xA;We went for the Kunsthalle (art museum) next, but since it&#39;s mostly artworks, I took no photos. &#xA;&#xA;There were two buildings: one for the pre-modern art, and the other for modern exhibitions. Regardless, you get a ticket for both (8EUR on student discount) from the former building with the main entrance. &#xA;&#xA;The first building organizes itself as a story moving from Realism art to Impressionistic, before touching a bit on Surrealism. I&#39;m no art student, so definitely not here to comment. But I found the idea interesting: if Realism&#39;s motive was to capture the artist&#39;s rendering of nature in its full details, then an Impressionistic wants to do the same thing, but making the viewer filling in the details by drawing impressions instead of the details themselves. Of course, as viewers, we inject details drawing from our common experience, while keeping the &#34;soul&#34; of the image intact through the impressions. A bit of work makes you enjoy it more, I might say. Is Modernism the next step, where the &#34;soul&#34; is even more abstract, allowing us, the viewer, to inject our feelings and thoughts into our own personal rendering of the art? I pondered a little bit... and then moved to the next building.&#xA;&#xA;The second building hosts a few unconnected exhibitions, each on its own floor. Confusing and abstract is the common theme, I would say, as the art ranges from gory pieces of flesh to... something resembling myself playing with MS Paint for the first time. Interestingly, each exhibition gives us a few paragraphs of context on the author and the collection: their upbringing, their interests and their message they hope to convey through the pieces.&#xA;Still not so clear about whether I would &#34;get&#34; the exhibitions, but something dawned on me today: yes the pieces by themselves are confusing and abstract, but it seemed that none was meant to be viewed on its own. Taken together, one finds a bit of structure: a common through-line, reaching out from the first impressions of each piece, that hopefully one can take and feed back into our own rendering process. &#xA;A bit like osu! mapping: one might find a common theme when you look at a piece both as individual patterns and as a whole, and use that as a recursive process... &#xA;&#xA;Well, I felt a touch smarter leaving the museum than entering. &#xA;&#xA;Food... &#xA;&#xA;We stopped for food in-between. Highlight of the day was definitely the Currywurst I got in one of the Christmas markets:&#xA;&#xA;Soft and tasteful bratwursts against the classic flavor of curry, just the perfect combination. Worth it every time.&#xA;&#xA;Now, for the un-highlight. We decided to have a bit of Korean food for lunch, as our home(?)town lacks its presence. Settled on &#34;Chingu&#34;, we made our order... and found out immediately afterwards that it was not really authentic, and seems to be run by a group of Vietnamese cooks and staff. &#xA;&#xA;I got myself a bowl of Kimchi Jjigae (one of my favorite Korean dishes), while my friend got themselves some Japchae with chicken, and we shared some fried chicken together.&#xA;&#xA;The Jjigae had the ingredients, but lacked the complex taste of pork belly / kimchi rinse in its soup. The end product reminds you of its salt and MSG, which isn&#39;t bad by themselves, but they should be elevating the original flavors instead of replacing them... &#xA;They just had some wacky sauce for the fried chicken though which turned us away from it as well. Not the best experience. But the yuzu tea was good.&#xA;&#xA;There is another story involving ramen... but that&#39;s left for another time!&#xA;&#xA;Internationales Maritimes Museum Hamburg&#xA;&#xA;This one completely blew my mind, despite being a last-minute addition.&#xA;&#xA;From the main station, we needed to take the S4 not-really-subway-but-is-much-underground line, and then walked a bit further. The sun already made most of its way by 4pm, so it was not the most positive walk we had. One interesting note: we walked from Tokiostrasse to Osakaallee, ending up with a bridge across a river (reminding myself a lot of Osaka) before getting to the museum itself. &#xA;&#xA;We arrived just around 4.30pm, when the museum started its almost-closing-time ticket sale of 9EUR. Wonder why so expensive? You&#39;re about to find out. First thing we were instructed: please head to the 9th floor and make your way down as you visit the exhibitions. So we headed to the elevator. As the doors open up, we saw in front of our eyes...&#xA;&#xA;video src=&#34;https://files.catbox.moe/55bhv1.MP4&#34; controls muted /&#xA;&#xA;Thousands of mini models of ships!!! What an opening. And it just keeps getting better:&#xA;&#xA;And bigger...: &#xA;&#xA;And even bigger...: &#xA;&#xA;And more exotic:&#xA;&#xA;Can you believe, even for captain hats, they manage to find hundreds???&#xA;&#xA;At some point we jokingly said, &#34;well, grab your Legos, you ain&#39;t gonna build any of these ships&#34;. What do you see next?&#xA;&#xA;(lego guy for scale in the 2nd photo, the ship is so big...)&#xA;&#xA;But my most favorite thing is that this museum as well has a damn to-scale miniature model of the whole Hamburg port!?!?&#xA;&#xA;(not sure if they have any connections to Miniatur Wunderland... also, damn, this is dream Cities Skylines material...)&#xA;&#xA;My jaws were on the floor the whole time during this 1+ hour visit. I mean, this is no museum, this is just Hamburg flexing on all other harbor cities... To be frank, I think this will be the best maritime museum I will ever see in my life, and there is just zero competition. &#xA;&#xA;Even the train station on the way home gets a model ship.&#xA;&#xA;Home&#xA;&#xA;... and that&#39;s all for the day. I&#39;m very pleased! Hamburg&#39;s cultural side is perfect so far. We grabbed some Glühwein on our way home, and joined some remote friends for a gaming stream to spend the night. &#xA;And then there&#39;s me spending hours writing this up. I&#39;m happy to have had this experience, so it&#39;s probably worth it to jot it down while it&#39;s still fresh. &#xA;&#xA;Until next time! Trains to Copenhagen tomorrow afternoon.]]&gt;</description>
      <content:encoded><![CDATA[<p>Been a while since I was last here... ;D</p>

<p>It&#39;s also been a few months since I last took a vacation and went somewhere new for fun, so I decided to write some notes about it. Perhaps not the kind of thing you would post online, but eh, might as well share a bit of the fun (and the pain) with whoever that&#39;s following this blog (hehe)...</p>



<p>For context, I&#39;ve been eyeing one of the more northern European country for a Christmas trip since I was here, but a plan has never materialized: I was in Korea, then in Japan, both times with the crew I had from Canada. This time around, the crew was <em>not</em> assembling, so I was back to eyeing the Scandanavians. Luckily, one of my other friends just started their Master&#39;s at the same place as mine, and they are also up for a trip. Let&#39;s go... to Copenhagen I thought. It&#39;s not too far, probably we can reach by train, I thought.</p>

<p>... and that&#39;s what we planned. Now, it was not initially this way: we thought of taking a cheap flight to Copenhagen before Christmas, then slowly taking our time through Germany with a few ICE trips afterwards. Looking at the air ticket prices though:
– 97CHF for <em>one way</em>, <em>no luggage</em> (not even carry-on) tickets from Geneva to Copenhagen (~1.xx hours)
– ~110CHF for the same trip but by trains and we stop at Hamburg as a middle point (~16 hours total)</p>

<p>At this point I think most people can see <em>some</em> reasons to take the trains, but would still think flight is the saner choice. Not me! I&#39;m a bit of a train enthusiast, and I do enjoy traveling with my small luggage, so train it went. Our plan seems nice:</p>
<ol><li>train to Hamburg (~11 hours)</li>
<li>train to Copenhagen (~6 hours)</li>
<li>train to Berlin (~8 hours)</li>
<li>train to home (~11 hours)</li></ol>

<p>Fitting some dates in between every trip, and we got ourselves a multi-city vacation. Sweet! We booked all the train trips (~220CHF total, not bad!) as well as all the accomodations a week before. My friend was a bit of an Airbnb hater, so we went for some of the cheaper hotels, and to be frank, the price was about the same.</p>

<p>Eagerly waiting for the departure date...</p>

<h2 id="day-1-depart-for-hamburg">Day 1: Depart for Hamburg!</h2>

<p>We got on the first train at around 2pm, it was a Swiss (SBB) train. As expected, the train was on time to the minute. Despite not being able to book seats in advance on these regular cross-country trains (unless you are getting 1st class, which we students are never touching), there were enough vacancy for us to enjoy the ride all the way up until we arrived at Basel.</p>

<p>From then on, we changed to one of the German (DB) trains. Nothing too peculiar: I&#39;ve been on DB ICE (high speed railway) before, and the trains they use are pretty similar. Our pre-booked seats seemed full, but that&#39;s alright, though it&#39;s a bit strange that it did not show the station we&#39;re getting off at (later on we found out from the announcements that they had to switch the actual train used on the route at the last minute and did not manage to move all the seat data over). Unlike the previous ride, this time we&#39;re sitting on the train for 4 hours straight, so we got comfortable and I dozed asleep for a little bit.</p>

<p>I prepared a <em>lot</em> of snacks for the trip: some grapes, a pack of <a href="https://duckduckgo.com/?q=zweifel&amp;t=ffab&amp;iax=images&amp;ia=images" rel="nofollow">Zweifel</a>, a pack of chocolate waffles, apples, bananas, coffee and apfelschorle. It was a bit overkill, but who knows what your stomach will act during a 11 hour train trip? In the end, we managed to munch over all the fruits and drinks, leaving half of the waffles and never opened the Zweifel. During this first DB train, we were sitting with a German father and his kindergarten-aged daughter. Some thoughts of sharing snacks crossed my mind, but as soon as we saw them eating pre-cut fruits and vegetables together, we kept ourselves away from unhealthy snacks as well and just shared some grapes between us and the daughter. During the trip, she was also trying to talk to us, but her German is too much for my untrained ears, and so we could only smile awkwardly. Unfortunate!</p>

<h2 id="fulda">Fulda</h2>

<p>Now, no Deutsche Bahn trip would be complete without a bit of <em>spätung</em>. It was no simple crash either: the delay built up and up and up over time, adding a few minutes each station, before reaching a crescendo in Frankfurt. There, the delays seemed to have collapsed the train with another on the same track, causing it to be sitting behind for 15(!) minutes.</p>

<p><img src="https://files.catbox.moe/5iivwj.JPEG" alt="The train schedule we had"></p>

<p>(this image was lying to you, the schedule itself was modified from Frankfurt on)</p>

<p>The DB app I had was very helpful to let us know that because of the delay we will be missing our connection, and offered a simple “choose an alternative” page. Well done DB app! I guess it happened often enough that having its own page is worth the hassle ;)
With all that done, we were told to get off in Fulda, and spend about half an hour there. We will arrive around 40 minutes later than schedule, but that&#39;s alright: the hotel was already informed that we would be checking in quite late – another 40 minutes will make no difference.</p>

<p>Fulda is a small town, but has its own interesting Christmas market open. We didn&#39;t have time to actually buy something there, but the smell of glühwein was very inviting. Next time!</p>

<p><img src="https://files.catbox.moe/h8w05w.JPEG" alt="Bahnhofstrasse in Fulda">
<img src="https://files.catbox.moe/jveocd.JPEG" alt="Frog in Fulda">
<img src="https://files.catbox.moe/q2ckye.JPEG" alt="Glühwein Place in Fulda"></p>

<h2 id="arriving-in-hamburg">Arriving in Hamburg</h2>

<p>The second train had some minor delays as well, but nothing painful: we arrived in Hamburg a bit past 0:40. Not too bad — the original schedule was 23:55~. Big city!</p>

<p>(I forgot to take a photo at the time, but it was dark and I was tired)</p>

<p>With little patience left for making HVV (the local transport system) accounts or dealing with ticket machines, we decided to just walk to the hotel — should take us around 12 minutes according to Google Maps.
Of course, “misfortune never comes alone” (a Vietnamese idiom), we walked to the wrong branch of the hotel instead! And so another 20 minutes was spent...</p>

<p>We arrived at our correct hotel and got our room at around 1:30. At this time, all I could think of was to shower and sleep.</p>

<h1 id="day-2-hamburg-day">Day 2: Hamburg Day!</h1>

<p>Did I tell you we booked a visit at freaking 9am in advance?</p>

<p>We passed some time on the train to Fulda looking at possible locations to visit. One of them was the <a href="https://www.miniatur-wunderland.com/" rel="nofollow">Miniatur Wunderland</a>, which (incorrectly, not sure if maliciously) informed us that <em>all visits for Saturday from 10am to 5pm are fully booked</em>. We had two choices: 9am, or night... And nights are for Christmas market, and so we took the 9am tickets, without knowing the incoming hit to our sleep budget...</p>

<p>... and so we were up 7:30am, totalling 5.5 hours in bed. The huge coffee bottle from home proved useful, as it kept me awake for the Miniatur Wunderland visit. We walked a few minutes from the hotel, getting a Day pass from the HVV app on the way. Interesting fact: HVV iOS app requires registration before getting a pass (yuck!) but works perfectly afterwards. The Android app, you can do it as a guest, but then it bought my friend the pass for the next day...</p>

<h2 id="miniatur-wunderland">Miniatur Wunderland</h2>

<p><a href="https://www.miniatur-wunderland.com/" rel="nofollow">Miniatur Wunderland</a> is a permanent exhibition of miniature cities, built to scale with cars, buildings and people. And <strong>moving trains</strong>! They got various cities around the world: from Swiss cities, Italian places, Monaco race tracks, French farms, Hamburg itself... to the further Norwegian ports, the American landscape, Mexican desserts and even Antartica!</p>

<p>Did I tell you trains run through all of them?</p>

<p><img src="https://files.catbox.moe/vkicjn.JPEG" alt="">
<img src="https://files.catbox.moe/dc6rii.JPEG" alt="">
<img src="https://files.catbox.moe/f9a0pi.JPEG" alt="">
<img src="https://files.catbox.moe/1pf68w.JPEG" alt="">
<img src="https://files.catbox.moe/43eecy.JPEG" alt="">
<img src="https://files.catbox.moe/0jlmb9.JPEG" alt="">
<img src="https://files.catbox.moe/vc5p3o.JPEG" alt=""></p>

<p>I only took stills, so perhaps a YouTube clip would describe so much better...</p>

<p>Needless to say I was ecstatic about the models. They say it&#39;s a total of ~1.2 million man-hours to create the props, and I think it&#39;s so much worth it. For the price of 17EUR (student discount applied) I think you&#39;re going to have a great time. I highly recommend!</p>

<p>Also, not in the photos: you can see the operator room watching over the model simulation and the <strong>train network</strong> in real time! How cool is that!!</p>

<p>Another extra: one of the techniques that was used to “simulate” strong waves was to build a non-even surface, project a moving wave on top and synchronizing the ship&#39;s movement to the waves moving... Impressive!</p>

<h2 id="hamburg-kunsthalle">Hamburg Kunsthalle</h2>

<p>We went for the Kunsthalle (art museum) next, but since it&#39;s mostly artworks, I took no photos.</p>

<p>There were two buildings: one for the pre-modern art, and the other for modern exhibitions. Regardless, you get a ticket for both (8EUR on student discount) from the former building with the main entrance.</p>

<p>The first building organizes itself as a story moving from Realism art to Impressionistic, before touching a bit on Surrealism. I&#39;m no art student, so definitely not here to comment. But I found the idea interesting: if Realism&#39;s motive was to capture the artist&#39;s rendering of nature in its full details, then an Impressionistic wants to do the same thing, but <em>making the viewer filling in the details</em> by drawing impressions instead of the details themselves. Of course, as viewers, we inject details drawing from our common experience, while keeping the “soul” of the image intact through the impressions. A bit of work makes you enjoy it more, I might say. Is Modernism the next step, where the “soul” is even more abstract, allowing us, the viewer, to inject our feelings and thoughts into our own personal rendering of the art? I pondered a little bit... and then moved to the next building.</p>

<p>The second building hosts a few unconnected exhibitions, each on its own floor. Confusing and abstract is the common theme, I would say, as the art ranges from gory pieces of flesh to... something resembling myself playing with MS Paint for the first time. Interestingly, each exhibition gives us a few paragraphs of context on the author and the collection: their upbringing, their interests and their message they hope to convey through the pieces.
Still not so clear about whether I would “get” the exhibitions, but something dawned on me today: yes the pieces by themselves are confusing and abstract, but it seemed that none was meant to be viewed on its own. Taken together, one finds a bit of structure: a common through-line, reaching out from the first impressions of each piece, that hopefully one can take and feed back into our own rendering process.
A bit like osu! mapping: one might find a common theme when you look at a piece both as individual patterns and as a whole, and use that as a recursive process...</p>

<p>Well, I felt a touch smarter leaving the museum than entering.</p>

<h2 id="food">Food...</h2>

<p>We stopped for food in-between. Highlight of the day was definitely the Currywurst I got in one of the Christmas markets:</p>

<p><img src="https://files.catbox.moe/yo76cj.JPEG" alt=""></p>

<p>Soft and tasteful bratwursts against the classic flavor of curry, just the perfect combination. Worth it every time.</p>

<p>Now, for the un-highlight. We decided to have a bit of Korean food for lunch, as our home(?)town lacks its presence. Settled on “Chingu”, we made our order... and found out immediately afterwards that it was not really authentic, and seems to be run by a group of Vietnamese cooks and staff.</p>

<p><img src="https://files.catbox.moe/sp4fg7.JPEG" alt=""></p>

<p>I got myself a bowl of Kimchi Jjigae (one of my favorite Korean dishes), while my friend got themselves some Japchae with chicken, and we shared some fried chicken together.</p>

<p>The Jjigae had the ingredients, but lacked the complex taste of pork belly / kimchi rinse in its soup. The end product reminds you of its salt and MSG, which isn&#39;t bad by themselves, but they should be elevating the original flavors instead of replacing them...
They just had some wacky sauce for the fried chicken though which turned us away from it as well. Not the best experience. But the yuzu tea was good.</p>

<p>There is another story involving ramen... but that&#39;s left for another time!</p>

<h2 id="internationales-maritimes-museum-hamburg">Internationales Maritimes Museum Hamburg</h2>

<p>This one completely blew my mind, despite being a last-minute addition.</p>

<p>From the main station, we needed to take the S4 not-really-subway-but-is-much-underground line, and then walked a bit further. The sun already made most of its way by 4pm, so it was not the most positive walk we had. One interesting note: we walked from Tokiostrasse to Osakaallee, ending up with a bridge across a river (reminding myself a lot of Osaka) before getting to the museum itself.</p>

<p>We arrived just around 4.30pm, when the museum started its almost-closing-time ticket sale of 9EUR. Wonder why so expensive? You&#39;re about to find out. First thing we were instructed: please head to the 9th floor and make your way down as you visit the exhibitions. So we headed to the elevator. As the doors open up, we saw in front of our eyes...</p>

<p><video src="https://files.catbox.moe/55bhv1.MP4" controls="" muted=""/></p>

<p>Thousands of mini models of ships!!! What an opening. And it just keeps getting better:</p>

<p><img src="https://files.catbox.moe/txta2s.JPG" alt="">
<img src="https://files.catbox.moe/md6wco.JPG" alt="">
<img src="https://files.catbox.moe/x71equ.JPEG" alt="">
<img src="https://files.catbox.moe/tt54b0.JPEG" alt=""></p>

<p>And bigger...:</p>

<p><img src="https://files.catbox.moe/ikmt61.JPEG" alt="">
<img src="https://files.catbox.moe/3kq92c.JPEG" alt="">
<img src="https://files.catbox.moe/oo7811.JPEG" alt="">
<img src="https://files.catbox.moe/za81k4.JPEG" alt="">
<img src="https://files.catbox.moe/4lg9d7.JPEG" alt="">
<img src="https://files.catbox.moe/7rloja.JPEG" alt=""></p>

<p>And even bigger...:</p>

<p><img src="https://files.catbox.moe/fatwj6.JPEG" alt="">
<img src="https://files.catbox.moe/ujxmfe.JPEG" alt="">
<img src="https://files.catbox.moe/jf30ca.JPEG" alt=""></p>

<p>And more exotic:</p>

<p><img src="https://files.catbox.moe/35cw4u.JPEG" alt="">
<img src="https://files.catbox.moe/fs9cp2.JPEG" alt=""></p>

<p>Can you believe, even for <em>captain hats</em>, they manage to find <em>hundreds</em>???</p>

<p><img src="https://files.catbox.moe/ocheuq.JPEG" alt=""></p>

<p>At some point we jokingly said, “well, grab your Legos, you ain&#39;t gonna build any of these ships”. What do you see next?</p>

<p><img src="https://files.catbox.moe/cp2a1t.JPEG" alt="">
<img src="https://files.catbox.moe/im6a4e.JPEG" alt=""></p>

<p>(lego guy for scale in the 2nd photo, the ship is so big...)</p>

<p>But my most favorite thing is that this museum <em>as well</em> has a damn <strong>to-scale</strong> miniature model of the whole Hamburg port!?!?</p>

<p><img src="https://files.catbox.moe/s3whwm.JPG" alt="">
<img src="https://files.catbox.moe/eu07cg.JPG" alt=""></p>

<p>(not sure if they have any connections to Miniatur Wunderland... also, damn, this is dream Cities Skylines material...)</p>

<p>My jaws were on the floor the <strong>whole time</strong> during this 1+ hour visit. I mean, this is no museum, this is just Hamburg flexing on all other harbor cities... To be frank, I think this will be the best maritime museum I will ever see in my life, and there is just zero competition.</p>

<p>Even the train station on the way home gets a model ship.</p>

<p><img src="https://files.catbox.moe/z1c5ej.JPEG" alt=""></p>

<h2 id="home">Home</h2>

<p>... and that&#39;s all for the day. I&#39;m very pleased! Hamburg&#39;s cultural side is perfect so far. We grabbed some Glühwein on our way home, and joined some remote friends for a gaming stream to spend the night.
And then there&#39;s me spending hours writing this up. I&#39;m happy to have had this experience, so it&#39;s probably worth it to jot it down while it&#39;s still fresh.</p>

<p>Until next time! Trains to Copenhagen tomorrow afternoon.</p>
]]></content:encoded>
      <guid>https://blog.dtth.ch/nki/hamburg-copenhagen-berlin-travel-trip-day-1-2</guid>
      <pubDate>Mon, 23 Dec 2024 00:01:41 +0000</pubDate>
    </item>
    <item>
      <title>Chef Thuy Pham&#39;s &#34;Thịt Kho Tàu&#34; recipe </title>
      <link>https://blog.dtth.ch/nki/chef-thuy-phams-thit-kho-tau-recipe</link>
      <description>&lt;![CDATA[#cooking #recipe #vietnamesefood&#xA;&#xA;Just writing down what this video is doing for my own reference. &#xA;&#xA;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/C-YeM3MZYdE&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; allowfullscreen/iframe&#xA;&#xA;!--more--&#xA;&#xA;Preparing the meat&#xA;&#xA;For 6 portions: 1/2 kg pork belly + 1/2 kg pork leg (for the extra softness and sweetness).&#xA;&#xA;Cut them into bigger chunks, they&#39;ll be nice to bite anyway!&#xA;Marinate the meat:&#xA;    Baby onions (4x): slice them thinly&#xA;    Red chili pepper: cut two ways! Thinly slice one half, and slice the other half into big chunks.&#xA;    Garlic: crushed.&#xA;    Lime: squeeze half into the meat.&#xA;    Take the chunked chili, garlic and salt and grind them. Put everything into the meat, mix them carefully. &#xA;Leave them in the fridge for half an hour.&#xA;&#xA;For the eggs: boil for 12 minutes, then set aside. &#xA;&#xA;Braising Juice&#xA;&#xA;You need:&#xA;&#xA;Coconut juice: 1 liter. Filter away the coconut: you only need the juice!&#xA;Water: add the same amount as the juice.&#xA;&#xA;Wait for everything to boil!&#xA;&#xA;Putting them together&#xA;&#xA;Once the juice is boiled, add the meat in. Take out all the marinating ingredients from the boil!&#xA;Leave everything for two hours.&#xA;Add the eggs, a small amount (~1 teaspoon) of fish sauce and a bit of sugar for the eggs to golden up. Leave for another hour!&#xA;&#xA;When braising, leave a big gap for the lid: it keeps the juice clear. &#xA;&#xA;That&#39;s it, have fun :D ]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="/nki/tag:cooking" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">cooking</span></a> <a href="/nki/tag:recipe" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">recipe</span></a> <a href="/nki/tag:vietnamesefood" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">vietnamesefood</span></a></p>

<p>Just writing down what <a href="https://www.youtube.com/watch?v=C-YeM3MZYdE" rel="nofollow">this video</a> is doing for my own reference.</p>

<iframe width="560" height="315" src="https://www.youtube.com/embed/C-YeM3MZYdE" title="YouTube video player" frameborder="0" allowfullscreen=""></iframe>



<h2 id="preparing-the-meat">Preparing the meat</h2>

<p>For 6 portions: ½ kg pork belly + ½ kg pork leg (for the extra softness and sweetness).</p>
<ul><li>Cut them into bigger chunks, they&#39;ll be nice to bite anyway!</li>
<li>Marinate the meat:
<ul><li>Baby onions (4x): slice them thinly</li>
<li>Red chili pepper: cut two ways! Thinly slice one half, and slice the other half into big chunks.</li>
<li>Garlic: crushed.</li>
<li>Lime: squeeze half into the meat.</li>
<li>Take the chunked chili, garlic and salt and grind them. Put everything into the meat, mix them carefully.</li></ul></li>
<li>Leave them in the fridge for half an hour.</li></ul>

<p><strong>For the eggs</strong>: boil for 12 minutes, then set aside.</p>

<h2 id="braising-juice">Braising Juice</h2>

<p>You need:</p>
<ul><li>Coconut juice: 1 liter. Filter away the coconut: you only need the juice!</li>
<li>Water: add the same amount as the juice.</li></ul>

<p>Wait for everything to boil!</p>

<h2 id="putting-them-together">Putting them together</h2>
<ul><li>Once the juice is boiled, add the meat in. Take out all the marinating ingredients from the boil!</li>
<li>Leave everything for two hours.</li>
<li>Add the eggs, a small amount (~1 teaspoon) of fish sauce and a bit of sugar for the eggs to golden up. Leave for another hour!</li></ul>

<p>When braising, leave a big gap for the lid: it keeps the juice clear.</p>

<p>That&#39;s it, have fun :D</p>
]]></content:encoded>
      <guid>https://blog.dtth.ch/nki/chef-thuy-phams-thit-kho-tau-recipe</guid>
      <pubDate>Sun, 25 Jun 2023 13:40:28 +0000</pubDate>
    </item>
    <item>
      <title>The default system-ui font of Firefox on Linux</title>
      <link>https://blog.dtth.ch/nki/the-default-system-ui-font-of-firefox-on-linux</link>
      <description>&lt;![CDATA[... is the GTK&#39;s default gtk-font-name settings.&#xA;&#xA;What is system-ui?&#xA;&#xA;You can change them in .config/gtk-4.0/settings.ini (or .config/gtk-3.0/settings.ini) like this:&#xA;gtk-font-name=Font Name 10&#xA;&#xA;Read more to see how I found it.&#xA;&#xA;!--more--&#xA;&#xA;Looking for &#34;system-ui&#34; firefox landed me on this Firefox issue tracking the feature.&#xA;The issue has an implementation here that maps the system-ui family to the internal menu font-family.&#xA;Looking at the gfx/thebes/gfxPlatformFontList.cpp file in the implementation I saw StyleSystemFont::Menu. How do we find it?&#xA;... Searchfox of course! That leads me to widget/gtk/nsLookAndFeel.cpp...&#xA;And from there just look for mMenuFontName that got assigned... &#xA;From here, we know it&#39;s something from GTK. A quick look at ArchWiki on GTK shows that you can change gtk-font-name... so we just try it and 🎉]]&gt;</description>
      <content:encoded><![CDATA[<p>... is the GTK&#39;s default <code>gtk-font-name</code> settings.</p>

<p><a href="https://caniuse.com/?search=system-ui" rel="nofollow">What is <code>system-ui</code>?</a></p>

<p>You can change them in <code>.config/gtk-4.0/settings.ini</code> (or <code>.config/gtk-3.0/settings.ini</code>) like this:</p>

<pre><code class="language-ini">gtk-font-name=Font Name 10
</code></pre>

<p>Read more to see how I found it.</p>


<ol><li>Looking for <code>&#34;system-ui&#34; firefox</code> landed me on this <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1226042" rel="nofollow">Firefox issue</a> tracking the feature.</li>
<li>The issue has an implementation <a href="https://phabricator.services.mozilla.com/D120714" rel="nofollow">here</a> that maps the <code>system-ui</code> family to the internal <code>menu</code> font-family.</li>
<li>Looking at the <code>gfx/thebes/gfxPlatformFontList.cpp</code> file in the implementation I saw <code>StyleSystemFont::Menu</code>. How do we find it?</li>
<li>... <a href="https://searchfox.org/mozilla-central/search?q=StyleSystemFont%3A%3AMenu&amp;path=&amp;case=false&amp;regexp=false" rel="nofollow">Searchfox</a> of course! That leads me to <a href="https://searchfox.org/mozilla-central/source/widget/gtk/nsLookAndFeel.cpp#1074" rel="nofollow"><code>widget/gtk/nsLookAndFeel.cpp</code></a>...</li>
<li>And from there just look for <code>mMenuFontName</code> that got assigned...</li>
<li>From here, we know it&#39;s something from GTK. A quick look at <a href="https://wiki.archlinux.org/title/GTK#Basic_theme_configuration" rel="nofollow">ArchWiki on GTK</a> shows that you can change <code>gtk-font-name</code>... so we just try it and 🎉</li></ol>
]]></content:encoded>
      <guid>https://blog.dtth.ch/nki/the-default-system-ui-font-of-firefox-on-linux</guid>
      <pubDate>Wed, 21 Jun 2023 23:26:29 +0000</pubDate>
    </item>
    <item>
      <title>Extension of Erased Values in Scala 3</title>
      <link>https://blog.dtth.ch/nki/extension-of-erased-values-in-scala-3</link>
      <description>&lt;![CDATA[This is a web version of my last semester project report.&#xA;&#xA;Introduction&#xA;&#xA;In functional programming, it is common that programmers describe the possible side effects of a function in its signature. This can be expressed by returning a monad, or by expressing the capability to use the side effects as a parameter of the function. Scala uses the notion of contextual parameters (“Contextual Parameters, Aka Implicit Parameters” n.d.) for such capabilities, allowing the programmer to delegate to the compiler the tedious work of explicitly writing down an (obvious) instance of the capability when calling that function: &#xA;def sqrt(x: Double)(using {} CanThrow[Exception]) =&#xA;    if x   = 0 then builtin.sqrt(x)&#xA;    else&#xA;        throw new Exception(&#34;sqrt of negative number&#34;)&#xA;&#xA;However, often, the capabilities exist only as a proof of existence for static type-checking purposes, and is never interacted with during the execution of the program (other than being passed around). Hence, this style of programming creates a runtime overhead that we wish to avoid. &#xA;Erased definitions allows the programmer to explicitly mark a definition/parameter as unused at runtime, guaranteeing that they are optimized away during compilation. The compiler should also guarantee that erased definitions are not referenced to at runtime, and the observable behaviour of the program does not change after erasure of these definitions. Scala already has erased definitions as an experimental feature (“Erased Definitions” n.d.), however the feature is incomplete and has some soundness issues (“Unsoundness Due to ‘Erased‘ · Issue \#4060 · Lampepfl/Dotty” n.d.).&#xA;&#xA;!--more--&#xA;&#xA;My semester project’s goal was to expand on the original implementation of erased definitions, adding needed functionalities as well as fixing soundness issues, giving it a firm ground to become an official Scala feature. Although the goal was not fully realized, my project has made the following contributions, which will be later described in the report:&#xA;&#xA;Implemented support for mixed erased/non-erased parameters in functions (“Implement Individual Erased Parameters · Pull Request \#16507 · Lampepfl/Dotty” n.d.). This fundamentally changes how erased functions are represented and treated in the compiler, which will be cover in a href=&#34;#eraseddefs&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;eraseddefs&#34;2/a, a href=&#34;#erasedparameters&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;erasedparameters&#34;3/a and a href=&#34;#erasedfntypes&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;erasedfntypes&#34;4/a.&#xA;Extend support for erased classes, a syntax sugar for classes with only erased definitions. The work is preliminary, however future ideas on expansions as well as possible challenges are described in a href=&#34;#erasedclasses&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;erasedclasses&#34;5/a.&#xA;Fixed (“Ensure Erased Vals Pass CheckRealizable · Pull Request \#16111 · Lampepfl/Dotty” n.d.) the aforementioned unsoundness bug (“Unsoundness Due to ‘Erased‘ · Issue \#4060 · Lampepfl/Dotty” n.d.), which uses a pessimisstic type check on erased definitions to prevent bad bounds. We describe the details in a href=&#34;#soundness&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;soundness&#34;6/a.&#xA;Explored the interaction between erased definitions and capabilities, where the ability to create a capability at will is a problem. We present a way forward, by restricting the right-hand sides of the erased defintions to certain forms. The details are on a href=&#34;#capabilities&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;capabilities&#34;7.1/a.&#xA;&#xA;Finally, the latter half of a href=&#34;#futurework&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;futurework&#34;7/a discusses some future directions on erased defintions, which include a possible formalization of the feature.&#xA;&#xA;Erased Definitions and “Erased” Functions&#xA;&#xA;Erased Definitions is an experimental feature of Scala 3 (dotty), available behind a language feature import (“Erased Definitions” n.d.). When enabled, the current dotty compiler introduces two new concept, erased definitions and erased functions.&#xA;&#xA;Erased definitions are val or def definitions that are guaranteed to be erased at runtime. They cannot be referenced from other code, except other erased defintions’ right-hand side and passed as erased parameters. Note that the erased definition’s right-hand side itself only type-checked by the compiler: in fact, right after type-checking, the right-hand side of the definition gets erased. This creates a problem if the right-hand side itself is a diverging computation, and the assigned type being uninhabited and/or not meant to be easily constructed. We discuss this problem in a href=&#34;#soundness&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;soundness&#34;6/a and a href=&#34;#capabilities&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;capabilities&#34;7.1/a.&#xA;&#xA;Erased functions, as of the start of the project, are functions that takes erased parameters: all parameters are erased at runtime, and the actual function at runtime does not take any (runtime) parameters.&#xA;&#xA;// before erasure&#xA;def fn(erased x: Int, y: Int) = 5&#xA;&#xA;erased val x = 5&#xA;val t = fn(x, x)&#xA;&#xA;// after erasure&#xA;def fn() = 5&#xA;&#xA;val t = fn()&#xA;&#xA;Erased functions have a different signature to their non-erased counterpart, represented by a different method companion (ErasedContextualMethod). This disallows overriding an erased method with a non-erased method and vice-versa, avoiding a conflict in signatures post-erasure.&#xA;&#xA;Individual Erased Parameters&#xA;&#xA;The above representation of method types also has a severe limitation: all parameters have to be erased, or none are erased. A major part of the semester project is to overcome this limitation, by allowing the erased keyword on every parameter before every parameter, and only erasing them during erasure phase.&#xA;&#xA;To support this, several parts of the compiler needs to be changed:&#xA;Adding support for erased flags in the parser.&#xA;Introduce a special annotation for erased parameters in method types, and change the rules in how we compare method types with erased parameters.&#xA;Change how we erase method parameters in erasure. This is rather simple, as this is just an extension of the current work: we only erased erased parameters, instead of all of them in an erased method.&#xA;&#xA;We shall go into more details of the first two changes.&#xA;&#xA;Parser changes&#xA;&#xA;The main challenge with the parser is how to handle erased as a soft keyword, in the following cases:&#xA;&#xA;def f1(using inline Int)&#xA;def f2(using inline: Int)&#xA;&#xA;def f3(using erased x: Int)&#xA;&#xA;def f4(using erased inline  Int)&#xA;def f5(using erased inline: Int)&#xA;&#xA;Scala allows contextual parameters to be declared unnamed (so they are mainly used as contextual parameters of other function calls in its body). However, by the time the parser reaches the identifier following using keyword, it wants to know whether we are parsing the parameters named or unnamed (since the whole parameter list has to be of the same syntax).&#xA;&#xA;The original parser before erased parameters only allowed inline as a parameter modifier and a soft keyword. Therefore, we can differentiate cases f1 and f2 by checking the lookahead when we see inline: If the lookahead is :, we know that inline is an identifier, otherwise it is a keyword. Furthermore, Scala does not allow implicit inline parameters without an identifier (possibly because of this parsing ambiguity), so f3 is not valid Scala with the inline keyword.&#xA;&#xA;A rather simple approach works here: we eagerly parse all the soft modifiers of the first parameters (erased for f3 and f4, and both erased and inline for f5); figure out if the following syntax is named or unnamed, parse them, and finally apply the modifiers back to the first parameter. To figure out whether the current erased or inline is a soft modifier or not, we simply look at the lookahead, and assume that they are if the lookahead is not :.&#xA;&#xA;The same eager modifier parsing approach is used when parsing lambdas with erased parameters, whereas previously no modifiers are allowed in lambda parameters.&#xA;&#xA;Erased Method Types, Erased Method Companions&#xA;&#xA;We implement method types with erased parameters as normal method types: there are no distinction of   MethodCompanions between the former and latter. Erased parameter are instead annotated with an internal annotation, ErasedParam. When comparing method types, we require that the parameters match this annotation: for two method types to have a subtype relationship, each pair of parameters in their respective positions have to both have the annotation, or both not have the annotation.&#xA;&#xA;That is, given&#xA;&#xA;m1 = MethodType([Int, Int @ErasedParam], Int) // (Int, erased Int) =  Int&#xA;m2 = MethodType([Int, Any @ErasedParam], Int) // (Int, erased Any) =  Int&#xA;m3 = MethodType([Int, Int], Int)              // (Int, Int) =  Int&#xA;m4 = MethodType([Int @ErasedParam, Int], Int) // (erased Int, Int) =  Int&#xA;m5 = MethodType([Int], Int)                   // (Int) =  Int&#xA;&#xA;We consider m2 &amp;lt;  : m1 by usual subtyping rules. However, m3, m4 and m5 does not have any subtyping relationship with any other types listed. Interestingly, although m1, m2 and m4 all become m5 after erasure, during typing we do not consider them equal nor having any subtyping relationship. This is to avoid “casting away” the erased parameter, defeating the purpose of having them in the first place.&#xA;&#xA;The approach requires no change to the representation of the method types, nor the TASTy output format. In fact, the special logic for the ErasedContextualMethod companions are now unneeded and removed, making method types handling less cumbersome. It does, however, require some changes in the type comparer to treat the annotation differently while doing method parameter comparisons. This messes with the usual logic of annotations, where they do not matter during comparison; hopefully, since this is a tightened restriction, the implementation shall not introduce unsoundness here.&#xA;&#xA;Another approach was also considered, which does not require this special annotation treatment: adopt the ErasedContextualMethod companions to also carry a boolean flag list indicating erased/non-erased parameters. Method type comparison includes MethodCompanion comparison, and method types with differing companions are naturally considered not comparable. However, this further complicates the already exponentially growing set of method companions (with mixed Impure, Contextual, Implicit); and introduces a non-backward-compatible change to the TASTy representation of the method type. This is considered both difficult to maintain and changing TASTy format is undesirable, so the former approach was preferred.&#xA;&#xA;Function Types with Erased Parameters&#xA;&#xA;Function traits are how Scala represent lambdas/closures in the type system. Typically, a function of arity N with parameter types Tsub1/sub, …, TsubN/sub and returning type R is assigned the type FunctionN[Tsub1/sub, …, TsubN/sub, R] with its syntax sugar (Tsub1/sub, …, TsubN/sub) =  R. The trait itself is defined as&#xA;&#xA;trait FunctionN[-T1, ..., -TN, +R] {&#xA;    def apply(x1: T1, ..., xN: TN): R&#xA;}&#xA;&#xA;Similarly, contextual function types are represented as ContextualFunctionN, with a similar trait definition (with using added to the parameter list).&#xA;&#xA;Function traits are important during typing and during erasure (into JVM types):&#xA;&#xA;During typing, we want to distinguish between different function types (Function1[Int, Int] and   Function2[Int, Int, Int] shoud not have a subtyping relation, nor do Function1[Int, Int] and   ContextualFunction1[Int, Int]; however, Function1[Any, Int]  &amp;lt; : Function1[Int, Int]).&#xA;During erasure, we want to convert function types into the JVM equivalent. Furthermore, Scala-specific function types like ContextualFunctions do not have a JVM equivalent, they will need to be converted into normal Functions.&#xA;&#xA;Previously, a function can only have the entire parameter list erased, and hence can be represented by a simple set of traits ErasedFunctionN:&#xA;&#xA;trait ErasedFunctionN[-T1, ..., -TN, +R] {&#xA;    def apply(erased x1: T1, ..., erased xN: TN): R&#xA;}&#xA;&#xA;The ErasedFunctionN[Tsub1/sub, …, TsubN/sub, R] traits are then transformed into Function0[R] during erasure. This is a simple representation, however we cannot simply extend this to allow individual erased parameters.&#xA;&#xA;An initial idea for extension was to simply do the same thing as method types, and annotate the parameter types Tsub1/sub, …, TsubN/sub with ErasedParam annotations. This, however, does not work when adding the same treatment of annotations during ErasedFunctions comparison. This is due to the fact that ErasedFunctionN are just normal traits, which can be aliased or extended:&#xA;&#xA;trait EraseSecondParam[T1, T2, R]&#xA;    extends ErasedFunction2[T2, erased T1, R] { }&#xA;// is EraseSecondParam[Int, Int, Int] =:= ErasedFunction2[Int, erased Int, Int]?&#xA;&#xA;Note that in the example, EraseSecondParam annotates parameters for ErasedFunction2, but it can shuffle the parameters (or use any type expressions) freely. This makes type-parameter annotation matching impossible.&#xA;&#xA;Erased function types with refined ErasedFunction trait&#xA;&#xA;The final approach involves defining an empty trait, ErasedFunction, and defining a function type with erased parameters as a refinement of this trait:&#xA;&#xA;// (Int, erased Int) =  Int&#xA;type IeII = ErasedFunction {&#xA;    def apply(x1: Int, erased x2: Int): Int&#xA;}&#xA;&#xA;With this approach, we simply delegate both typing and erasure to the underlying MethodType (with erased parameters) of the refined apply method:&#xA;&#xA;During type comparison, the type comparer naturally compares the apply refined methods, which performs the comparison of the underlying MethodTypes, so the logic implemented in a href=&#34;#erasedparameters&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;erasedparameters&#34;3/a kicks in automatically.&#xA;During erasure, we erase all refined types of which the base type is derived from ErasedFunction (to support aliased/extended traits of it). Erasure looks at the refined apply method type, and erases the value into a FunctionM value, where M is the number of non-erased parameters.&#xA;&#xA;From this delegation we also get contextual/implicit function types with erased parameters for free.&#xA;&#xA;The implementation mostly focuses on adding support for other phases of the compiler to recognize refined ErasedFunction types as function types. Luckily, this is very similar to the implementation of polymorphic function types (“Polymorphic Function Types” n.d.). However, since the compiler treat polymorphic function types themselves as a separate function type, support for polymorphic functions with erased parameters does not come “for free”, and hence are left out of the scope of the project.&#xA;&#xA;Lambdas with Erased Parameters&#xA;&#xA;Just like methods and function types, lambdas themselves can declare a parameter as erased:&#xA;&#xA;type T = (Int, erased Int) =  Int // syntax sugar for refined ErasedFunction trait&#xA;&#xA;val v1    = (x: Int, erased y: Int) =  x + 1&#xA;val v2: T = (x, erased y) =  x + 1&#xA;val v3: T = (x, y) =  x + 1 // error: expected T, got (Int, Int) =  Int&#xA;&#xA;Of note is the special case for v3, where we know that y has to be erased, but the compiler rejects the definition anyway. This is intentional: it is hard to tell whether a parameter is inferred as erased (especially with the introduction of erased classes in a href=&#34;#erasedclasses&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;erasedclasses&#34;5/a), and runtime references to the parameters can be surprising. However, if we explicitly mark y as erased, the compiler will accept the defintion, as in v2.&#xA;&#xA;Adding support for a soft keyword in lambda parameter list, however, is another challenge for the parser. Scala has support for placeholder syntax for lambdas (“Placeholder Syntax for Anonymous Functions Scala Language Reference” n.d.), but this introduces an ambiguity in the parser:&#xA;&#xA;val v1 = ( + 1,  + 2)           // (Int, Int) =  (Int, Int)&#xA;val v2 = (x, y) =  (x + 1, y + 2) // (Int, Int) =  (Int, Int)&#xA;&#xA;val v3 = (x, erased y)      // error: invalid syntax&#xA;val v4 = (x, erased y) =  x // ok&#xA;&#xA;Note that while parsing the right-hand side of v1 and v2, we cannot distinguish between a tuple expression and a parameter list until we have parsed the entire “tuple” and see the following arrow. The Scala parser overcomes this ambiguity by parsing the parameter list/tuple as a tuple itself, and re-interpret the tuple as a parameter list if it is followed by an arrow. However, this is a problem for erased, since parameter modifiers in a tuple is not valid syntax.&#xA;&#xA;To mitigate this, we allow tuples to have a temporarily invalid ValDef as a tuple value (with modifiers). During parsing, if the tuple turns out to be an actual tuple (and not a parameter list), we check for ValDefs and reject them.&#xA;&#xA;Erased Classes&#xA;&#xA;Erased classes are a syntax sugar for marking every defintion/parameter of the defined class as erased automatically. Classes that extend an erased class has to be erased as well.&#xA;&#xA;erased class A(x: Int)&#xA;erased class B extends A(5)&#xA;val a = new A(5) // a is erased&#xA;def f(x: A, y: Int) = y + 1 // x is an erased parameter&#xA;val v = (x: A, y: Int) =  y + 1 // (erased x: A, y: Int) =  Int&#xA;&#xA;def f1T &lt;: A = 5 // x is NOT erased&#xA;&#xA;During typing, we mark all definitions and parameters whose types declared or inferred to be a concrete erased class as erased. Erased classes are otherwise treated exactly as normal classes. This means that, even if by subtyping rules we can deduce that a type parameter is of an erased class, we don’t infer erased from type parameters (as in f1).&#xA;&#xA;These limitations mean that we are not creating a separate hiararchy of “erased” types, similar to phantom types (Stucki, Biboudis, and Odersky 2017). Instead, erased classes are purely syntactic sugar, and does not change the type system in any way.&#xA;&#xA;Soundness of Erasure&#xA;&#xA;Scala supports path-dependent types, where one type can refer to another type defined as a member of another variable/path, and traits can give a lower and upper bound of its values’ type members. Any bounds can be defined on traits, but a concrete type has to be given when constructing a value. Hence, the existence of a value implies a subtyping relationship between the lower and upper bound. The call-by-value semantics of Scala ensures that an expression with impossible bounds cannot reduce successfully to a value (it can throw an exception, or loop forever). However, this is not ensured for erased values, whose runtime semantics do not exist.&#xA;&#xA;trait A {&#xA;    type T   : Any&#xA;}&#xA;def upcast(erased x: A, y: Any): x.T = y&#xA;erased val v: A { type T &lt;: Nothing } = ???&#xA;def coerce(x: Any): Int = upcast(v, x)&#xA;&#xA;The above program type-checks if v and the parameter x are not erased, but the program would crash once v’s right-hand side is executed. However, we cannot allow the program to type-check if these defintions are erased, since the right hand side of v is then never executed (in fact, v does not exist at runtime, and we have an unchecked cast in coerce).&#xA;&#xA;Note that at the point of v’s definition, we know that v’s type is uninhabited: there is no type T that is a subtype of Nothing and a supertype of Any. Normally, any attempt to use the path-dependent type v.T are subject to uninhabited type checks (i.e. a realizability check), but it is fine to pass it to a function. This gives us an approach to check for this unsoundness: at the point of passing an expression as an erased parameter, we perform a realizability check on that expression’s type. This is implemented in (“Ensure Erased Vals Pass CheckRealizable · Pull Request \#16111 · Lampepfl/Dotty” n.d.).&#xA;&#xA;There are two notable points about this implementation:&#xA;&#xA;The realizability check itself is not a perfect check, but rather a pessimisstic check: types passing the check are guaranteed to be inhabited, but not vice versa.&#xA;We only perform checks if the function being called uses the path-dependent type in its parameters or result type. This seems to be enough to prevent the unsoundness bug, but it is future work to formally prove that this is a sufficient condition.&#xA;&#xA;Future Work&#xA;&#xA;Interaction with Capabilities&#xA;&#xA;Right now, safer exceptions (“CanThrow Capabilities” n.d.) uses erased CanThrow[E] capabilities to control the types of exceptions allowed to be thrown. The CanThrow instances themselves are synthesized with the right-hand side scala.compiletime.   erasedValue[T], which is a special alias to ???, the “undefined” value. Since the erased values are erased at compile-time, this does not cause a crash at runtime.&#xA;&#xA;However, this also means that the programmer themself can summon their own instance of CanThrow with   scala.compiletime.erasedValue[T], or another similar expression (such as an endless loop). This completely defeats the purpose of having CanThrow instances as capability tokens. We are in a hard position: we wish to prove that erased defintions are terminating, yet it is an impossible task.&#xA;&#xA;One of the approach that is proposed is to limit the erased expressions themselves. After macro expansions and inlining, erased expressions should be either a constant, or a constructor call. Note that no function calls are allowed; however, we don’t attempt to check that constructors are terminating: it should be the programmer’s responsibility that capability tokens are successfully created, erased or not.&#xA;&#xA;The realization of this approach, including the restriction implementation and a new approach to synthesized erased values, is early work in progress.&#xA;&#xA;Formalization&#xA;&#xA;A possible formalization of erased definitions and parameters, including a proof of semantics-preserving erasure, is highly desired for future work. With the restriction in a href=&#34;#capabilities&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;capabilities&#34;7.1/a, it might also be possible to prove that erased values are side-effect-free.&#xA;&#xA;Related Works&#xA;&#xA;Idris 2 has compile-time only parameters, as part of its quantitative type theory (Brady 2021). Its explicit annotation and checking for usage counts of the parameters within the function body help make compile-time erasure semantics of dependent type values obvious and easily understandable. It will be interesting to look into the underlying formalisms of quantitative type theory, to apply to our own future formalization.&#xA;&#xA;Dafny has ghost defintions (“Dafny Documentation” n.d.), which are defined to be specification-only variables. Due to a strict distinction between pure and impure functions, pure functions can take both ghost and runtime values and return either; while impure functions are the same as Scala’s methods with erased parameters.&#xA;&#xA;References&#xA;&#xA;Brady, Edwin. 2021. “Idris 2: Quantitative Type Theory in Practice.” arXiv. https://doi.org/10.48550/arXiv.2104.00480.&#xA;“CanThrow Capabilities.” n.d. Accessed January 29, 2023. https://docs.scala-lang.org/scala3/reference/experimental/canthrow.html#.&#xA;“Contextual Parameters, Aka Implicit Parameters.” n.d. Scala Documentation. Accessed January 25, 2023. https://docs.scala-lang.org/tour/implicit-parameters.html.&#xA;“Dafny Documentation.” n.d. Dafny Documentation. Accessed January 29, 2023. https://dafny-lang.github.io/dafny/QuickReference.html.&#xA;“Ensure Erased Vals Pass CheckRealizable · Pull Request \#16111 · Lampepfl/Dotty.” n.d. GitHub. Accessed January 29, 2023. https://github.com/lampepfl/dotty/pull/16111.&#xA;“Erased Definitions.” n.d. Accessed January 25, 2023. https://docs.scala-lang.org/scala3/reference/experimental/erased-defs.html#.&#xA;“Implement Individual Erased Parameters · Pull Request \#16507 · Lampepfl/Dotty.” n.d. GitHub. Accessed January 29, 2023. https://github.com/lampepfl/dotty/pull/16507.&#xA;“Placeholder Syntax for Anonymous Functions Scala Language Reference.” n.d. Accessed January 29, 2023. https://scala-lang.org/files/archive/spec/2.13/06-expressions.html#placeholder-syntax-for-anonymous-functions.&#xA;“Polymorphic Function Types.” n.d. Accessed January 29, 2023. https://docs.scala-lang.org/scala3/reference/new-types/polymorphic-function-types.html.&#xA;Stucki, Nicolas Alexander, Aggelos Biboudis, and Martin Odersky, eds. 2017. Dotty Phantom Types.&#xA;“Unsoundness Due to ‘Erased‘ · Issue \#4060 · Lampepfl/Dotty.” n.d. GitHub*. Accessed January 25, 2023. https://github.com/lampepfl/dotty/issues/4060.&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>This is a web version of my last semester project report.</p>

<h2 id="introduction">Introduction</h2>

<p>In functional programming, it is common that programmers describe the possible side effects of a function in its signature. This can be expressed by returning a monad, or by expressing the capability to use the side effects as a parameter of the function. Scala uses the notion of <em>contextual parameters</em> (“Contextual Parameters, Aka Implicit Parameters” n.d.) for such capabilities, allowing the programmer to delegate to the compiler the tedious work of explicitly writing down an (obvious) instance of the capability when calling that function:</p>

<pre><code class="language-scala">def sqrt(x: Double)(using {*} CanThrow[Exception]) =
    if x &gt;= 0 then builtin.sqrt(x)
    else
        throw new Exception(&#34;sqrt of negative number&#34;)
</code></pre>

<p>However, often, the capabilities exist only as a proof of existence for static type-checking purposes, and is never interacted with during the execution of the program (other than being passed around). Hence, this style of programming creates a runtime overhead that we wish to avoid.
<em>Erased definitions</em> allows the programmer to explicitly mark a definition/parameter as <strong>unused at runtime</strong>, guaranteeing that they are optimized away during compilation. The compiler should also guarantee that erased definitions are not referenced to at runtime, and the observable behaviour of the program does not change after erasure of these definitions. Scala already has erased definitions as an experimental feature (“Erased Definitions” n.d.), however the feature is incomplete and has some soundness issues (“Unsoundness Due to ‘Erased‘ · Issue #4060 · Lampepfl/Dotty” n.d.).</p>



<p>My semester project’s goal was to expand on the original implementation of erased definitions, adding needed functionalities as well as fixing soundness issues, giving it a firm ground to become an official Scala feature. Although the goal was not fully realized, my project has made the following contributions, which will be later described in the report:</p>
<ul><li>Implemented support for mixed erased/non-erased parameters in functions (“Implement Individual Erased Parameters · Pull Request #16507 · Lampepfl/Dotty” n.d.). This fundamentally changes how erased functions are represented and treated in the compiler, which will be cover in <a href="#erased_defs" rel="nofollow">2</a>, <a href="#erased_parameters" rel="nofollow">3</a> and <a href="#erased_fn_types" rel="nofollow">4</a>.</li>
<li>Extend support for erased classes, a syntax sugar for classes with only erased definitions. The work is preliminary, however future ideas on expansions as well as possible challenges are described in <a href="#erased_classes" rel="nofollow">5</a>.</li>
<li>Fixed (“Ensure Erased Vals Pass CheckRealizable · Pull Request #16111 · Lampepfl/Dotty” n.d.) the aforementioned unsoundness bug (“Unsoundness Due to ‘Erased‘ · Issue #4060 · Lampepfl/Dotty” n.d.), which uses a pessimisstic type check on erased definitions to prevent bad bounds. We describe the details in <a href="#soundness" rel="nofollow">6</a>.</li>
<li>Explored the interaction between erased definitions and capabilities, where the ability to create a capability at will is a problem. We present a way forward, by restricting the right-hand sides of the erased defintions to certain forms. The details are on <a href="#capabilities" rel="nofollow">7.1</a>.</li></ul>

<p>Finally, the latter half of <a href="#future_work" rel="nofollow">7</a> discusses some future directions on erased defintions, which include a possible formalization of the feature.</p>

<h2 id="erased-definitions-and-erased-functions">Erased Definitions and “Erased” Functions</h2>

<p>Erased Definitions is an experimental feature of Scala 3 (dotty), available behind a language feature import (“Erased Definitions” n.d.). When enabled, the current dotty compiler introduces two new concept, <em>erased definitions</em> and <em>erased functions</em>.</p>

<p><em>Erased definitions</em> are <code>val</code> or <code>def</code> definitions that are guaranteed to be erased at runtime. They <strong>cannot</strong> be referenced from other code, except other erased defintions’ right-hand side and passed as erased parameters. Note that the erased definition’s right-hand side itself only type-checked by the compiler: in fact, right after type-checking, the right-hand side of the definition gets erased. This creates a problem if the right-hand side itself is a diverging computation, and the assigned type being uninhabited and/or not meant to be easily constructed. We discuss this problem in <a href="#soundness" rel="nofollow">6</a> and <a href="#capabilities" rel="nofollow">7.1</a>.</p>

<p><em>Erased functions</em>, as of the start of the project, are functions that takes erased parameters: all parameters are erased at runtime, and the actual function at runtime does not take any (runtime) parameters.</p>

<pre><code class="language-scala">// before erasure
def fn(erased x: Int, y: Int) = 5

erased val x = 5
val t = fn(x, x)
</code></pre>

<pre><code class="language-scala">// after erasure
def fn() = 5

val t = fn()
</code></pre>

<p>Erased functions have a different signature to their non-erased counterpart, represented by a different method companion (<code>Erased[Contextual][Implicit]Method</code>). This disallows overriding an erased method with a non-erased method and vice-versa, avoiding a conflict in signatures post-erasure.</p>

<h2 id="individual-erased-parameters">Individual Erased Parameters</h2>

<p>The above representation of method types also has a severe limitation: all parameters have to be erased, or none are erased. A major part of the semester project is to overcome this limitation, by allowing the <code>erased</code> keyword on every parameter before every parameter, and only erasing them during erasure phase.</p>

<p>To support this, several parts of the compiler needs to be changed:
–   Adding support for <code>erased</code> flags in the parser.
–   Introduce a special annotation for erased parameters in method types, and change the rules in how we compare method types with erased parameters.
–   Change how we erase method parameters in erasure. This is rather simple, as this is just an extension of the current work: we only erased <code>erased</code> parameters, instead of all of them in an <code>erased</code> method.</p>

<p>We shall go into more details of the first two changes.</p>

<h3 id="parser-changes">Parser changes</h3>

<p>The main challenge with the parser is how to handle <code>erased</code> as a soft keyword, in the following cases:</p>

<pre><code class="language-scala">def f1(using inline Int)
def f2(using inline: Int)

def f3(using erased x: Int)

def f4(using erased inline  Int)
def f5(using erased inline: Int)
</code></pre>

<p>Scala allows contextual parameters to be declared unnamed (so they are mainly used as contextual parameters of other function calls in its body). However, by the time the parser reaches the identifier following <code>using</code> keyword, it wants to know whether we are parsing the parameters named or unnamed (since the whole parameter list has to be of the same syntax).</p>

<p>The original parser before <code>erased</code> parameters only allowed <code>inline</code> as a parameter modifier and a soft keyword. Therefore, we can differentiate cases <code>f1</code> and <code>f2</code> by checking the lookahead when we see <code>inline</code>: If the lookahead is <code>:</code>, we know that <code>inline</code> is an identifier, otherwise it is a keyword. Furthermore, Scala does not allow implicit inline parameters without an identifier (possibly because of this parsing ambiguity), so <code>f3</code> is not valid Scala with the <code>inline</code> keyword.</p>

<p>A rather simple approach works here: we eagerly parse all the soft modifiers of the first parameters (<code>erased</code> for <code>f3</code> and <code>f4</code>, and both <code>erased</code> and <code>inline</code> for <code>f5</code>); figure out if the following syntax is named or unnamed, parse them, and finally apply the modifiers back to the first parameter. To figure out whether the current <code>erased</code> or <code>inline</code> is a soft modifier or not, we simply look at the lookahead, and assume that they are if the lookahead is not <code>:</code>.</p>

<p>The same eager modifier parsing approach is used when parsing lambdas with erased parameters, whereas previously no modifiers are allowed in lambda parameters.</p>

<h3 id="erased-method-types-erased-method-companions">Erased Method Types, Erased Method Companions</h3>

<p>We implement method types with erased parameters as normal method types: there are no distinction of   <code>MethodCompanion</code>s between the former and latter. Erased parameter are instead annotated with an internal annotation, <code>ErasedParam</code>. When comparing method types, we require that the parameters match this annotation: for two method types to have a subtype relationship, each pair of parameters in their respective positions have to both have the annotation, or both <em>not</em> have the annotation.</p>

<p>That is, given</p>

<pre><code class="language-scala">m1 = MethodType([Int, Int @ErasedParam], Int) // (Int, erased Int) =&gt; Int
m2 = MethodType([Int, Any @ErasedParam], Int) // (Int, erased Any) =&gt; Int
m3 = MethodType([Int, Int], Int)              // (Int, Int) =&gt; Int
m4 = MethodType([Int @ErasedParam, Int], Int) // (erased Int, Int) =&gt; Int
m5 = MethodType([Int], Int)                   // (Int) =&gt; Int
</code></pre>

<p>We consider <code>m2</code> &lt;  : <code>m1</code> by usual subtyping rules. However, <code>m3</code>, <code>m4</code> and <code>m5</code> does not have any subtyping relationship with any other types listed. Interestingly, although <code>m1</code>, <code>m2</code> and <code>m4</code> all become <code>m5</code> after erasure, during typing we do not consider them equal nor having any subtyping relationship. This is to avoid “casting away” the erased parameter, defeating the purpose of having them in the first place.</p>

<p>The approach requires no change to the representation of the method types, nor the TASTy output format. In fact, the special logic for the <code>Erased[Contextual][Implicit]Method</code> companions are now unneeded and removed, making method types handling less cumbersome. It does, however, require some changes in the type comparer to treat the annotation differently while doing method parameter comparisons. This messes with the usual logic of annotations, where they do not matter during comparison; hopefully, since this is a tightened restriction, the implementation shall not introduce unsoundness here.</p>

<p>Another approach was also considered, which does not require this special annotation treatment: adopt the <code>Erased[Contextual][Implicit]Method</code> companions to also carry a boolean flag list indicating erased/non-erased parameters. Method type comparison includes <code>MethodCompanion</code> comparison, and method types with differing companions are naturally considered not comparable. However, this further complicates the already exponentially growing set of method companions (with mixed <code>Impure</code>, <code>Contextual</code>, <code>Implicit</code>); and introduces a non-backward-compatible change to the TASTy representation of the method type. This is considered both difficult to maintain and changing TASTy format is undesirable, so the former approach was preferred.</p>

<h2 id="function-types-with-erased-parameters">Function Types with Erased Parameters</h2>

<p>Function traits are how Scala represent lambdas/closures in the type system. Typically, a function of arity <em>N</em> with parameter types <em>T</em><sub>1</sub>, …, <em>T</em><sub><em>N</em></sub> and returning type <em>R</em> is assigned the type <code>Function</code><em>N</em><code>[</code><em>T</em><sub>1</sub>, …, <em>T</em><sub><em>N</em></sub>, <em>R</em><code>]</code> with its syntax sugar <code>(</code><em>T</em><sub>1</sub>, …, <em>T</em><sub><em>N</em></sub><code>) =&gt;</code><em>R</em>. The trait itself is defined as</p>

<pre><code class="language-scala">trait FunctionN[-T1, ..., -TN, +R] {
    def apply(x1: T1, ..., xN: TN): R
}
</code></pre>

<p>Similarly, contextual function types are represented as <code>ContextualFunction</code><em>N</em>, with a similar trait definition (with <code>using</code> added to the parameter list).</p>

<p>Function traits are important during typing and during erasure (into JVM types):</p>
<ul><li>During typing, we want to distinguish between different function types (<code>Function1[Int, Int]</code> and   <code>Function2[Int, Int, Int]</code> shoud not have a subtyping relation, nor do <code>Function1[Int, Int]</code> and   <code>ContextualFunction1[Int, Int]</code>; however, <code>Function1[Any, Int]</code>  &lt; : <code>Function1[Int, Int]</code>).</li>
<li>During erasure, we want to convert function types into the JVM equivalent. Furthermore, Scala-specific function types like <code>ContextualFunction</code>s do not have a JVM equivalent, they will need to be converted into normal <code>Function</code>s.</li></ul>

<p>Previously, a function can only have the entire parameter list erased, and hence can be represented by a simple set of traits <code>ErasedFunction</code><em>N</em>:</p>

<pre><code class="language-scala">trait ErasedFunctionN[-T1, ..., -TN, +R] {
    def apply(erased x1: T1, ..., erased xN: TN): R
}
</code></pre>

<p>The <code>ErasedFunction</code><em>N</em><code>[</code><em>T</em><sub>1</sub>, …, <em>T</em><sub><em>N</em></sub>, <em>R</em><code>]</code> traits are then transformed into <code>Function0[</code><em>R</em><code>]</code> during erasure. This is a simple representation, however we cannot simply extend this to allow individual erased parameters.</p>

<p>An initial idea for extension was to simply do the same thing as method types, and annotate the parameter types <em>T</em><sub>1</sub>, …, <em>T</em><sub><em>N</em></sub> with <code>ErasedParam</code> annotations. This, however, does not work when adding the same treatment of annotations during <code>ErasedFunction</code>s comparison. This is due to the fact that <code>ErasedFunctionN</code> are just normal traits, which can be aliased or extended:</p>

<pre><code class="language-scala">trait EraseSecondParam[T1, T2, R]
    extends ErasedFunction2[T2, erased T1, R] { }
// is EraseSecondParam[Int, Int, Int] =:= ErasedFunction2[Int, erased Int, Int]?
</code></pre>

<p>Note that in the example, <code>EraseSecondParam</code> annotates parameters for <code>ErasedFunction2</code>, but it can shuffle the parameters (or use any type expressions) freely. This makes type-parameter annotation matching impossible.</p>

<h3 id="erased-function-types-with-refined-erasedfunction-trait">Erased function types with refined <code>ErasedFunction</code> trait</h3>

<p>The final approach involves defining an empty trait, <code>ErasedFunction</code>, and defining a function type with erased parameters as a refinement of this trait:</p>

<pre><code class="language-scala">// (Int, erased Int) =&gt; Int
type IeII = ErasedFunction {
    def apply(x1: Int, erased x2: Int): Int
}
</code></pre>

<p>With this approach, we simply delegate both typing and erasure to the underlying <code>MethodType</code> (with erased parameters) of the refined <code>apply</code> method:</p>
<ul><li>During type comparison, the type comparer naturally compares the <code>apply</code> refined methods, which performs the comparison of the underlying <code>MethodType</code>s, so the logic implemented in <a href="#erased_parameters" rel="nofollow">3</a> kicks in automatically.</li>
<li>During erasure, we erase all refined types of which the base type is derived from <code>ErasedFunction</code> (to support aliased/extended traits of it). Erasure looks at the refined <code>apply</code> method type, and erases the value into a <code>Function</code><em>M</em> value, where <em>M</em> is the number of non-erased parameters.</li></ul>

<p>From this delegation we also get contextual/implicit function types with erased parameters for free.</p>

<p>The implementation mostly focuses on adding support for other phases of the compiler to recognize refined <code>ErasedFunction</code> types as function types. Luckily, this is very similar to the implementation of polymorphic function types (“Polymorphic Function Types” n.d.). However, since the compiler treat polymorphic function types themselves as a separate function type, support for polymorphic functions with erased parameters does not come “for free”, and hence are left out of the scope of the project.</p>

<h3 id="lambdas-with-erased-parameters">Lambdas with Erased Parameters</h3>

<p>Just like methods and function types, lambdas themselves can declare a parameter as erased:</p>

<pre><code class="language-scala">type T = (Int, erased Int) =&gt; Int // syntax sugar for refined ErasedFunction trait

val v1    = (x: Int, erased y: Int) =&gt; x + 1
val v2: T = (x, erased y) =&gt; x + 1
val v3: T = (x, y) =&gt; x + 1 // error: expected T, got (Int, Int) =&gt; Int
</code></pre>

<p>Of note is the special case for <code>v3</code>, where we know that <em>y</em> has to be erased, but the compiler rejects the definition anyway. This is intentional: it is hard to tell whether a parameter is inferred as erased (especially with the introduction of erased classes in <a href="#erased_classes" rel="nofollow">5</a>), and runtime references to the parameters can be surprising. However, if we explicitly mark <em>y</em> as <code>erased</code>, the compiler will accept the defintion, as in <code>v2</code>.</p>

<p>Adding support for a soft keyword in lambda parameter list, however, is another challenge for the parser. Scala has support for placeholder syntax for lambdas (“Placeholder Syntax for Anonymous Functions Scala Language Reference” n.d.), but this introduces an ambiguity in the parser:</p>

<pre><code class="language-scala">val v1 = (_ + 1, _ + 2)           // (Int, Int) =&gt; (Int, Int)
val v2 = (x, y) =&gt; (x + 1, y + 2) // (Int, Int) =&gt; (Int, Int)

val v3 = (x, erased y)      // error: invalid syntax
val v4 = (x, erased y) =&gt; x // ok
</code></pre>

<p>Note that while parsing the right-hand side of <code>v1</code> and <code>v2</code>, we cannot distinguish between a tuple expression and a parameter list until we have parsed the entire “tuple” and see the following arrow. The Scala parser overcomes this ambiguity by parsing the parameter list/tuple as a tuple itself, and re-interpret the tuple as a parameter list if it is followed by an arrow. However, this is a problem for <code>erased</code>, since parameter modifiers in a tuple is not valid syntax.</p>

<p>To mitigate this, we allow tuples to have a temporarily invalid <code>ValDef</code> as a tuple value (with modifiers). During parsing, if the tuple turns out to be an actual tuple (and not a parameter list), we check for <code>ValDef</code>s and reject them.</p>

<h2 id="erased-classes">Erased Classes</h2>

<p>Erased classes are a syntax sugar for marking every defintion/parameter of the defined class as <code>erased</code> automatically. Classes that extend an erased class has to be erased as well.</p>

<pre><code class="language-scala">erased class A(x: Int)
erased class B extends A(5)
val a = new A(5) // a is erased
def f(x: A, y: Int) = y + 1 // x is an erased parameter
val v = (x: A, y: Int) =&gt; y + 1 // (erased x: A, y: Int) =&gt; Int

def f1[T &lt;: A](x: T) = 5 // x is NOT erased
</code></pre>

<p>During typing, we mark all definitions and parameters whose types declared or inferred to be a <em>concrete</em> erased class as <code>erased</code>. Erased classes are otherwise treated exactly as normal classes. This means that, even if by subtyping rules we can deduce that a type parameter is of an erased class, we don’t infer <code>erased</code> from type parameters (as in <code>f1</code>).</p>

<p>These limitations mean that we are not creating a separate hiararchy of “erased” types, similar to phantom types (Stucki, Biboudis, and Odersky 2017). Instead, erased classes are purely syntactic sugar, and does not change the type system in any way.</p>

<h2 id="soundness-of-erasure">Soundness of Erasure</h2>

<p>Scala supports path-dependent types, where one type can refer to another type defined as a member of another variable/path, and traits can give a lower and upper bound of its values’ type members. Any bounds can be defined on traits, but a concrete type has to be given when constructing a value. Hence, the existence of a value implies a subtyping relationship between the lower and upper bound. The call-by-value semantics of Scala ensures that an expression with impossible bounds cannot reduce successfully to a value (it can throw an exception, or loop forever). However, this is not ensured for erased values, whose runtime semantics do not exist.</p>

<pre><code class="language-scala">trait A {
    type T &gt;: Any
}
def upcast(erased x: A, y: Any): x.T = y
erased val v: A { type T &lt;: Nothing } = ???
def coerce(x: Any): Int = upcast(v, x)
</code></pre>

<p>The above program type-checks if <em>v</em> and the parameter <em>x</em> are not <code>erased</code>, but the program would crash once <em>v</em>’s right-hand side is executed. However, we cannot allow the program to type-check if these defintions are <code>erased</code>, since the right hand side of <em>v</em> is then never executed (in fact, <em>v</em> does not exist at runtime, and we have an unchecked cast in <code>coerce</code>).</p>

<p>Note that at the point of <em>v</em>’s definition, we know that <em>v</em>’s type is uninhabited: there is no type <em>T</em> that is a subtype of <code>Nothing</code> and a supertype of <code>Any</code>. Normally, any attempt to use the path-dependent type <code>v.T</code> are subject to uninhabited type checks (i.e. a realizability check), but it is fine to pass it to a function. This gives us an approach to check for this unsoundness: at the point of passing an expression as an erased parameter, we perform a realizability check on that expression’s type. This is implemented in (“Ensure Erased Vals Pass CheckRealizable · Pull Request #16111 · Lampepfl/Dotty” n.d.).</p>

<p>There are two notable points about this implementation:</p>
<ul><li>The realizability check itself is not a perfect check, but rather a pessimisstic check: types passing the check are guaranteed to be inhabited, but not vice versa.</li>
<li>We only perform checks if the function being called uses the path-dependent type in its parameters or result type. This seems to be enough to prevent the unsoundness bug, but it is future work to formally prove that this is a sufficient condition.</li></ul>

<h2 id="future-work">Future Work</h2>

<h3 id="interaction-with-capabilities">Interaction with Capabilities</h3>

<p>Right now, safer exceptions (“CanThrow Capabilities” n.d.) uses erased <code>CanThrow[E]</code> capabilities to control the types of exceptions allowed to be thrown. The <code>CanThrow</code> instances themselves are synthesized with the right-hand side <code>scala.compiletime.</code>   <code>erasedValue[T]</code>, which is a special alias to <code>???</code>, the “undefined” value. Since the erased values are erased at compile-time, this does not cause a crash at runtime.</p>

<p>However, this also means that the programmer themself can summon their own instance of <code>CanThrow</code> with   <code>scala.compiletime.erasedValue[T]</code>, or another similar expression (such as an endless loop). This completely defeats the purpose of having <code>CanThrow</code> instances as capability tokens. We are in a hard position: we wish to prove that erased defintions are terminating, yet it is an impossible task.</p>

<p>One of the approach that is proposed is to limit the erased expressions themselves. After macro expansions and inlining, erased expressions should be either a constant, or a constructor call. Note that no function calls are allowed; however, we don’t attempt to check that constructors are terminating: it should be the programmer’s responsibility that capability tokens are successfully created, erased or not.</p>

<p>The realization of this approach, including the restriction implementation and a new approach to synthesized erased values, is early work in progress.</p>

<h3 id="formalization">Formalization</h3>

<p>A possible formalization of erased definitions and parameters, including a proof of semantics-preserving erasure, is highly desired for future work. With the restriction in <a href="#capabilities" rel="nofollow">7.1</a>, it might also be possible to prove that erased values are side-effect-free.</p>

<h2 id="related-works">Related Works</h2>

<p>Idris 2 has compile-time only parameters, as part of its quantitative type theory (Brady 2021). Its explicit annotation and checking for usage counts of the parameters within the function body help make compile-time erasure semantics of dependent type values obvious and easily understandable. It will be interesting to look into the underlying formalisms of quantitative type theory, to apply to our own future formalization.</p>

<p>Dafny has ghost defintions (“Dafny Documentation” n.d.), which are defined to be specification-only variables. Due to a strict distinction between pure and impure functions, pure functions can take both ghost and runtime values and return either; while impure functions are the same as Scala’s methods with erased parameters.</p>

<h2 id="references">References</h2>
<ul><li>Brady, Edwin. 2021. “Idris 2: Quantitative Type Theory in Practice.” arXiv. <a href="https://doi.org/10.48550/arXiv.2104.00480" rel="nofollow">https://doi.org/10.48550/arXiv.2104.00480</a>.</li>
<li>“CanThrow Capabilities.” n.d. Accessed January 29, 2023. <a href="https://docs.scala-lang.org/scala3/reference/experimental/canthrow.html" rel="nofollow">https://docs.scala-lang.org/scala3/reference/experimental/canthrow.html#</a>.</li>
<li>“Contextual Parameters, Aka Implicit Parameters.” n.d. <em>Scala Documentation</em>. Accessed January 25, 2023. <a href="https://docs.scala-lang.org/tour/implicit-parameters.html" rel="nofollow">https://docs.scala-lang.org/tour/implicit-parameters.html</a>.</li>
<li>“Dafny Documentation.” n.d. <em>Dafny Documentation</em>. Accessed January 29, 2023. <a href="https://dafny-lang.github.io/dafny/QuickReference.html" rel="nofollow">https://dafny-lang.github.io/dafny/QuickReference.html</a>.</li>
<li>“Ensure Erased Vals Pass CheckRealizable · Pull Request #16111 · Lampepfl/Dotty.” n.d. <em>GitHub</em>. Accessed January 29, 2023. <a href="https://github.com/lampepfl/dotty/pull/16111" rel="nofollow">https://github.com/lampepfl/dotty/pull/16111</a>.</li>
<li>“Erased Definitions.” n.d. Accessed January 25, 2023. <a href="https://docs.scala-lang.org/scala3/reference/experimental/erased-defs.html" rel="nofollow">https://docs.scala-lang.org/scala3/reference/experimental/erased-defs.html#</a>.</li>
<li>“Implement Individual Erased Parameters · Pull Request #16507 · Lampepfl/Dotty.” n.d. <em>GitHub</em>. Accessed January 29, 2023. <a href="https://github.com/lampepfl/dotty/pull/16507" rel="nofollow">https://github.com/lampepfl/dotty/pull/16507</a>.</li>
<li>“Placeholder Syntax for Anonymous Functions Scala Language Reference.” n.d. Accessed January 29, 2023. <a href="https://scala-lang.org/files/archive/spec/2.13/06-expressions.html#placeholder-syntax-for-anonymous-functions" rel="nofollow">https://scala-lang.org/files/archive/spec/2.13/06-expressions.html#placeholder-syntax-for-anonymous-functions</a>.</li>
<li>“Polymorphic Function Types.” n.d. Accessed January 29, 2023. <a href="https://docs.scala-lang.org/scala3/reference/new-types/polymorphic-function-types.html" rel="nofollow">https://docs.scala-lang.org/scala3/reference/new-types/polymorphic-function-types.html</a>.</li>
<li>Stucki, Nicolas Alexander, Aggelos Biboudis, and Martin Odersky, eds. 2017. <em>Dotty Phantom Types</em>.</li>
<li>“Unsoundness Due to ‘Erased‘ · Issue #4060 · Lampepfl/Dotty.” n.d. <em>GitHub</em>. Accessed January 25, 2023. <a href="https://github.com/lampepfl/dotty/issues/4060" rel="nofollow">https://github.com/lampepfl/dotty/issues/4060</a>.</li></ul>
]]></content:encoded>
      <guid>https://blog.dtth.ch/nki/extension-of-erased-values-in-scala-3</guid>
      <pubDate>Tue, 13 Jun 2023 16:03:32 +0000</pubDate>
    </item>
    <item>
      <title>Slides from my &#34;Copying Stacks for fun and Profit&#34; Scala Lunch talk</title>
      <link>https://blog.dtth.ch/nki/slides-from-my-copying-stacks-for-fun-and-profit-scala-lunch-talk</link>
      <description>&lt;![CDATA[Someday I will be writing about this in details, some day...&#xA;&#xA;For now, enjoy my slides as is ^^&#xA;&#xA;[Download slides]&#xA;&#xA;References:&#xA;Martin Odersky - Direct Style Scala / Scalar 23&#xA;Alexis King - Delimited Continuations Demystified / ZuriHac 2023&#xA;My WIP implementation of Delimited Continuations for Scala Native]]&gt;</description>
      <content:encoded><![CDATA[<p>Someday I will be writing about this in details, some day...</p>

<p>For now, enjoy my slides as is ^^</p>

<p><a href="https://cdn.discordapp.com/attachments/676817846617243658/1118204392475201547/copying_stacks_for_fun_and_profit.pdf" rel="nofollow">[Download slides]</a></p>

<p>References:
1. <a href="https://www.youtube.com/watch?v=0Fm0y4K4YO8" rel="nofollow">Martin Odersky – Direct Style Scala / Scalar 23</a>
2. <a href="https://youtu.be/aaApZhfisbs" rel="nofollow">Alexis King – Delimited Continuations Demystified / ZuriHac 2023</a>
3. <a href="https://github.com/scala-native/scala-native/pull/3286" rel="nofollow">My WIP implementation of Delimited Continuations for Scala Native</a></p>
]]></content:encoded>
      <guid>https://blog.dtth.ch/nki/slides-from-my-copying-stacks-for-fun-and-profit-scala-lunch-talk</guid>
      <pubDate>Tue, 13 Jun 2023 15:46:03 +0000</pubDate>
    </item>
    <item>
      <title>Parcel 2: Pain and Suffering</title>
      <link>https://blog.dtth.ch/nki/parcel-2-pain-and-suffering</link>
      <description>&lt;![CDATA[Made some efforts to move kjudge to use Parcel 2.&#xA;&#xA;!--more--&#xA;&#xA;1. What&#39;s &#34;module&#34; mode?&#xA;&#xA;Basically it&#39;s the adoption of ES modules-style imports to the browser.&#xA;They look nice, but they are now strongly isolated. &#xA;That means, code modifying global variables like window are no longer possible.&#xA;Sounds great, until you realize that it&#39;s one of the &#34;normal&#34; way for HTML-inlined JS to work with &#34;imported&#34; JS.&#xA;&#xA;What we have instead is an export mechanism, which does what you think, just like how ES modules work in Node.js, TypeScript and the like. &#xA;You can even import them from the browser, like this:&#xA;script type=&#34;module&#34;&#xA;  import { func } from &#34;./module.js&#34;;&#xA;  &#xA;  // call func&#xA;  func();&#xA;/script&#xA;However I&#39;m not yet sure on how those things work yet. &#xA;One thing I know is that I&#39;ve been abusing globals like window for most interaction with go-generated HTML and independent TypeScript code in kjudge.&#xA;&#xA;And that did not go well with Parcel 2...&#xA;&#xA;2. Parcel 2&#xA;&#xA;Parcel 2 decided to push the standard to the extreme, requiring all TS/JS code with imports to be imported (from HTML) as modules. This means that as long as you import anything (not just other JS/TS files but also images, audio, ... anything you can think of with a Parcel magic import), you live with an isolated JS/TS file.&#xA;Why is this a strong requirement? I get it for code import, but for resources they don&#39;t make a lot of sense, especially when they also are making it explicit that what you get is just an URL...&#xA;&#xA;Even worse, Parcel has some logic to inline the script content into the HTML once imported, making static global variables totally impossible (yay?). &#xA;&#xA;3. Pain&#xA;&#xA;I haven&#39;t found a nice way out of this yet. Perhaps changing inlining rules? Perhaps a way to compromise with the import algorithm? &#xA;&#xA;We&#39;ll see once I have time for this again :) ]]&gt;</description>
      <content:encoded><![CDATA[<p>Made some efforts to move <a href="https://github.com/natsukagami/kjudge" rel="nofollow"><code>kjudge</code></a> to use Parcel 2.</p>



<h2 id="1-what-s-module-mode">1. What&#39;s “module” mode?</h2>

<p>Basically it&#39;s the adoption of ES modules-style imports to the browser.
They look nice, but they are now <strong>strongly isolated</strong>.
That means, code modifying global variables like <code>window</code> are no longer possible.
Sounds great, until you realize that it&#39;s one of the “normal” way for HTML-inlined JS to work with “imported” JS.</p>

<p>What we have instead is an <code>export</code> mechanism, which does what you think, just like how ES modules work in Node.js, TypeScript and the like.
You can even <code>import</code> them from the browser, like this:</p>

<pre><code class="language-html">&lt;script type=&#34;module&#34;&gt;
  import { func } from &#34;./module.js&#34;;
  
  // call func
  func();
&lt;/script&gt;
</code></pre>

<p>However I&#39;m not yet sure on how those things work yet.
One thing I know is that I&#39;ve been abusing globals like <code>window</code> for most interaction with go-generated HTML and independent TypeScript code in <a href="https://github.com/natsukagami/kjudge" rel="nofollow"><code>kjudge</code></a>.</p>

<p>And that did not go well with Parcel 2...</p>

<h2 id="2-parcel-2">2. Parcel 2</h2>

<p>Parcel 2 decided to push the standard to the extreme, requiring all TS/JS code with <code>import</code>s to be imported (from HTML) as modules. This means that as long as you import anything (not just other JS/TS files but also images, audio, ... anything you can think of with a Parcel magic import), you live with an isolated JS/TS file.
Why is this a strong requirement? I get it for code import, but for resources they don&#39;t make a lot of sense, especially when they also are making it explicit that what you get is just an URL...</p>

<p>Even worse, Parcel has some logic to inline the script content into the HTML once imported, making static global variables totally impossible (yay?).</p>

<h2 id="3-pain">3. Pain</h2>

<p>I haven&#39;t found a nice way out of this yet. Perhaps changing inlining rules? Perhaps a way to compromise with the import algorithm?</p>

<p>We&#39;ll see once I have time for this again :)</p>
]]></content:encoded>
      <guid>https://blog.dtth.ch/nki/parcel-2-pain-and-suffering</guid>
      <pubDate>Mon, 27 Feb 2023 15:47:36 +0000</pubDate>
    </item>
    <item>
      <title>Welcome to another attempt at writing stuff...</title>
      <link>https://blog.dtth.ch/nki/welcome-to-another-attempt-at-writing-stuff-xy5t</link>
      <description>&lt;![CDATA[Things should work now as they should. Or so I hope.&#xA;&#xA;Giving some code a try...&#xA;str :: String&#xA;str = &#34;Hello World!&#34;&#xA;&#xA;main :: IO ()&#xA;main = putStrLn str&#xA;&#xA;See ya :3&#xA;&#xA;welcome ]]&gt;</description>
      <content:encoded><![CDATA[<p>Things should work now as they should. Or so I hope.</p>

<p>Giving some code a try...</p>

<pre><code class="language-haskell">str :: String
str = &#34;Hello World!&#34;

main :: IO ()
main = putStrLn str
</code></pre>

<p>See ya :3</p>

<p><a href="/nki/tag:welcome" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">welcome</span></a></p>
]]></content:encoded>
      <guid>https://blog.dtth.ch/nki/welcome-to-another-attempt-at-writing-stuff-xy5t</guid>
      <pubDate>Mon, 14 Nov 2022 20:45:06 +0000</pubDate>
    </item>
    <item>
      <title>Moving my DigitalOcean node to Nix (Chapter 1...)</title>
      <link>https://blog.dtth.ch/nki/moving-my-digitalocean-node-to-nix-chapter-1</link>
      <description>&lt;![CDATA[#nix #hosting&#xA;&#xA;Lately I have been switching my primary Linux PC from Arch Linux to NixOS, a Linux distribution with a functional twist: completely stateless configuration. It&#39;s been quite a pleasant ride, and Nix the expression language, though a bit lacking in friendly documentation, is actually not that hard to get used to (... but the nixpkgs.lib functions are not!)&#xA;&#xA;With the help of home-manager, a Nix module/program that manages user-specific configuation in the same Nix language, I have a completely working .config with all the relevant programs installed (kak editor, fish with my bindings, ...) completely synchronized between my M1 MacBook Air and the Linux PC. &#xA;&#xA;Of course, to expand the reach of Nix, I decided to move my DigitalOcean node, where I run my Discord bots, a personal email server, a git server and a small photo store, to NixOS as well.&#xA;&#xA;!--more--&#xA;&#xA;What are the goals of a remote NixOS configuration?&#xA;&#xA;Well, for a starter, it should be able to run all the stuff I have been running. &#xA;  This is actually not trivial. I run most of my stuff on the old DigitalOcean node as a bunch of Docker Compose containers, each with their own docker-compose.yml file (but sharing the same reverse proxy and PostgreSQL database!)&#xA;  However with NixOS I want all of them to run &#34;bare-metal&#34; on the system, where I can declaratively update them at will and not through a hacked-up docker-compose wrapper.&#xA;The configuration process should be done remotely and preferrably automated.&#xA;  Right now my deploy tool of choice is deploy-rs, which I have chosen for its native Nix Flakes support and seemingly careful deployment process. Also, the project is used internally by Serokell, who is also basing their business off of the growth of Nix ecosystem; which means, this project should be maintained well for the foreseeable future! &#xA;  However I have a small gripe that it requires evaluating the Nix configuration of the remote machine on the local machine, which I cannot do from my MacBook because of different environment (aarch64-darwin vs x86_64-linux). &#xA;&#xA;And... that should be good for now! I am still in the process of setting things up. Expect follow up posts in the future! &#xA;&#xA;You can find my entire configuration tree on nix-home (which will be public soon, I promise...!)&#xA;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="/nki/tag:nix" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">nix</span></a> <a href="/nki/tag:hosting" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">hosting</span></a></p>

<p>Lately I have been switching my primary Linux PC from Arch Linux to <a href="https://nixos.org" rel="nofollow">NixOS</a>, a Linux distribution with a functional twist: completely stateless configuration. It&#39;s been quite a pleasant ride, and Nix the expression language, though a bit lacking in friendly documentation, is actually not that hard to get used to (... but the <code>nixpkgs.lib</code> functions are <strong>not</strong>!)</p>

<p>With the help of <a href="https://github.com/nix-community/home-manager" rel="nofollow"><code>home-manager</code></a>, a Nix module/program that manages user-specific configuation in the same Nix language, I have a completely working <code>.config</code> with all the relevant programs installed (<a href="https://kakoune.org" rel="nofollow">kak</a> editor, fish with my bindings, ...) completely synchronized between my M1 MacBook Air and the Linux PC.</p>

<p>Of course, to expand the reach of Nix, I decided to move my DigitalOcean node, where I run my Discord bots, a personal email server, a git server and a small photo store, to NixOS as well.</p>



<h2 id="what-are-the-goals-of-a-remote-nixos-configuration">What are the goals of a remote NixOS configuration?</h2>
<ul><li>Well, for a starter, it should be able to run all the stuff I have been running.
This is actually not trivial. I run most of my stuff on the old DigitalOcean node as a bunch of Docker Compose containers, each with their own <code>docker-compose.yml</code> file (but sharing the same reverse proxy and PostgreSQL database!)
However with NixOS I want all of them to run “bare-metal” on the system, where I can declaratively update them at will and not through a hacked-up <code>docker-compose</code> wrapper.</li>
<li>The configuration process should be done remotely and preferrably automated.
Right now my deploy tool of choice is <a href="https://github.com/Serokell/deploy-rs" rel="nofollow"><code>deploy-rs</code></a>, which I have chosen for its native Nix Flakes support and seemingly careful deployment process. Also, the project is used internally by Serokell, who is also basing their business off of the growth of Nix ecosystem; which means, this project should be maintained well for the foreseeable future!
However I have a small gripe that it requires evaluating the Nix configuration of the remote machine on the local machine, which I cannot do from my MacBook because of different environment (<code>aarch64-darwin</code> vs <code>x86_64-linux</code>).</li></ul>

<p>And... that should be good for now! I am still in the process of setting things up. Expect follow up posts in the future!</p>

<p>You can find my entire configuration tree on <a href="https://github.com/natsukagami/nix-home" rel="nofollow"><code>nix-home</code></a> (which will be public soon, I promise...!)</p>
]]></content:encoded>
      <guid>https://blog.dtth.ch/nki/moving-my-digitalocean-node-to-nix-chapter-1</guid>
      <pubDate>Sun, 31 Oct 2021 02:00:00 +0000</pubDate>
    </item>
  </channel>
</rss>