[{"comments":false,"content":"\u003cp\u003eToday is my birthday, but I\u0026rsquo;m not in the mood to celebrate. Today marks \u003cstrong\u003e131 days\u003c/strong\u003e since my legal status in Poland has basically become \u003cem\u003etax-paying prisoner\u003c/em\u003e, which was the day my second temporary residency card has expired (April 16, 2026).\u003c/p\u003e\n\u003cp\u003eToday is \u003cstrong\u003eDay 439\u003c/strong\u003e since my residency application was submitted on June 12, 2025. The Polish law says it shouldn\u0026rsquo;t take more than \u003cem\u003e60 days\u003c/em\u003e.\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '⏳';\"\u003e\n  \u003ch4\u003ePobyt stały? Na razie stałe jest tylko czekanie.\u003c/h4\u003e\n  \u003cp\u003e\n\"Permanent residence? For now, the only permanent is wait.\"\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cp\u003eDuring this period, we\u0026rsquo;re left in the limbo, unable to travel for business or family urgencies. Need to make it to a funeral? Attend your brother\u0026rsquo;s wedding? Forget about it, you\u0026rsquo;re not allowed to leave the country without losing years of progress on your residency status.\u003c/p\u003e\n\u003cp\u003eThe same happened with my two previous temporary residency permits. The first one took 18 months and it was issued just for 1 year (11 months in practice since they take an extra month to produce the physical card 🤦‍♂). The second one took \u0026ldquo;only\u0026rdquo; 9 months with a good lawyer.\u003c/p\u003e\n\u003cp\u003eYesterday, a \u003ca href=\"https://glos-migranta.eu/\"\u003eprotest\u003c/a\u003e has been held in Warsaw on the \u003ca href=\"https://nowinywschodnie.pl/placimy-podatki-czekamy-latami-protest-cudzoziemcow-w-warszawie/\"\u003ematter\u003c/a\u003e. Wish I could have joined.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/immigration/protest.jpg\" alt=\"protest\"\u003e\u003c/p\u003e\n\u003cp\u003eGovernment officials are bluntly (and in many cases \u003cem\u003eunconstitutionally\u003c/em\u003e) violating a few laws clearly outlined in \u003ca href=\"https://warsawpoint.com/politics/92285-the-394-day-prison-how-a-residence-card-holders-life-is-suspended-by-official-delay.html\"\u003ethis article\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eMy lawyer has submitted a formal inaction complaint (\u003cem\u003e\u0026ldquo;ponaglenie\u0026rdquo;\u003c/em\u003e), which has been ignored by the \u003ca href=\"https://wsc.gdansk.uw.gov.pl/en\"\u003eDepartment for Foreigners\u0026rsquo; Affairs\u003c/a\u003e (or \u003cem\u003ePomorski Urząd Wojewódzki w Gdańsku\u003c/em\u003e) as it has become the norm. We have now submitted a complaint for inaction with the administrative court (\u003cem\u003e\u0026ldquo;Skarga do WSA na bezczynność\u0026rdquo;\u003c/em\u003e) and are awaiting a response, but it may not be enough.\u003c/p\u003e\n\u003ch2 id=\"background-story\"\u003eBackground Story\u003c/h2\u003e\n\u003cp\u003eMy Polish wife and I moved here from Japan 🇯🇵 during the pandemic madness in early 2020, so she could stay close to her grandparents that were at the time in a very delicate state of health. They have passed away during this time, but we remained in the country so she could take care of her mother as the single daughter she is.\u003c/p\u003e\n\u003cp\u003eEver since, we have built a peaceful life here; adopted two cats (\u003ca href=\"https://www.caninejournal.com/adopt-dont-shop/\"\u003eadopt, don\u0026rsquo;t shop\u003c/a\u003e) and have simply been two \u003cem\u003elaw-abiding citizens\u003c/em\u003e, much like the estimated \u003ca href=\"https://biznes.interia.pl/gospodarka/news-gus-ujawnil-nowe-dane-o-emigracji-coraz-mniej-polakow-wyjezd,nId,23527904\"\u003e1.51 million Polish citizens\u003c/a\u003e that lived abroad during 2025.\u003c/p\u003e\n\u003cp\u003eThough, my connection with Poland has existed before even moving here. I gave \u003ca href=\"../../tags/poland/\"\u003econference talks\u003c/a\u003e and even learned some Polish words circa 2015/16 when I lived in Ireland 🇮🇪. I remember meeting a few Poles that taught me what they don\u0026rsquo;t teach you in school \u003cem\u003e#iykyk\u003c/em\u003e 😅\u003c/p\u003e\n\u003cp\u003eIn hindsight, I wish I had done my Italian 🇮🇹 citizenship when I had the chance before the \u003ca href=\"https://italylawfirms.com/en/the-jus-sanguinis-2025-reform-italian-citizenship-law/\"\u003elaw was changed\u003c/a\u003e in March 2025 (talk about luck), but the process wasn\u0026rsquo;t any less bureaucratic so\u0026hellip; My father\u0026rsquo;s family were immigrants from \u003ca href=\"https://maps.app.goo.gl/oLYoz562AekZhMH87\"\u003eFoggia, Italy\u003c/a\u003e that have fled the war by ship to Argentina 🇦🇷 in search of a better life. My last name \u0026ldquo;Volpe\u0026rdquo; means \u0026ldquo;fox\u0026rdquo; 🦊 in Italian 🤌.\u003c/p\u003e\n\u003ch2 id=\"tax-contributions\"\u003eTax Contributions\u003c/h2\u003e\n\u003cp\u003eDuring my time here, I have built a business and have \u003cem\u003epositively contributed\u003c/em\u003e to the local economy, though the playground isn\u0026rsquo;t fair game.\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '🤑';\"\u003e\n  \u003ch4\u003ePaństwo chce podatków na czas. My chcemy decyzji na czas.\u003c/h4\u003e\n  \u003cp\u003e\n\"The State wants taxes on time. We want decisions on time.\"\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cp\u003eTo give you an idea, here\u0026rsquo;s the amount of money I have paid in Personal Income Taxes (PIT) during last year in Poland:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e2025\u003c/strong\u003e: \u003cem\u003e121.794,84 zł\u003c/em\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eI have totalled nearly \u003cem\u003ea million złoty\u003c/em\u003e (\u003cem\u003e~271k USD\u003c/em\u003e) in PIT contributions, including 2026, but I cannot carry on like this.\u003c/p\u003e\n\u003ch2 id=\"blocked-bank-accounts\"\u003eBlocked Bank Accounts\u003c/h2\u003e\n\u003cp\u003eTax obligations come after us, but it\u0026rsquo;s becoming a mission impossible to remain compliant.\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '🚫';\"\u003e\n  \u003ch4\u003eKarty brak. Konto zablokowane. Podatki nadal do zapłaty.\u003c/h4\u003e\n  \u003cp\u003e\n\"No card. Account blocked. Taxes still payable.\"\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cp\u003e\u003ca href=\"https://wise.com/\"\u003eWise\u003c/a\u003e has suspended my bank account yesterday. \u003ca href=\"https://www.revolut.com/\"\u003eRevolut\u003c/a\u003e has given me an ultimatum. \u003ca href=\"https://www.pekao.com.pl/\"\u003ePekao\u003c/a\u003e (local bank) is threatening to close my bank account. All because \u003cem\u003ethey require me to present valid proof of residency which I don\u0026rsquo;t have\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eHow am I supposed to pay taxes when I\u0026rsquo;m unable to have a bank account? 🤷‍♂ \u003cstrong\u003eTo naprawdę nie ma sensu\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eThis is an incredibly \u003cem\u003efrustrating and stressful\u003c/em\u003e situation. I don\u0026rsquo;t wish anyone go through this hell.\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '🏦';\"\u003e\n  \u003ch4\u003eUrząd czeka. Bank nie czeka.\u003c/h4\u003e\n  \u003cp\u003e\n\"The office waits. The bank doesn't.\"\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cp\u003eAll I have is a stamp on my passport that is not accepted by any banks that are regulated by EU laws.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/immigration/stamp.jpg\" alt=\"stamp\"\u003e\u003c/p\u003e\n\u003cp\u003eThe average processing times for residency applications is now of \u003cem\u003e2+ years\u003c/em\u003e and that is a very long time.\u003c/p\u003e\n\u003ch2 id=\"mental-health\"\u003eMental Health\u003c/h2\u003e\n\u003cp\u003eThis whole situation has certainly taken a toll on my mental health, and many times I have considered just throwing in the towel, but I\u0026rsquo;ve being through worse times and I know I\u0026rsquo;m capable of getting through this as well. I won\u0026rsquo;t just give up, though the damage has already been done.\u003c/p\u003e\n\u003cp\u003e\u003cem\u003eMy life has been literally put on pause\u003c/em\u003e, all while having to maintain my legal obligations. This is cruel and inhumane.\u003c/p\u003e\n\u003cp\u003eEver since I was born, my life was a constant battle uphill having been raised by a poor family in a humble Argentine neighborhood. It always felt like the Sisyphus story from Greek mythology, and even though I\u0026rsquo;m used to fight for my rights, it really is exhausting.\u003c/p\u003e\n\u003ch2 id=\"final-thoughts\"\u003eFinal Thoughts\u003c/h2\u003e\n\u003cp\u003ePoland is a wonderful country with fantastic people. My wife and I would love to remain here to continue building our future together, so we\u0026rsquo;re hoping things improve for the thousands of \u003cem\u003elegal immigrants\u003c/em\u003e currently affected.\u003c/p\u003e\n\u003cp\u003eThough, even if we end up moving elsewhere where we\u0026rsquo;re treated more fairly, I would never forget its people and the many long-lasting and meaningful relationships we have built around here. \u003cem\u003eNiech żyje Polska!\u003c/em\u003e 🇵🇱\u003c/p\u003e\n\u003cp\u003eIf you\u0026rsquo;ve made it until here, I ask you to share this with as many as possible🙏, perhaps some leaders are listening. To those who had come here for my tech content, I promise that will resume soon, but I needed to get this out of my chest before my head implodes 🤯.\u003c/p\u003e\n\u003cp\u003eThe ask is fairly simple: \u003cstrong\u003eGovernment officials to respect and follow the Polish law!\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eAnd to the internet trolls spewing out xenophobic comments, I love you all 😘 (\u003cem\u003e#MakeLoveNotWar\u003c/em\u003e)\u003c/p\u003e\n\u003cp\u003ePeace out ✌,\nGabriel.\u003c/p\u003e\n","date":"2026-08-25","dateFormatted":"2026.08.25","excerpt":"Today is my birthday, but I\u0026rsquo;m not in the mood to celebrate. Today marks 131 days since my legal status in Poland has basically become tax-paying prisoner, …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/tax-paying-prisoner-in-poland/","readingTime":5,"slug":"tax-paying-prisoner-in-poland","subtitle":null,"summary":"\u003cp\u003eToday is my birthday, but I\u0026rsquo;m not in the mood to celebrate. Today marks \u003cstrong\u003e131 days\u003c/strong\u003e since my legal status in Poland has basically become \u003cem\u003etax-paying prisoner\u003c/em\u003e, which was the day my second temporary residency card has expired (April 16, 2026).\u003c/p\u003e\n\u003cp\u003eToday is \u003cstrong\u003eDay 439\u003c/strong\u003e since my residency application was submitted on June 12, 2025. The Polish law says it shouldn\u0026rsquo;t take more than \u003cem\u003e60 days\u003c/em\u003e.\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '⏳';\"\u003e\n  \u003ch4\u003ePobyt stały? Na razie stałe jest tylko czekanie.\u003c/h4\u003e\n  \u003cp\u003e\n\"Permanent residence? For now, the only permanent is wait.\"\n\u003c/p\u003e","tags":["poland","immigration"],"title":"A tax-paying prisoner in Poland","url":"https://gvolpe.com/blog/tax-paying-prisoner-in-poland/","wordCount":1024},{"comments":true,"content":"\u003cdiv class=\"thumbnail\"\u003e\n  \u003cimg src=\"https://systemd.io/assets/systemd-logo.svg#systemd-logo\" alt=\"thumbnail\" style=\"max-width:100%; height:auto;\"\u003e\u003c/img\u003e\n\u003c/div\u003e\n\n\u003cp\u003eThe System and Service Manager \u003ca href=\"https://systemd.io/\"\u003esystemd\u003c/a\u003e provides the building blocks for Linux systems, and it\u0026rsquo;s the default in NixOS \u0026mdash; though, there are a few alternatives such as \u003ca href=\"https://github.com/finix-community/finix\"\u003efinix\u003c/a\u003e and \u003ca href=\"https://codeberg.org/amjoseph/sixos\"\u003esixos\u003c/a\u003e that currently explore this design space.\u003c/p\u003e\n\u003cp\u003eAs someone currently self-hosting a few services on my NixOS servers, it\u0026rsquo;s become a constant in my toolkit.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/topology.svg\" alt=\"topology\"\u003e\n\u003cem\u003eCurrent status of my servers, as generated by \u003ca href=\"https://oddlama.github.io/nix-topology/\"\u003enix-topology\u003c/a\u003e (with a few custom patches) ❄️\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"runtime-schedule\"\u003eRuntime Schedule\u003c/h2\u003e\n\u003cp\u003eRecently, I\u0026rsquo;ve been doing some upgrades and maintenance in one of my servers running \u003ca href=\"../immich-photos\"\u003eImmich\u003c/a\u003e, and I thought I would check the current job schedule to ensure they are configured correctly in my timezone.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-console\" data-lang=\"console\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[admin@metropolis:~]$ systemctl list-timers --all\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eNEXT                              LEFT LAST                             PASSED UNIT                                 ACTIVATES\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSat 2026-08-15 10:44:47 CEST     15min Sat 2026-08-15 10:14:47 CEST  14min ago disk-monitor.timer                   disk-monitor.service\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSat 2026-08-15 11:00:00 CEST     30min Sat 2026-08-15 10:00:08 CEST  29min ago logrotate.timer                      logrotate.service\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSat 2026-08-15 15:21:37 CEST  4h 52min Fri 2026-08-14 15:21:40 CEST    19h ago acme-renew-immich.gvolpe.com.timer   acme-order-renew-immich.gvolpe.com.service\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSat 2026-08-15 22:52:43 CEST       12h Fri 2026-08-14 22:52:43 CEST    11h ago systemd-tmpfiles-clean.timer         systemd-tmpfiles-clean.service\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSun 2026-08-16 00:00:00 CEST       13h Sat 2026-08-15 00:00:08 CEST    10h ago custom-immich-stop-trigger.timer     custom-immich-stop-trigger.service\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSun 2026-08-16 00:00:01 CEST       13h Sat 2026-08-15 00:00:08 CEST    10h ago postgresql-backup.timer              postgresql-backup.service\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSun 2026-08-16 00:00:05 CEST       13h Sat 2026-08-15 00:00:08 CEST    10h ago borgbackup-job-immich-s3-media.timer borgbackup-job-immich-s3-media.service\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSun 2026-08-16 00:00:05 CEST       13h Sat 2026-08-15 00:00:08 CEST    10h ago borgbackup-job-postgres.timer        borgbackup-job-postgres.service\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSun 2026-08-16 00:00:30 CEST       13h Sat 2026-08-15 00:00:35 CEST    10h ago s3-replica.timer                     s3-replica.service\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSun 2026-08-16 03:00:00 CEST       16h Sat 2026-08-15 03:00:08 CEST     7h ago s3ql-fsck.timer                      s3ql-fsck.service\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSun 2026-08-16 03:30:00 CEST       17h Sat 2026-08-15 03:30:06 CEST     6h ago custom-immich-start-trigger.timer    custom-immich-start-trigger.service\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eMon 2026-08-17 00:00:00 CEST 1 day 13h Mon 2026-08-10 02:00:08 CEST 5 days ago nix-gc.timer                         nix-gc.service\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eMon 2026-08-17 01:39:34 CEST 1 day 15h Mon 2026-08-10 02:52:22 CEST 5 days ago fstrim.timer                         fstrim.service\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u003c/span\u003e13 timers listed.\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cem\u003eAs the number of jobs grows, it\u0026rsquo;s easy to lose track.\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003eHowever, looking at this information has got me wondering whether it was possible to get this information directly from my NixOS configuration instead of having to deploy before checking \u0026mdash; i.e. build-time checks instead of runtime checks.\u003c/p\u003e\n\u003ch2 id=\"build-time-schedule\"\u003eBuild-Time Schedule\u003c/h2\u003e\n\u003cp\u003eA nice property of NixOS is that it produces a derivation at build time, which is a build recipe of the entire system that can be easily inspected; and the systemd configuration that\u0026rsquo;s part of it is no exception.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-console\" data-lang=\"console\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enix-repl\u0026gt; nixosConfigurations.metropolis.config.systemd.timers\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u0026#34;acme-renew-immich.gvolpe.com\u0026#34; = { ... };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  borgbackup-job-immich-s3-media = { ... };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  borgbackup-job-postgres = { ... };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  custom-immich-start-trigger = { ... };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  custom-immich-stop-trigger = { ... };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  disk-monitor = { ... };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  fstrim = { ... };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  logrotate = { ... };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  nix-gc = { ... };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  postgresql-backup = { ... };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  s3-replica = { ... };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  s3ql-fsck = { ... };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u003c/span\u003enix-repl\u0026gt; nixosConfigurations.metropolis.config.systemd.timers.s3-replica.timerConfig\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  OnCalendar = \u0026#34;*-*-* 0:00:30\u0026#34;;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  Persistent = false;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eSo the information is available and we can process it however we see fit. In my case, I wanted to create a Mermaid flow diagram with each server\u0026rsquo;s schedule, and here\u0026rsquo;s the result (see it on \u003ca href=\"https://mermaid.live/edit#pako:eNqtlVtu2zgUhrdCMCh6geXoakua1EDdGH1rHprBAAO_0BQtc0yKGpJK4gRZy8wOurUuoRSlKLLj2GkR2TBE-vzU9x8eHd5BLDICU_jmDfhCCiKRJhlYSsEBuUKsssOv9ObiG1AbpQnPgKacSDWcF0smrvEKSQ0uz-cFMH9oRt7N4WUdABRekaxiBCyFPFvI0wk4W0w40VKUglF1drqYNNPvZpWZI6d_IanQ9fs5fF8vhhlS6pwsm1XBkjKWniSzaTIbDZRZZE3Sk6W9BlgwIdMTz1472pqk0U7jmTudvly7EEK30s9JLX65tCSSioziVj4bTUefvJfJW7v2t568dE1CTeI-OOYDXDe13y55uFJacIdyTvHKMfeloyXNcyKH1rsNOitN8OTHf_9_t7rTcgIO6BSRVxST_jYYiiaV3S4Dx5mYWUvobRFaQK8DLIXSuSTqX-YsEF5X5WGup-F7cbxHHINmWWwKL_2nLFHHshAyb1Z1_hGLznzgcJJRdBjsiLalbNVA6Q0jH982W5xLtPnj7cQUhaLmDSpMVSGmSL3stiu_58prXPl2EPyCq4cM_pKdTvQqPoKeD7_xEdhB-MRH8FjKJpeSlIziIzvRi3sV2rBHGzS0oR1EW7TBzpunAlOjS4XXx2gfwl4FNurBhg1sZAejHdjgYJswPfv3-kRfuPfNHPUAowZwZAfjBvCimJq--o3gjxEfgIviz4LqT1jTK1LPBS7vmDOq1g4X5n9xBHIrci_V2Hbzne41tlxxw5UhyjbdsxHmxFRZQa5b68P8SrCSDLHgh1msUsiMyGf1LygEiYpMcHprzt6MMLQBfrhq2uljhWyIelofcXf27JiNrdmkMbsSley5ZSKXQpuT_rC1x7C9OU6ee3TSNLT2ILsmZN179tKcivRITtuYrbytzE9Bb5wcH9a2Mb-Tc3c341pWe15Jz33OuGfORziAuaQZTGvxABpSjuohvKuD51CvCDeJTM1thuR6DufFvdGUqPhbCP4gk6LKVzC1TWEAqzIzu3BOkSHn3awpN1N1n0VVaJiOI7sGTO_gDUz9MBkmrhuEXhj5sZcEowHcwNSJ46HnReMwdt1k7Id-cD-At_ax3tB3_XHsB1EYJ7Efef79T2O6XiQ\"\u003eMermaid Live\u003c/a\u003e).\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/systemd-schedule.svg\" alt=\"schedule\"\u003e\u003c/p\u003e\n\u003cp\u003eThis was hacked-up together with a Python script that fetches the core information with the following command:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-console\" data-lang=\"console\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enix eval --json .#nixosConfigurations.metropolis.config.systemd.timers \\\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e         --apply \u0026#39;builtins.mapAttrs (name: timer: timer.timerConfig)\u0026#39; | jq\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWhich produces the following JSON data:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-json\" data-lang=\"json\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;acme-renew-immich.gvolpe.com\u0026#34;\u003c/span\u003e: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;AccuracySec\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;86400s\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;FixedRandomDelay\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;OnCalendar\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;daily\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;Persistent\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;yes\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;RandomizedDelaySec\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;24h\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;Unit\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;acme-order-renew-immich.gvolpe.com.service\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;borgbackup-job-immich-s3-media\u0026#34;\u003c/span\u003e: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;OnCalendar\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;*-*-* 0:00:05\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;Persistent\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;borgbackup-job-postgres\u0026#34;\u003c/span\u003e: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;OnCalendar\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;*-*-* 0:00:05\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;Persistent\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;custom-immich-start-trigger\u0026#34;\u003c/span\u003e: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;OnCalendar\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;*-*-* 03:30:00\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;Unit\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;custom-immich-start-trigger.service\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;custom-immich-stop-trigger\u0026#34;\u003c/span\u003e: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;OnCalendar\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;*-*-* 00:00:00\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;Unit\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;custom-immich-stop-trigger.service\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;disk-monitor\u0026#34;\u003c/span\u003e: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;OnBootSec\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;5m\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;OnUnitActiveSec\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;30m\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;fstrim\u0026#34;\u003c/span\u003e: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;OnCalendar\u0026#34;\u003c/span\u003e: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;weekly\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;logrotate\u0026#34;\u003c/span\u003e: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;OnCalendar\u0026#34;\u003c/span\u003e: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;hourly\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;nix-gc\u0026#34;\u003c/span\u003e: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;OnCalendar\u0026#34;\u003c/span\u003e: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;weekly\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;Persistent\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;RandomizedDelaySec\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;0\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;postgresql-backup\u0026#34;\u003c/span\u003e: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;OnCalendar\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;*-*-* 0:00:01\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;Unit\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;postgresql-backup.service\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;s3-replica\u0026#34;\u003c/span\u003e: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;OnCalendar\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;*-*-* 0:00:30\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;Persistent\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;s3ql-fsck\u0026#34;\u003c/span\u003e: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;OnCalendar\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;*-*-* 03:00:00\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;Persistent\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;Unit\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;s3ql-fsck.service\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThen it\u0026rsquo;s all about processing this data to generate the Mermaid diagram in the desired format.\u003c/p\u003e\n\u003ch3 id=\"timezone\"\u003eTimezone\u003c/h3\u003e\n\u003cp\u003eThe script follows this order of evaluation, and it defaults to UTC if both values are absent.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-console\" data-lang=\"console\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enix-repl\u0026gt; nixosConfigurations.metropolis.config.systemd.globalEnvironment.TZ\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026#34;Europe/Warsaw\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u003c/span\u003enix-repl\u0026gt; nixosConfigurations.metropolis.config.time.timeZone\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026#34;Europe/Warsaw\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eMultiple timezones can be set individually on each service unit, but this is not a feature I need right now.\u003c/p\u003e\n\u003ch2 id=\"runtime-dependencies\"\u003eRuntime Dependencies\u003c/h2\u003e\n\u003cp\u003eOn a running server, we can generate a diagram of systemd dependencies with the following command:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-console\" data-lang=\"console\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esystemd-analyze dot --require --from-pattern=\u0026#34;*.service\u0026#34; --to-pattern=\u0026#34;*.service\u0026#34; \\\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  | dot -Tsvg \u0026gt; services.svg\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWhich renders something like this for this NixOS server:\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/deps.svg\" alt=\"deps\"\u003e\u003c/p\u003e\n\u003cp\u003eThat can quickly get pretty unreadable, so in general it\u0026rsquo;s more useful to analyze specific units and not all of them, e.g.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-console\" data-lang=\"console\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[admin@metropolis:~]$ systemctl show immich-server.service -p Requires -p Wants\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eRequires=postgresql.target s3ql-mount.service -.mount system-immich.slice\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eWants=-.mount\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThese tools are useful for our daily server maintenance tasks, but can we verify dependencies without actually deploying first?\u003c/p\u003e\n\u003ch2 id=\"build-time-dependencies\"\u003eBuild-Time Dependencies\u003c/h2\u003e\n\u003cp\u003eIt is no surprise that this information is available in our NixOS configuration blueprint as well.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-console\" data-lang=\"console\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enix-repl\u0026gt; nixosConfigurations.metropolis.config.systemd.services.s3 \u0026lt;TAB\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enixosConfigurations.metropolis.config.systemd.services.s3-aliases\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enixosConfigurations.metropolis.config.systemd.services.s3-replica\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enixosConfigurations.metropolis.config.systemd.services.s3ql-auth\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enixosConfigurations.metropolis.config.systemd.services.s3ql-fs\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enixosConfigurations.metropolis.config.systemd.services.s3ql-fsck\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enixosConfigurations.metropolis.config.systemd.services.s3ql-mount\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe following one-liner command shows how to get all systemd dependencies directly from our NixOS configuration:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-console\" data-lang=\"console\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enix eval --json .#nixosConfigurations.metropolis.config.systemd.services \\\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e         --apply \u0026#39;builtins.mapAttrs (name: service: { inherit (service) requires wants wantedBy requiredBy; })\u0026#39; \\\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e         | jq \u0026#39;with_entries(.value |= with_entries(select(.value != []))) | with_entries(select(.value != {}))\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eTo keep it brief, here\u0026rsquo;s part of the JSON data that\u0026rsquo;s produced by it:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-json\" data-lang=\"json\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;immich-machine-learning\u0026#34;\u003c/span\u003e: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;requires\u0026#34;\u003c/span\u003e: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;postgresql.target\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;s3ql-mount.service\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;wantedBy\u0026#34;\u003c/span\u003e: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;multi-user.target\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;immich-server\u0026#34;\u003c/span\u003e: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;requires\u0026#34;\u003c/span\u003e: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;postgresql.target\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;s3ql-mount.service\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;wantedBy\u0026#34;\u003c/span\u003e: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;multi-user.target\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;nginx\u0026#34;\u003c/span\u003e: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;wantedBy\u0026#34;\u003c/span\u003e: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;multi-user.target\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;wants\u0026#34;\u003c/span\u003e: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;acme-immich.gvolpe.com.service\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eI didn\u0026rsquo;t create any diagrams out of this, but we have the necessary data if that\u0026rsquo;s something we want to do.\u003c/p\u003e\n\u003ch2 id=\"final-thoughts\"\u003eFinal thoughts\u003c/h2\u003e\n\u003cp\u003eI thought this was worth sharing, as NixOS doesn\u0026rsquo;t cease to amaze me 🤩. Safe to say I\u0026rsquo;m always learning new things, and more importantly, having tons of fun running these servers! Can\u0026rsquo;t recommend it enough.\u003c/p\u003e\n\u003cp\u003eWhat do you think? Let me know in the comments below 👇\u003c/p\u003e\n\u003cp\u003eBest,\nGabriel.\u003c/p\u003e\n","date":"2026-08-15","dateFormatted":"2026.08.15","excerpt":" The System and Service Manager systemd provides the building blocks for Linux systems, and it\u0026rsquo;s the default in NixOS \u0026mdash; though, there are a few …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/systemd-schedule/","readingTime":5,"slug":"systemd-schedule","subtitle":null,"summary":"\u003cdiv class=\"thumbnail\"\u003e\n  \u003cimg src=\"https://systemd.io/assets/systemd-logo.svg#systemd-logo\" alt=\"thumbnail\" style=\"max-width:100%; height:auto;\"\u003e\u003c/img\u003e\n\u003c/div\u003e\n\n\u003cp\u003eThe System and Service Manager \u003ca href=\"https://systemd.io/\"\u003esystemd\u003c/a\u003e provides the building blocks for Linux systems, and it\u0026rsquo;s the default in NixOS \u0026mdash; though, there are a few alternatives such as \u003ca href=\"https://github.com/finix-community/finix\"\u003efinix\u003c/a\u003e and \u003ca href=\"https://codeberg.org/amjoseph/sixos\"\u003esixos\u003c/a\u003e that currently explore this design space.\u003c/p\u003e\n\u003cp\u003eAs someone currently self-hosting a few services on my NixOS servers, it\u0026rsquo;s become a constant in my toolkit.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/topology.svg\" alt=\"topology\"\u003e\n\u003cem\u003eCurrent status of my servers, as generated by \u003ca href=\"https://oddlama.github.io/nix-topology/\"\u003enix-topology\u003c/a\u003e (with a few custom patches) ❄️\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"runtime-schedule\"\u003eRuntime Schedule\u003c/h2\u003e\n\u003cp\u003eRecently, I\u0026rsquo;ve been doing some upgrades and maintenance in one of my servers running \u003ca href=\"../immich-photos\"\u003eImmich\u003c/a\u003e, and I thought I would check the current job schedule to ensure they are configured correctly in my timezone.\u003c/p\u003e","tags":["cloud","systemd","nixos"],"title":"Build-time systemd schedule","url":"https://gvolpe.com/blog/systemd-schedule/","wordCount":988},{"comments":true,"content":"\u003cp\u003eThis blogpost would have been too short if I only talked about a single topic, so I decided to combine both topics into a single post, as both qualify as actual Quality Of Service (QoS) improvements to my workflows.\u003c/p\u003e\n\u003ch2 id=\"sunix-software-updates-for-nix\"\u003eSUNix: Software Updates for Nix\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/gvolpe/sunix\"\u003eSUNix\u003c/a\u003e is a recent project I worked on that provides a feature many standard Linux distros ship with: \u003cstrong\u003e\u0026ldquo;Available Software Updates\u0026rdquo;\u003c/strong\u003e. Ever wanted to know what packages would be upgraded or removed with the next \u003ccode\u003enixpkgs\u003c/code\u003e upgrade without switching yet?\u003c/p\u003e\n\u003cp\u003eThe following video showcases the first version \u003ccode\u003e0.1.0\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv style=\"position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;\"\u003e\n      \u003ciframe allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen\" loading=\"lazy\" referrerpolicy=\"strict-origin-when-cross-origin\" src=\"../../images/sunix/sunix.mp4?autoplay=0\u0026amp;controls=1\u0026amp;end=0\u0026amp;loop=0\u0026amp;mute=0\u0026amp;start=0\" style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;\" title=\"Local video\"\u003e\u003c/iframe\u003e\n    \u003c/div\u003e\n\n\u003ch3 id=\"diff-reports\"\u003eDiff Reports\u003c/h3\u003e\n\u003cp\u003eFurthermore, the newer version \u003ccode\u003e0.2.0\u003c/code\u003e ships with a new feature: \u003cstrong\u003ePDF and Markdown reports\u003c/strong\u003e. I was too lazy to record another video just to show that, but you can get a pretty good idea by looking at the Markdown reports generated for \u003ca href=\"https://github.com/gvolpe/nix-config/pull/334\"\u003ethis Pull Request\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/sunix/sunix-report-pr.png\" alt=\"sunix-pr\"\u003e\u003c/p\u003e\n\u003cp\u003eThese reports can be generated directly from the UI or via the CLI, e.g.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-console\" data-lang=\"console\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enix build --print-out-paths --no-link \\\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  .#nixosConfigurations.\u0026lt;your_flake\u0026gt;.config.system.build.toplevel \\\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  | xargs dix /run/current-system/ \\\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  | sunix --markdown-report \u0026#34;NixOS .#\u0026lt;your_flake\u0026gt;\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAnd the PDF reports are very pretty as well 🤩, but that\u0026rsquo;s just a bonus.\u003c/p\u003e\n\u003ch2 id=\"dots-the-hm-module-you-didnt-know-you-needed\"\u003eDots: the HM module you didn\u0026rsquo;t know you needed\u003c/h2\u003e\n\u003cp\u003eThis is a quick follow-up post from \u003ca href=\"../home-manager-dotfiles-management\"\u003eHome Manager: dotfiles management\u003c/a\u003e, written a while ago now. In the last part of this old post, I\u0026rsquo;ve introduced a \u003ccode\u003edotfiles\u003c/code\u003e Home Manager module, which was on the right track.\u003c/p\u003e\n\u003cp\u003eHowever, each module that needed to handle mutable configuration files looked something like this:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ pkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  filePath \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003econfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edotfiles\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epath\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/programs/neofetch/electric.conf\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  configSrc \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003econfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edotfiles\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emutable \u003cspan style=\"color:#66d9ef\"\u003ethen\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e./electric.conf\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efile\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkOutOfStoreSymlink filePath;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  home\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epackages \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eneofetch ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  xdg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003econfigFile\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;neofetch/config.conf\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esource \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e configSrc;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe \u003ccode\u003emkOutOfStoreSymlink\u003c/code\u003e function was leaking everywhere, so I\u0026rsquo;ve come up with a better implementation of the \u003ccode\u003edotfiles\u003c/code\u003e module ever since. To give you an idea on the current looks, the equivalent would now be this (if I still used \u003ccode\u003eneofetch\u003c/code\u003e):\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ pkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  home\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epackages \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eneofetch ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  xdg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003econfigFile\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;neofetch/config.conf\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esource \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edotfiles\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emake \u003cspan style=\"color:#e6db74\"\u003e./electric.conf\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eNeat! Isn\u0026rsquo;t it? The \u003ccode\u003edotfiles.make\u003c/code\u003e function encapsulates all the symlinking logic now.\u003c/p\u003e\n\u003cp\u003eThis newly improved module lives now under the \u003ca href=\"https://github.com/gvolpe/dots\"\u003edots\u003c/a\u003e repo, and everyone can benefit from it. All you have to do is import the module (see instructions in the \u003cstrong\u003eREADME\u003c/strong\u003e file) and set the following options:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ config\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  dotfiles \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    mutable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    path \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003econfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehome\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehomeDirectory\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/workspace/nix-config/home\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    sourceRoot \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e./.\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe module has good test coverage, and the documentation explains these options in detail if you\u0026rsquo;d like to learn more.\u003c/p\u003e\n\u003ch3 id=\"why-mutability\"\u003eWhy mutability?\u003c/h3\u003e\n\u003cp\u003eAn essential part of my workflows is the ability to tinker with configuration files freely without \u003ccode\u003ehome-manager switch\u003c/code\u003e getting in the way of experimenting \u0026mdash; like adding keybindings to my Niri configuration or adjusting the blur levels of certain layers.\u003c/p\u003e\n\u003cp\u003eAt the same time, I don\u0026rsquo;t want to give up the reproducibility and portability that immutable configuration files give me. For instance, I could share my Neovim wrapper and anyone could run the same exact version with all the plugins and keybindings I set up.\u003c/p\u003e\n\u003cp\u003eThe \u003ccode\u003edotfiles\u003c/code\u003e module gives us the best of both worlds with none of the fuzz.\u003c/p\u003e\n\u003ch2 id=\"final-thoughts\"\u003eFinal thoughts\u003c/h2\u003e\n\u003cp\u003eThese little two projects have brought me much joy recently, so I hope you enjoy them too!\u003c/p\u003e\n\u003cp\u003eBest,\nGabriel.\u003c/p\u003e\n","date":"2026-08-04","dateFormatted":"2026.08.04","excerpt":"This blogpost would have been too short if I only talked about a single topic, so I decided to combine both topics into a single post, as both qualify as actual …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/dots-sunix/","readingTime":3,"slug":"dots-sunix","subtitle":null,"summary":"\u003cp\u003eThis blogpost would have been too short if I only talked about a single topic, so I decided to combine both topics into a single post, as both qualify as actual Quality Of Service (QoS) improvements to my workflows.\u003c/p\u003e\n\u003ch2 id=\"sunix-software-updates-for-nix\"\u003eSUNix: Software Updates for Nix\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/gvolpe/sunix\"\u003eSUNix\u003c/a\u003e is a recent project I worked on that provides a feature many standard Linux distros ship with: \u003cstrong\u003e\u0026ldquo;Available Software Updates\u0026rdquo;\u003c/strong\u003e. Ever wanted to know what packages would be upgraded or removed with the next \u003ccode\u003enixpkgs\u003c/code\u003e upgrade without switching yet?\u003c/p\u003e","tags":["nix","nixos","flakes","home-manager","dotfiles"],"title":"QoS: Dots \u0026 SUNix","url":"https://gvolpe.com/blog/dots-sunix/","wordCount":541},{"comments":true,"content":"\u003cp\u003eOne of the most beloved features \u003ca href=\"../immich-photos\"\u003eImmich\u003c/a\u003e offers (also Google Photos) is \u003cem\u003ememories\u003c/em\u003e. It temporarily transports us back in time to cherish our old precious photos and videos once again. However, we need to remember to open the app on a daily basis to do so \u0026mdash; there\u0026rsquo;s no baked in notification system that would remind us.\u003c/p\u003e\n\u003cp\u003eThis is another \u003ca href=\"https://github.com/immich-app/immich/discussions/3981\"\u003eheavily-requested feature\u003c/a\u003e, and if you check out that discussion, you\u0026rsquo;ll find a couple of vibe-coded solutions like \u003ca href=\"https://github.com/james-maschal/Immich-Memories-Notification\"\u003ethis python script\u003c/a\u003e or \u003ca href=\"https://github.com/ismaildakrory/immich-memories-notify\"\u003ethis dockerized application\u003c/a\u003e. None of them felt right to me, though.\u003c/p\u003e\n\u003cp\u003eSo after living without this feature for the past few months, I finally found the time and energy to tackle the issue, and I\u0026rsquo;d like to share a custom solution that ensures we are reminded to check on our memories 👇\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/memories/android.jpg\" alt=\"android\"\u003e\n\u003cem\u003eSneak peek of a custom Android push notification 🤖\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003eDoes this pick your interest? Keep on reading :)\u003c/p\u003e\n\u003ch2 id=\"memories\"\u003eMemories\u003c/h2\u003e\n\u003cp\u003eNotifications must support multiple users in my case, so I\u0026rsquo;ve created NixOS module that can be used as follows.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  notifications\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eimmich-memories \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    domain \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;immich.app\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    users \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      gab \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        apiKey \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;immich-memories-api-key\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ntfy\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etopic \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;immich\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        schedule \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;*-*-* 08:00:00\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe premise is simple enough if we look at the main script implementation.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  cfg \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enotifications\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eimmich-memories;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  script \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e userCfg: pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ewriteShellApplication {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    name \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;run\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    runtimeInputs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ewith\u003c/span\u003e pkgs; [ coreutils curl jq ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    text \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e      IMMICH_API_KEY=$(cat /run/agenix/\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003euserCfg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eapiKey\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e | tr -d \u0026#39;\\n\u0026#39;)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e      NTFY_API_TOKEN=$(cat /run/agenix/\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003euserCfg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003entfy\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etoken\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e | tr -d \u0026#39;\\n\u0026#39;)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e      TODAY=$(date +\u0026#34;%Y-%m-%d\u0026#34;)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e      MEMORIES=$(curl -s \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e        -H \u0026#34;x-api-key:$IMMICH_API_KEY\u0026#34; \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e        -H \u0026#34;Content-Type: application/json\u0026#34; \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e        \u0026#34;https://\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ecfg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edomain\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/api/memories/statistics?for=$TODAY\u0026#34; | jq .total)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e      if [ \u0026#34;\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\u0026#39;\u0026#39;$\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{MEMORIES:-0}\u0026#34; -ge 1 ]; then\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e        curl -s \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e          -H \u0026#34;Title: Remember this day? 📸\u0026#34; \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e          -H \u0026#34;Click: immich://open\u0026#34; \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e          -H \u0026#34;Authorization: Bearer $NTFY_API_TOKEN\u0026#34; \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e          -d \u0026#34;You have $MEMORIES memories available 🤩\u0026#34; \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e          \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003econfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eservices\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003entfy-sh\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esettings\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ebase-url\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003euserCfg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003entfy\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etopic\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e      fi\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    \u0026#39;\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e { }\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt queries the \u003ca href=\"https://api.immich.app/endpoints/memories\"\u003eImmich Memories API\u003c/a\u003e to check whether we have any memories available (it requires an API key with \u003ccode\u003ememory.statistics\u003c/code\u003e permissions), and if that\u0026rsquo;s the case, it sends a push notification via the \u003ca href=\"https://github.com/binwiederhier/ntfy\"\u003entfy-sh\u003c/a\u003e service.\u003c/p\u003e\n\u003cp\u003eThis job is then triggered on a daily basis via standard systemd timers.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  config \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkIf (cfg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenable \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eservices\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003entfy-sh\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenable) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    systemd \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efoldlAttrs\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      (acc: name: userCfg: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        services \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e acc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eservices \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;immich-memories-notification-\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ename\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Immich Memories Notification for \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ename\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            after \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ntfy-sh.service\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            serviceConfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e              Type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;oneshot\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e              ExecStart \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003escript userCfg\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/run\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        timers \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e acc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etimers \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;immich-memories-notification-trigger-\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ename\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Run Immich Memories Notification check daily for \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ename\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            timerConfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e              OnCalendar \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e userCfg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eschedule;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e              Unit \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;immich-memories-notification-\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ename\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e.service\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            wantedBy \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;timers.target\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      })\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      { services \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e { }; timers \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e { }; }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      cfg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eusers;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe \u003ccode\u003ecfg.users\u003c/code\u003e attrset comes from the aforementioned custom NixOS module, shown below.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  options\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enotifications\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eimmich-memories \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkEnableOption \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Enable Immich memories notifications\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    domain \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkOption {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;The domain on which immich is hosted\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etypes\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estr;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    users \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkOption {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etypes\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eattrsOf (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etypes\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esubmodule (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          { \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            options \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e              apiKey \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkOption {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;The immich api key secret to be looked up under /run/agenix/\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etypes\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estr;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e              };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e              ntfy \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                token \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkOption {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                  description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;The ntfy api token secret to be looked up under /run/agenix/\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                  default \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ntfy-api-token\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                  type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etypes\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estr;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                topic \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkOption {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                  description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;The ntfy topic to send the push notification to\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                  type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etypes\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estr;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e              };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e              schedule \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkOption {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;The calendar time when the job needs to run (default at 8 am)\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                default \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;*-*-* 08:00:00\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etypes\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estr;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e              };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe following screenshot shows the systemd service being triggered for username \u003ccode\u003egab\u003c/code\u003e at 8 am UTC.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/memories/systemd-job.png\" alt=\"service\"\u003e\u003c/p\u003e\n\u003cp\u003eDid I ever mention I love NixOS? 🤩\u003c/p\u003e\n\u003ch3 id=\"notification-service\"\u003eNotification service\u003c/h3\u003e\n\u003cp\u003eTesting can be done quickly via the freely available \u003ca href=\"https://ntfy.sh/\"\u003entfy-sh\u003c/a\u003e service. However, for real-world use you should consider self-hosting with the service behind a reverse proxy, which can\u0026rsquo;t be any easier with NixOS.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ config\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  cfg \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eservices\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003entfy-sh\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esettings;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  domain \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;your-custom-domain.com\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  systemd\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eservices\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003entfy-sh\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eserviceConfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    CacheDirectory \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ntfy-sh\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    StateDirectory \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ntfy-sh\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  services \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ntfy-sh \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      environmentFile \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/run/agenix/ntfy-env\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      settings \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        listen-http \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;127.0.0.1:8080\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        base-url \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;https://\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003edomain\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        behind-proxy \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# cache\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        attachment-cache-dir \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/var/cache/ntfy-sh/attachments\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        cache-file \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/var/cache/ntfy-sh/cache.db\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# authentication\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        auth-file \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/var/lib/ntfy-sh/auth.db\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        auth-default-access \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;deny-all\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# web ui auth\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        enable-login \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        require-login \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    nginx \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      virtualHosts\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003edomain\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        forceSSL \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        enableACME \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        locations\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          proxyPass \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;http://\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ecfg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elisten-http\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          proxyWebsockets \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe \u003ccode\u003eenvironmentFile\u003c/code\u003e option is perhaps the most relevant here; it is where we declare our admin user and available access tokens. It looks as follows (then encrypted via \u003ca href=\"https://github.com/ryantm/agenix\"\u003eagenix\u003c/a\u003e):\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-ini\" data-lang=\"ini\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eNTFY_AUTH_USERS\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;myuser:$2b$10$HY51Myw1T/tOV6dQHFJak.7P5JohyXXojxVqt4.PQMDjEZIeAb8MC:admin\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eNTFY_AUTH_TOKENS\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;myuser:tk_8zqjy8boky0s1kogjggdq35ej0ff1\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eYou can try their \u003ca href=\"https://docs.ntfy.sh/config/#config-generator\"\u003econfig generator\u003c/a\u003e or use the \u003ccode\u003entfy\u003c/code\u003e client to get these values.\u003c/p\u003e\n\u003ch3 id=\"mobile-notification\"\u003eMobile notification\u003c/h3\u003e\n\u003cp\u003eWe\u0026rsquo;ve seen a sneak peek of the push notification, but know it\u0026rsquo;s also possible to specify the icon, e.g. \u003ca href=\"https://raw.githubusercontent.com/immich-app/immich/refs/heads/main/mobile/android/fastlane/metadata/android/en-US/images/icon.png\"\u003eImmich Icon\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/memories/app-icon.jpg\" alt=\"android-icon\"\u003e\u003c/p\u003e\n\u003cp\u003eThe most important part is that when we tap on it, it opens the Immich App.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/memories/immich-app.jpg\" alt=\"immich\"\u003e\u003c/p\u003e\n\u003cp\u003eNow it took me some time to figure out the exact command to get this working as intended.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecurl \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e-\u003c/span\u003es \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\\\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e-\u003c/span\u003eH \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Title: Remember this day? 📸\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\\\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e-\u003c/span\u003eH \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Click: immich://open\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\\\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e-\u003c/span\u003eH \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Authorization: Bearer $NTFY_API_TOKEN\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\\\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e-\u003c/span\u003ed \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;You have $MEMORIES memories available 🤩\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\\\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003e{cfg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esettings\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ebase-url}\u003cspan style=\"color:#e6db74\"\u003e/immich\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe key was using \u003ccode\u003eimmich://open\u003c/code\u003e, which leverages their custom URL scheme defined both for \u003ca href=\"https://github.com/immich-app/immich/blob/26714f6bfe70db9b8ec776fa8897c16181eb59b1/mobile/android/app/src/main/AndroidManifest.xml#L97\"\u003eAndroid\u003c/a\u003e and \u003ca href=\"https://github.com/immich-app/immich/blob/a838167f110deaa6c7c7c2361e01287f05219bfa/mobile/ios/Runner/Info.plist#L103\"\u003eiOS\u003c/a\u003e.\u003c/p\u003e\n\u003ch2 id=\"closing-remarks\"\u003eClosing remarks\u003c/h2\u003e\n\u003cp\u003eImmich has been great so far! I hope someone finds this little trick useful to enhance the overall experience.\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '🌈';\"\u003e\n  \u003ch4\u003eI\u0026#39;m Google Photos free!\u003c/h4\u003e\n  \u003cp\u003e\nMy subscription has now come to an end — I previously thought it was end of August, but it turns out it was end of May. I deleted about 300 GBs worth of media and uninstalled the app from my phone for good. It felt scary and liberating at the same time 🐎\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cp\u003eClosing out this post by sharing a special memory from my home country \u0026mdash; \u003ca href=\"https://www.youtube.com/watch?v=UkJBS0Zazmw\"\u003etime flies\u003c/a\u003e 🥹\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/memories/calafate.jpeg\" alt=\"glacier\"\u003e\n\u003cem\u003eSnap from a 2016 ice trekking on Perito Moreno Glacier, Argentina 🇦🇷\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003eBest,\nGabriel.\u003c/p\u003e\n","date":"2026-05-30","dateFormatted":"2026.05.30","excerpt":"One of the most beloved features Immich offers (also Google Photos) is memories. It temporarily transports us back in time to cherish our old precious photos …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/immich-memories/","readingTime":5,"slug":"immich-memories","subtitle":null,"summary":"\u003cp\u003eOne of the most beloved features \u003ca href=\"../immich-photos\"\u003eImmich\u003c/a\u003e offers (also Google Photos) is \u003cem\u003ememories\u003c/em\u003e. It temporarily transports us back in time to cherish our old precious photos and videos once again. However, we need to remember to open the app on a daily basis to do so \u0026mdash; there\u0026rsquo;s no baked in notification system that would remind us.\u003c/p\u003e\n\u003cp\u003eThis is another \u003ca href=\"https://github.com/immich-app/immich/discussions/3981\"\u003eheavily-requested feature\u003c/a\u003e, and if you check out that discussion, you\u0026rsquo;ll find a couple of vibe-coded solutions like \u003ca href=\"https://github.com/james-maschal/Immich-Memories-Notification\"\u003ethis python script\u003c/a\u003e or \u003ca href=\"https://github.com/ismaildakrory/immich-memories-notify\"\u003ethis dockerized application\u003c/a\u003e. None of them felt right to me, though.\u003c/p\u003e","tags":["cloud","immich","nixos","ntfy"],"title":"Immich Memories Notifications","url":"https://gvolpe.com/blog/immich-memories/","wordCount":1031},{"comments":true,"content":"\u003cp\u003eAs someone who loves going for adventures abroad, the \u003ca href=\"https://gvolpe.com/classy-lens\"\u003ememories\u003c/a\u003e I\u0026rsquo;ve captured behind the lens have become extremely valuable to me. I\u0026rsquo;ve learned to appreciate that I can still cherish those memories many years later, regardless of whether the source was a DSLR, a smartphone, or a GoPro. The feelings these precious memories rekindle are priceless; as a tech person, it\u0026rsquo;s only reasonable to seek out a \u0026lsquo;safe haven\u0026rsquo; for them.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/immich/torres-del-paine.jpg\" alt=\"tdp\"\u003e\n\u003cem\u003eSnap from a 2020 mid-summer hike in Torres Del Paine, Chile 🇨🇱\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003eUp until now, that place was Google Photos. As much as that hurt the privacy-freak in me, I have come to accept that for a very long time, there was no real competition. However, the landscape has changed drastically in recent years. Privacy-focused and open-source solutions like \u003ca href=\"https://ente.io/\"\u003eEnte\u003c/a\u003e and \u003ca href=\"https://immich.app/\"\u003eImmich\u003c/a\u003e have entered the space (among \u003ca href=\"https://meichthys.github.io/foss_photo_libraries/\"\u003emany others\u003c/a\u003e), offering viable alternatives for those looking to reclaim their data.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eEnte\u003c/strong\u003e is impressive on paper. They offer \u003cem\u003eend-to-end encryption\u003c/em\u003e and \u003cem\u003ethree copies of your data spread across three different countries\u003c/em\u003e \u0026mdash; including an \u003ca href=\"https://ente.io/help/photos/faq/security-and-privacy#data-storage-location\"\u003eunderground fallout shelter in France\u003c/a\u003e. While it’s possible to self-host Ente (especially with the help of \u003ca href=\"https://search.nixos.org/options?channel=unstable\u0026amp;query=services.ente\"\u003eNixOS\u003c/a\u003e), the service is primarily designed around its excellent paid cloud plans.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eImmich\u003c/strong\u003e, on the other hand, is built from the ground up for self-hosting. While they \u003ca href=\"https://docs.immich.app/FAQ/#are-you-open-to-commercial-partnerships-and-collaborations\"\u003emay be working\u003c/a\u003e on a cloud offering, the current focus is on giving users total control over their deployment environment. The good news is that it\u0026rsquo;s incredibly easy to spin up a production-ready Immich instance via the \u003ca href=\"https://search.nixos.org/options?channel=unstable\u0026amp;query=services.immich\"\u003eNixOS\u003c/a\u003e module.\u003c/p\u003e\n\u003cp\u003eAfter running both for a while, I found myself leaning toward Immich; its UI/UX feels much more polished, and it resembles the Google Photos experience so closely that the transition felt almost seamless.\u003c/p\u003e\n\u003cp\u003eThe ultimate deciding factor, however, was the migration. When you have hundreds of gigabytes on the line, the import process needs to be bulletproof. \u003ca href=\"https://github.com/simulot/immich-go\"\u003eImmich-go\u003c/a\u003e handled my rather big Google Takeout library with ease, whereas my experience with Ente’s migration tool was a bit \u003ca href=\"https://github.com/ente-io/ente/issues/8259\"\u003eunfortunate\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eReady to dive into the full journey of self-hosting Immich? Buckle up! 🛠️\u003c/p\u003e\n\u003ch2 id=\"immich\"\u003eImmich\u003c/h2\u003e\n\u003cp\u003eWe\u0026rsquo;ll begin with the easiest piece of the puzzle: getting Immich up and running behind an Nginx proxy on NixOS.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  host \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;immich-domain.org\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  port \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e2283\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  systemd\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etmpfiles\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erules \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;d /var/cache/immich/matplotlib 0700 immich immich -\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  services \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    immich \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e port;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      accelerationDevices \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      database \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        enableVectors \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        enableVectorChord \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        createDB \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      machine-learning \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        environment\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eMPLCONFIGDIR \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/var/cache/immich/matplotlib\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      mediaLocation \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/var/lib/immich\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      openFirewall \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      redis\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#75715e\"\u003e# settings can be set directly in the UI\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      settings \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enull\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    nginx \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      clientMaxBodySize \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;500M\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      virtualHosts\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ehost\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        forceSSL \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        enableACME \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        locations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            proxyPass \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;http://localhost:\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003etoString port\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            proxyWebsockets \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThat\u0026rsquo;s pretty much all it takes. However, we\u0026rsquo;re still a long way off from reaching a final verdict, as setting up a reliable storage system requires some heavy lifting.\u003c/p\u003e\n\u003ch3 id=\"storage\"\u003eStorage\u003c/h3\u003e\n\u003cp\u003eImmich stores media files in a regular Unix-compatible file system (EXT4, ZFS, APFS, etc.). Ente, on the other hand, uses S3 object storage. This was a huge point in favor of Ente, making it even harder to make a decision.\u003c/p\u003e\n\u003cp\u003eThe main advantage of using S3 object storage is that it\u0026rsquo;s so much cheaper than raw disk space in cloud services. Immich native S3 support is a \u003ca href=\"https://github.com/immich-app/immich/discussions/23745\"\u003eheavily requested feature\u003c/a\u003e, so hopefully it becomes a reality some day.\u003c/p\u003e\n\u003cp\u003eTo circumvent this issue, I decided to go down the rabbit hole and implement the most technically challenging solution: setting up \u003ca href=\"https://github.com/s3ql/s3ql\"\u003eS3QL\u003c/a\u003e as the file system, because why not? 😁\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '📜';\"\u003e\n  \u003ch4\u003eS3QL\u003c/h4\u003e\n  \u003cp\u003e\n\"S3QL is a file system that stores all its data online using storage services like Google Storage, Amazon S3, or OpenStack. It provides a virtual drive of dynamic, infinite capacity that can be accessed from any computer with internet access. S3QL is a full featured UNIX file system that is conceptually indistinguishable from a local file system like ext4. Furthermore, S3QL has additional features like compression encryption, data de-duplication, immutable trees and snapshotting which make it especially suitable for online backup and archival.\"\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cp\u003eS3QL requires our storage provider to support \u0026ldquo;immediate consistency\u0026rdquo; to \u003ca href=\"https://www.rath.org/s3ql-docs/durability.html\"\u003eavoid data loss\u003c/a\u003e, as some providers only offer eventual consistency. However, since all my NixOS virtual machines run on Hetzner and they offer \u003ca href=\"https://docs.hetzner.com/storage/object-storage/overview/\"\u003eS3-compatible object storage\u003c/a\u003e with these guarantees (i.e. they run a \u003ca href=\"https://ceph.io/en/\"\u003eCeph\u003c/a\u003e cluster) at good prices, it was a no-brainer.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/immich/buckets.png\" alt=\"buckets\"\u003e\u003c/p\u003e\n\u003cp\u003eThe main media storage is located in Finland, which falls under the \u003ccode\u003eeu-central\u003c/code\u003e network zone, whereas the replica bucket is located in Germany, which shares the same network zone.\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '🤑';\"\u003e\n  \u003ch4\u003eS3 Object Storage\u003c/h4\u003e\n  \u003cp\u003e\n\"Internal traffic within the same network zone is free of charge. See \u003ca href=\"https://docs.hetzner.com/storage/object-storage/overview#pricing\" target=\"_blank\"\u003ePricing\u003c/a\u003e details.\"\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cp\u003eThis is a \u003cem\u003ecrucial factor\u003c/em\u003e, as it means we can replicate all our data from Finland over to Germany (pretty much) for free!\u003c/p\u003e\n\u003cp\u003eAnother key element is \u003cem\u003eproximity\u003c/em\u003e between the compute instance (Immich server) and the S3 bucket \u0026mdash; i.e. they should be in the same location to reduce latency. Since S3QL is a FUSE-based file system, every I/O request travels from the kernel to user-space and then over the network; even minor latency jitter and routing resets can disrupt the mount.\u003c/p\u003e\n\u003cp\u003eDuring an Immich startup, the application performs thousands of \u003ccode\u003estat()\u003c/code\u003e and \u003ccode\u003eread()\u003c/code\u003e file system calls. If S3QL is simultaneously flushing metadata or processing deletes, even a 100ms lag spike can lead to \u003ccode\u003eNoSuchBucket\u003c/code\u003e errors, causing the mount to panic and crash.\u003c/p\u003e\n\u003cp\u003eFor this reason, both the Immich server and the main S3 bucket are located in the same datacenter in Helsinki.\u003c/p\u003e\n\u003ch2 id=\"s3ql-file-system\"\u003eS3QL file system\u003c/h2\u003e\n\u003cp\u003eManaging the S3QL file system is possibly the most challenging part. It took me multiple attempts to ensure a reproducible approach that can survive restarts and can be replicated on a brand new machine.\u003c/p\u003e\n\u003cp\u003eAfter much trial and error, I came up with a \u003ca href=\"https://github.com/gvolpe/s3ql.nix\"\u003ecustom NixOS module\u003c/a\u003e that sets up a few \u003ca href=\"https://systemd.io/\"\u003esystemd\u003c/a\u003e services that are responsible for managing the intricacies of mounting such a file system, so that it can be easily used.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  services\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003es3ql \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    settings \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      bucket \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        url \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;s3c://hel1.your-objectstorage.com/source-bucket/s3ql\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        name \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;hzfi\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      cache \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        directory \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/mnt/s3ql-cache/.s3ql\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        size \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e30000000\u003c/span\u003e; \u003cspan style=\"color:#75715e\"\u003e# 30 GBs\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      mountpoint \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/mnt/s3ql\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      skipMkfs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eBehind the scenes, a few things are happening. First of all, it defines a \u003cem\u003e.mount\u003c/em\u003e unit that encodes information about a file system mount point controlled and supervised by systemd.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ config\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  cfg \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eservices\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003es3ql;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  systemd\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etmpfiles\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erules \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;d \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ecfg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esettings\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emountpoint\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e 0755 root root -\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  systemd\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emounts \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      name \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e cfg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esettings\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emountUnitName;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      requires \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;s3ql-mount.service\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      after \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;s3ql-mount.service\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      where \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e cfg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esettings\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emountpoint;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      what \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e cfg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esettings\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ebucket\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eurl;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;none\u0026#34;\u003c/span\u003e; \u003cspan style=\"color:#75715e\"\u003e# the mounting is done by the service\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      wantedBy \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;multi-user.target\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWhich sets up the requirements for the next three systemd services: \u003ccode\u003es3ql-auth\u003c/code\u003e, \u003ccode\u003es3ql-fs\u003c/code\u003e and \u003ccode\u003es3ql-mount\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ config\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  cfg \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eservices\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003es3ql;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  authService \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;s3ql-auth.service\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  fsService \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;s3ql-fs.service\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  systemd\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eservices \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    s3ql-auth \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;s3ql authfile setup\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      serviceConfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        Type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;oneshot\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        RemainAfterExit \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        User \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;root\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        Group \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;root\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ExecStart \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003eauthScript\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/run\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    s3ql-fs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;s3ql mkfs/fsck manager: run only once if uninitialized\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      requires \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;network-online.target\u0026#34;\u003c/span\u003e authService ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      after \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;network-online.target\u0026#34;\u003c/span\u003e authService ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      unitConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDefaultDependencies \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      stopIfChanged \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      serviceConfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        Type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;oneshot\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        RemainAfterExit \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        User \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;root\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        Group \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;root\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ExecCondition \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003eexecConditionScript\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/run\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ExecStart \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003efsckScript\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/run\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        TimeoutStartSec \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        Restart \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;on-failure\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        RestartSec \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e60\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    s3ql-mount \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;s3ql mount file system\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      before \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;multi-user.target\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      wantedBy \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;multi-user.target\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      requires \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;network-online.target\u0026#34;\u003c/span\u003e fsService ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      after \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;network-online.target\u0026#34;\u003c/span\u003e fsService \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;network.target\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      unitConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDefaultDependencies \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      stopIfChanged \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      serviceConfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        Type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;simple\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        RemainAfterExit \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e#  prevent systemd from killing sub-threads/wrappers to that s3ql can perform a clean unmount\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        KillMode \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;process\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        KillSignal \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;SIGINT\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        SuccessExitStatus \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;0 1 SIGINT\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ExecCondition \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003eexecConditionScript\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/run\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ExecStart \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003emountScript\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/run\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ExecStartPost \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003emountWaitScript\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/run\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ExecStop \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;-\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003epkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003es3ql\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/umount.s3ql \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ecfg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esettings\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emountpoint\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        MountFlags \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;shared\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        User \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;root\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        Group \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;root\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# give it time to download metadata from S3 when remounting\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        TimeoutStartSec \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# give it time to upload metadata to S3 before systemd kills it\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        TimeoutStopSec \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;5min\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# ensures systemd can track the FUSE process\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        NotifyAccess \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;all\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe authentication service sets up a file with the required secrets for these commands (details are omitted for brevity).\u003c/p\u003e\n\u003cp\u003eThe \u003ccode\u003es3ql-fs\u003c/code\u003e service is responsible for setting up the file system via the \u003ccode\u003emkfs.s3ql\u003c/code\u003e and \u003ccode\u003efsck.s3ql\u003c/code\u003e commands. On the other hand, the \u003ccode\u003es3ql-mount\u003c/code\u003e service is the most delicate one: it mounts the file system via \u003ccode\u003emount.s3ql\u003c/code\u003e and ensures it\u0026rsquo;s gracefully unmounted when the systemd unit is stopped (e.g. on redeployments).\u003c/p\u003e\n\u003cp\u003eTo begin with, the file system needs to be created when a new S3 bucket is being initialized.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eecho -n \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$S3_PASSPHRASE\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e | mkfs.s3ql \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ecfg.settings.bucket.url\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e --authfile \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003eauthfile\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eOnce that\u0026rsquo;s done, we should avoid this command as that can fail in spectacular ways. The NixOS module has a fail-safe mechanism that deals with this issue, which guarantees it is executed exactly once.\u003c/p\u003e\n\u003cp\u003eHowever, if the file system is ever unmounted ungracefully (e.g. power shutdown), it may be necessary to re-run the \u003ccode\u003es3ql-fs\u003c/code\u003e service (deleting the safety flag under \u003ccode\u003e/var/lib\u003c/code\u003e), which runs the \u003ccode\u003efsck.s3ql\u003c/code\u003e command.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003efsck.s3ql --authfile \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003eauthfile\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e --batch --force-remote --log syslog \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ecfg.settings.bucket.url\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt forces the remote metadata onto this file system \u0026mdash; i.e. it makes the remote S3 bucket the source of truth. Once the \u003ccode\u003es3ql-fs\u003c/code\u003e service has run, it\u0026rsquo;s time for the next service in line, as declared by the \u003ccode\u003erequires\u003c/code\u003e and \u003ccode\u003eafter\u003c/code\u003e sections.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emkdir -p \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ecfg.settings.mountpoint\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emount.s3ql \u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ae81ff\"\u003e\u003c/span\u003e  --allow-other \u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ae81ff\"\u003e\u003c/span\u003e  --authfile \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003eauthfile\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ae81ff\"\u003e\u003c/span\u003e  --cachedir \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ecfg.settings.cache.directory\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ae81ff\"\u003e\u003c/span\u003e  --cachesize \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003etoString cfg.settings.cache.size\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ae81ff\"\u003e\u003c/span\u003e  --fg \u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ae81ff\"\u003e\u003c/span\u003e  --threads \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003etoString cfg.settings.threads\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ae81ff\"\u003e\u003c/span\u003e  --log syslog \u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ae81ff\"\u003e\u003c/span\u003e  \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ecfg.settings.bucket.url\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ecfg.settings.mountpoint\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt\u0026rsquo;s only two lines: create the mount point directory (optional) and run \u003ccode\u003emount.s3ql\u003c/code\u003e. The \u003ccode\u003e--allow-other\u003c/code\u003e flag is critical here, as we want to grant read and write access to the Immich service.\u003c/p\u003e\n\u003cp\u003eSetting both the cache size and directory is highly recommended too, as that can fill up the disk pretty quickly. In my case, I went with a dedicated external 50 GBs volume after I ran out of disk space during the first week 😆\u003c/p\u003e\n\u003cp\u003eAnd that\u0026rsquo;s a wrap! It certainly wasn\u0026rsquo;t a piece of cake, though now the NixOS module hides a lot of the complexity of managing an S3QL file system. Finding the right configuration for the systemd unit took a lot of trial and error attempts to better understand the process. For instance, the \u003ccode\u003eKillMode = \u0026quot;process\u0026quot;;\u003c/code\u003e setting ensures that sub-threads are not killed by systemd when the unit is stopped, as one of those threads is responsible for uploading metadata to the remote S3 bucket. If this is interrupted, we end up with a dirty file system that would refuse to be mounted unless we run \u003ccode\u003efsck.s3ql\u003c/code\u003e once again, which can take ages when the bucket size is hundreds of GBs big.\u003c/p\u003e\n\u003cp\u003eNow with the S3QL file system in place, we can update the Immich service\u0026rsquo;s configuration as follows:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  cfg \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eservices\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eimmich;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  s3qlService \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;s3ql-mount.service\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  systemd\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eservices \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkIf cfg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenable {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    immich-machine-learning \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      requires \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ s3qlService ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      after \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ s3qlService ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    immich-server \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      requires \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ s3qlService ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      after \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ s3qlService ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  services \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    immich\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emediaLocation \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003econfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eservices\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003es3ql\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esettings\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emountpoint\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/immich\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe systemd units are modified so that they are not started until the S3QL file system is successfully mounted. Ultimately, the media location needs to be writable by the Immich service, which is ensured by the \u003ca href=\"https://github.com/NixOS/nixpkgs/blob/3e2499d5539c16d0d173ba53552a4ff8547f4539/nixos/modules/services/web-apps/immich.nix#L462\"\u003eNixOS module\u003c/a\u003e.\u003c/p\u003e\n\u003ch2 id=\"backups\"\u003eBackups\u003c/h2\u003e\n\u003cp\u003eSo far so good. We have our Immich service successfully running, persisting all our photos and videos on a remote S3 bucket without it even knowing thanks to S3QL. Furthermore, our data is de-duplicated and compressed without quality loss, further reducing the storage capacity requirements.\u003c/p\u003e\n\u003cp\u003eEverything\u0026rsquo;s great, until something goes wrong\u0026hellip; Think about file system corruption, S3 bucket deletion, or any issues with our cloud provider \u0026mdash; whatever happens, we must be prepared for a disaster recovery scenario.\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '💡';\"\u003e\n  \u003ch4\u003eImportant\u003c/h4\u003e\n  \u003cp\u003e\nBackups are a vital part of any data system and \u003cb\u003eshall not be overlooked\u003c/b\u003e.\u003cbr/\u003e\nA \u003ca href=\"https://www.backblaze.com/blog/the-3-2-1-backup-strategy\" target=\"_blank\"\u003e3-2-1 backup strategy\u003c/a\u003e is recommended to protect our data.\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cp\u003eWe will be following the guidelines and adhere to the 3-2-1 backup rule, which consists of:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e3\u003c/strong\u003e copies of our data.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e2\u003c/strong\u003e storage devices.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e1\u003c/strong\u003e copy off-site.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eIt was introduced in the late 2000s, but it\u0026rsquo;s still very relevant, even though a lot has changed since then. As previously mentioned, we\u0026rsquo;ll have two S3 buckets, which makes two copies on two different storage devices. The third copy will be on a bare-metal server located in Poland with two dedicated drives.\u003c/p\u003e\n\u003cp\u003eThe Immich service runs on a virtual machine hosted in Finland, so technically with the replica S3 bucket located in Germany and the bare-metal server running in Poland, we\u0026rsquo;re more than safe not with one but two copies off-site. Though, we\u0026rsquo;re not out of the woods just yet.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eWhat do we need to back-up exactly to be able to restore all data on a brand new machine?\u003c/strong\u003e The \u003ca href=\"https://docs.immich.app/administration/backup-and-restore/\"\u003eBackup and Restore\u003c/a\u003e documentation page outlines what needs to be backed up:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eThe media location (S3QL file system): all our photos and videos live here.\u003c/li\u003e\n\u003cli\u003ePostgreSQL database: it holds metadata and user information.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eBoth are necessary for a successful restoration process that would allow the system to function properly.\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '⛓️‍💥';\"\u003e\n  \u003ch4\u003eWarning\u003c/h4\u003e\n  \u003cp\u003e\nWhen backing these up it's possible for them to get out of sync, potentially resulting in broken assets after you restore.\nThe best way of dealing with this is to stop the immich-server container while you take a backup. If nothing is changing then the backup will always be in sync.\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cp\u003eIn order to do so, we\u0026rsquo;re going to automate this task via a systemd service that sets the stage for the backup jobs.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ config\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  cfg \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eservices\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eimmich;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  config \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkIf cfg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenable {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    systemd \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      services \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        custom-immich-start-trigger \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Trigger to start the Immich services\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          serviceConfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            Type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;oneshot\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            ExecStart \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003epkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esystemd\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/systemctl start immich-server immich-machine-learning\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        custom-immich-stop-trigger \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Trigger to stop all Immich services\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          after \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;immich-server.service\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          serviceConfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            Type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;oneshot\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            ExecStart \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003epkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esystemd\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/systemctl stop immich-*\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      timers \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        custom-immich-stop-trigger \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Stop Immich services to allow backups daily at midnight\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          timerConfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            OnCalendar \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;*-*-* 00:00:00\u0026#34;\u003c/span\u003e; \u003cspan style=\"color:#75715e\"\u003e# daily at midnight\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            Unit \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;custom-immich-stop-trigger.service\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          wantedBy \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;timers.target\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        custom-immich-start-trigger \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Restart Immich services daily at 3 AM\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          timerConfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            OnCalendar \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;*-*-* 0:03:00\u0026#34;\u003c/span\u003e; \u003cspan style=\"color:#75715e\"\u003e# daily at 3 am\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            Unit \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;custom-immich-start-trigger.service\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          wantedBy \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;timers.target\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt is important that we set a clear time-window for all the different backup jobs we schedule over night. The current configuration allows 3 hours to run all of them, which is rather generous.\u003c/p\u003e\n\u003ch3 id=\"s3-bucket-replication\"\u003eS3 bucket replication\u003c/h3\u003e\n\u003cp\u003eTo interact with our remote S3 buckets, we will rely on \u003ca href=\"https://docs.hetzner.com/storage/object-storage/getting-started/using-s3-api-tools\"\u003eS3-compatible tools\u003c/a\u003e, which include the popular \u003ca href=\"https://github.com/minio/mc\"\u003eMinIO client\u003c/a\u003e. It is one of the easiest to operate and it supports \u003cem\u003eServer-Side Bucket Replication\u003c/em\u003e via the \u003ccode\u003emc replicate\u003c/code\u003e command.\u003c/p\u003e\n\u003cp\u003eUnfortunately, we can\u0026rsquo;t use this command because Hetzner Object Storage \u003ca href=\"https://docs.hetzner.com/storage/object-storage/supported-actions\"\u003edoes not support replication\u003c/a\u003e. Instead, we\u0026rsquo;re going to run \u003ccode\u003emc mirror\u003c/code\u003e on a daily basis, which is basically \u003cem\u003eClient-Side Bucket Replication\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eWe could also run \u003ccode\u003emc mirror --watch\u003c/code\u003e continuously, but I find this unnecessary for this use case. It requires more resources and backing up our media daily is more than enough. YMMV ¯_(ツ)_/¯\u003c/p\u003e\n\u003cp\u003eResults can be inspected in the journal; we get a summary of the total data transferred at the end of every run.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-console\" data-lang=\"console\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ journalctl --no-hostname -u s3-replica.service -f\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec 13 00:30:01 systemd[1116]: Starting S3 replica service...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec 13 00:30:01 s3-replica[1146]: \u0026gt;\u0026gt; Creating S3 bucket replica from 🇫🇮 to 🇩🇪\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec 13 00:30:02 s3-replica[1147]: `s3-fi-immich/source-bucket/s3qls3ql_data_10180` -\u0026gt; `s3-de-backup/replica-bucket/s3qls3ql_data_10180`\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e......\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec 13 00:37:55 s3-replica[1147]: ┌───────────┬─────────────┬──────────┬──────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec 13 00:37:55 s3-replica[1147]: │ Total     │ Transferred │ Duration │ Speed        │\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec 13 00:37:55 s3-replica[1147]: │ 48.30 GiB │ 48.30 GiB   │ 05m40s   │ 145.29 MiB/s │\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec 13 00:37:55 s3-replica[1147]: └───────────┴─────────────┴──────────┴──────────────┘\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec 13 00:37:55 s3-replica[1146]: \u0026gt;\u0026gt; Done creating S3 bucket replica\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec 13 00:37:55 systemd[1116]: Finished S3 replica service.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec 13 00:37:55 systemd[1116]: s3-replica.service: Consumed 4min 25.343s CPU time, 85.8M memory peak.\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe daily task is modeled as a systemd service that\u0026rsquo;s triggered via a systemd timer. It runs at half-past midnight, right after the other backup jobs, because it\u0026rsquo;s the most time-consuming task that needs to account for failures and retries.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ config\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  cfg \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eservices\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003es3ql;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  config \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkIf cfg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenable {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    systemd \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      services \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        s3-aliases \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;S3 aliases manager\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          serviceConfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            Type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;oneshot\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            ExecStart \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003es3-aliases\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/run\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            RemainAfterExit \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            User \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;admin\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          wantedBy \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;default.target\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        s3-replica \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;S3 replica (mirror)\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          serviceConfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            Type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;oneshot\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            ExecStart \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003es3-replica\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/run\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            TimeoutStartSec \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            Restart \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;on-failure\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            RestartSec \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e60\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            User \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;root\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          after \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;s3-aliases.service\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          requires \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;s3-aliases.service\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          wantedBy \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;default.target\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      timers\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003es3-replica \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;S3 replica service timer\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        wantedBy \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;timers.target\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        timerConfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          OnCalendar \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;*-*-* 0:00:30\u0026#34;\u003c/span\u003e; \u003cspan style=\"color:#75715e\"\u003e# daily at half-past midnight\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          Persistent \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe \u003ccode\u003es3-aliases\u003c/code\u003e service ensures the MinIO client aliases are set up with the corresponding credentials (only done once, much like the creation of the file system), so that the \u003ccode\u003es3-replica\u003c/code\u003e service calls are authenticated.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emc alias set hzfi \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$BUCKET_URL\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$S3_ACCESS_KEY\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$S3_SECRET_KEY\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emc alias set hzde \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$BUCKET_URL\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$S3_ACCESS_KEY\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$S3_SECRET_KEY\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eBefore replication kicks in, we force S3QL to write its current memory state and metadata to the remote S3 bucket. S3QL would take the current state of that local SQLite database, compress it into a small bundle, and push it to the bucket as a few objects (usually named \u003ccode\u003es3ql_metadata_bak_N\u003c/code\u003e). This ensures we get an accurate replica.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003es3qlctrl flushcache \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ecfg.settings.mountpoint\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003es3qlctrl backup-metadata \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ecfg.settings.mountpoint\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAt last, the exact command we run to replicate the data over to Germany is the following one.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emc mirror --overwrite --remove --retry --summary hzfi/source-bucket hzde/replica-bucket\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe \u003ccode\u003e--overwrite\u003c/code\u003e and \u003ccode\u003e--remove\u003c/code\u003e flags should be used with caution. The former tells \u003ccode\u003emc\u003c/code\u003e that the source bucket is the source of truth and any discrepancies should be overridden. Similarly, the other flag will result in the removal of files that are present in the replica bucket but not at the source. I found that these may be necessary after restarts, as \u003ccode\u003efsck.s3ql\u003c/code\u003e may create file system conflicts between the source and the replica, but shouldn\u0026rsquo;t be required otherwise.\u003c/p\u003e\n\u003ch3 id=\"postgresql-backups\"\u003ePostgreSQL backups\u003c/h3\u003e\n\u003cp\u003eThus far, we have two copies of our media distributed across two S3 buckets. However, the media itself is not enough for a full restoration without the metadata that\u0026rsquo;s persisted in a PostgreSQL database. So, we\u0026rsquo;ll set up a daily backup job that creates a database dump via \u003ccode\u003epg_dumpall\u003c/code\u003e and drops it on a directory accessible to other jobs.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ config\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  cfg \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ebackups\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epostgresql;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  config \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkIf (cfg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenable \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eservices\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epostgresql\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenable) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    systemd \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      tmpfiles\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erules \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;d \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ecfg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edirectory\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e 0750 postgres postgres - -\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      services\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epostgresql-backup \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;PostgreSQL Full Database Backup\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        requires \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;postgresql.service\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        after \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;postgresql.service\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        serviceConfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          ExecStart \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ebackupScript\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/run\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          User \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;postgres\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          Type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;oneshot\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          TimeoutStartSec \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;10min\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      timers\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epostgresql-backup \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Timer for PostgreSQL Backup\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        wantedBy \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;timers.target\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        timerConfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          OnCalendar \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e cfg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eschedule;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          Unit \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;postgresql-backup.service\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis is the first backup job that runs right after Immich services are stopped at midnight \u0026mdash; it currently takes about 7 seconds, but it may grow over time. Additionally, the script takes care of pruning backups older than 7 days.\u003c/p\u003e\n\u003cp\u003eBesides this backup, we also have the one managed by Immich \u0026mdash; set up from the administration settings.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/immich/postgres.png\" alt=\"postgres\"\u003e\u003c/p\u003e\n\u003cp\u003eIt\u0026rsquo;s usually good practice to enable it, but to also make our own backups if we can afford the disk space.\u003c/p\u003e\n\u003ch3 id=\"borg-backups\"\u003eBorg backups\u003c/h3\u003e\n\u003cp\u003eDatabase dumps are stored in the same machine that\u0026rsquo;s running the Immich service (single device, single location), so we\u0026rsquo;ll need to copy them over to other devices to follow the 3-2-1 backup rule. There are multiple tools to handle this task: \u003ccode\u003ersync\u003c/code\u003e, \u003ccode\u003erclone\u003c/code\u003e, \u003ccode\u003ekopia\u003c/code\u003e, \u003ccode\u003erestic\u003c/code\u003e, you name it. We\u0026rsquo;re going to use \u003ca href=\"https://www.borgbackup.org/\"\u003eBorg\u003c/a\u003e, which is one of my favorites tools out there.\u003c/p\u003e\n\u003cp\u003eWe\u0026rsquo;ll set up two different systemd services that run right after the Postgres backup job: one for the Postgres database dump; another for the Immich media stored under the S3QL file system.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  compression \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;auto,zstd\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  encryption \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    mode \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;repokey-blake2\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    passCommand \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;cat run/agenix/borg-passphrase\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  environment \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    BORG_RSH \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ssh -i /etc/ssh/ssh_host_ed25519_key\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  extraCreateArgs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;--stats\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  failOnWarnings \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  prune \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    keep \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      within \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;1d\u0026#34;\u003c/span\u003e; \u003cspan style=\"color:#75715e\"\u003e# Keep all archives from the last day\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      daily \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e7\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      weekly \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e4\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      monthly \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e-1\u003c/span\u003e; \u003cspan style=\"color:#75715e\"\u003e# Keep at least one archive for each month\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  startAt \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;*-*-* 0:00:05\u0026#34;\u003c/span\u003e; \u003cspan style=\"color:#75715e\"\u003e# daily 5 mins after midnight\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  services\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eborgbackup\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ejobs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    postgres \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e compression encryption environment extraCreateArgs failOnWarnings prune startAt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      paths \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/var/lib/postgresql/backups\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      repo \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ssh://admin@cubi/data/backups/immich/postgres\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    immich-s3-media \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e compression encryption environment extraCreateArgs failOnWarnings prune startAt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      paths \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/mnt/s3ql/immich\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      repo \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ssh://admin@cubi/data/backups/immich/s3-media\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn fairness, there is an additional Postgres job to comply with the 3-2-1 rule; the only difference is the repo.\u003c/p\u003e\n\u003cp\u003eBorg allows us to set many different settings, with compression and encryption being among the most important ones. We\u0026rsquo;re setting them to run daily, sharing the same passphrase, as they are both Immich data after all.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003eadmin@metropolis:~\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e$ systemctl status borgbackup-job-postgres.timer\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e● borgbackup-job-postgres.timer - BorgBackup job postgres timer\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e     Loaded: loaded \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e/etc/systemd/system/borgbackup-job-postgres.timer; enabled; preset: ignored\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e     Active: active \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ewaiting\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e since Tue 2025-12-23 17:57:38 UTC; 18h ago\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Invocation: cb5f6e93f0b34a9bb37edb72d0d38bbe\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    Trigger: Thu 2025-12-25 00:00:05 UTC; 11h left\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e   Triggers: ● borgbackup-job-postgres.service\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec \u003cspan style=\"color:#ae81ff\"\u003e23\u003c/span\u003e 17:57:38 metropolis systemd\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e1\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e: Started BorgBackup job postgres timer.\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThese two Borg Backup jobs ensure the data is copied from the cloud over to the bare-metal server (hostname: \u003ccode\u003ecubi\u003c/code\u003e) via SSH. I still find this fascinating, because it didn\u0026rsquo;t require any complicated firewall rules or VPNs to get this working. Setting up \u003ca href=\"https://tailscale.com/\"\u003eTailscale\u003c/a\u003e on both machines was enough to establish a secure communication channel.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/immich/tailscale.png\" alt=\"tailscale\"\u003e\u003c/p\u003e\n\u003cp\u003eI use Tailscale for other purposes as well and I can\u0026rsquo;t recommend it enough!\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: 'ℹ️';\"\u003e\n  \u003ch4\u003eInformation\u003c/h4\u003e\n  \u003cp\u003e\nThe standard networking MTU (Maximum Transmission Unit) is 1500, but usually the Tailscale interface sets this value to 1280. If S3QL traffic is being routed over the Tailscale mesh to reach the remote S3 bucket, the overhead of the WireGuard encapsulation could cause packet fragmentation if the S3QL blocks are too large.\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cp\u003eThis hasn\u0026rsquo;t been a problem for me so far, but if that ever becomes one, a solution could be enabling Tailscale only before running backups run and disable it afterwards.\u003c/p\u003e\n\u003cp\u003eWe can inspect the service logs, where we\u0026rsquo;ll find some cool stats about the latest backup run.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003eadmin@metropolis:~\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e$ journalctl -u borgbackup-job-postgres.service --no-hostname\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec \u003cspan style=\"color:#ae81ff\"\u003e23\u003c/span\u003e 00:01:00 systemd\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e1\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e: Started BorgBackup job postgres.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec \u003cspan style=\"color:#ae81ff\"\u003e23\u003c/span\u003e 00:01:10 : -----------------------------------------------------------------------\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec \u003cspan style=\"color:#ae81ff\"\u003e23\u003c/span\u003e 00:01:10 : Repository: ssh://admin@cubi/data/backups/immich/postgres\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec \u003cspan style=\"color:#ae81ff\"\u003e23\u003c/span\u003e 00:01:10 : Archive name: metropolis-postgres-2025-12-23T00:01:00.failed\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec \u003cspan style=\"color:#ae81ff\"\u003e23\u003c/span\u003e 00:01:10 : Archive fingerprint: 3c96afcc0f4a4a49b2a7762c3303b7a23499ef8e8fd4ffe...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec \u003cspan style=\"color:#ae81ff\"\u003e23\u003c/span\u003e 00:01:10 : Time \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003estart\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e: Tue, 2025-12-23 00:01:04\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec \u003cspan style=\"color:#ae81ff\"\u003e23\u003c/span\u003e 00:01:10 : Time \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eend\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e:   Tue, 2025-12-23 00:01:10\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec \u003cspan style=\"color:#ae81ff\"\u003e23\u003c/span\u003e 00:01:10 : Duration: 6.09 seconds\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec \u003cspan style=\"color:#ae81ff\"\u003e23\u003c/span\u003e 00:01:10 : Number of files: \u003cspan style=\"color:#ae81ff\"\u003e8\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec \u003cspan style=\"color:#ae81ff\"\u003e23\u003c/span\u003e 00:01:10 : Utilization of max. archive size: 0%\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec \u003cspan style=\"color:#ae81ff\"\u003e23\u003c/span\u003e 00:01:10 : -----------------------------------------------------------------------\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec \u003cspan style=\"color:#ae81ff\"\u003e23\u003c/span\u003e 00:01:10 :                   Original size    Compressed size    Deduplicated size\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec \u003cspan style=\"color:#ae81ff\"\u003e23\u003c/span\u003e 00:01:10 : This archive:           2.81 GB            1.07 GB             26.13 MB\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec \u003cspan style=\"color:#ae81ff\"\u003e23\u003c/span\u003e 00:01:10 : All archives:           8.92 GB            3.37 GB            317.73 MB\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec \u003cspan style=\"color:#ae81ff\"\u003e23\u003c/span\u003e 00:01:10 :                   Unique chunks       Total chunks\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec \u003cspan style=\"color:#ae81ff\"\u003e23\u003c/span\u003e 00:01:10 : Chunk index:                \u003cspan style=\"color:#ae81ff\"\u003e362\u003c/span\u003e               \u003cspan style=\"color:#ae81ff\"\u003e3485\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec \u003cspan style=\"color:#ae81ff\"\u003e23\u003c/span\u003e 00:01:10 : -----------------------------------------------------------------------\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec \u003cspan style=\"color:#ae81ff\"\u003e23\u003c/span\u003e 00:01:17 systemd\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e1\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e: borgbackup-job-postgres.service: Deactivated successfully.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDec \u003cspan style=\"color:#ae81ff\"\u003e23\u003c/span\u003e 00:01:17 systemd\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e1\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e: borgbackup-job-postgres.service: Consumed 8.487s CPU time...\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eOr get information about all the backups in the remote machine via \u003ccode\u003eborg info \u0026lt;backup_directory\u0026gt;\u003c/code\u003e, e.g.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003eadmin@cubi:~\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e$ borg info /data/backups/immich/s3-media/\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eEnter passphrase \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e key /data/backups/immich/s3-media:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eRepository ID: 2b8f28abd83cca956e0bf816f9e2ada7beb0a3299d305e8c0a7536b12212fb2c\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eLocation: /data/backups/immich/s3-media\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eEncrypted: Yes \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003erepokey BLAKE2b\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCache: /home/admin/.cache/borg/2b8f28abd83cca956e0bf816f9e2ada7beb0a3299d305e8c0a7536b12212fb2c\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSecurity dir: /home/admin/.config/borg/security/2b8f28abd83cca956e0bf816f9e2ada7beb0a3299d305e8c0a7536b12212fb2c\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e------------------------------------------------------------------------------\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                       Original size      Compressed size    Deduplicated size\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAll archives:                1.47 TB              1.44 TB            290.85 GB\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                       Unique chunks         Total chunks\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eChunk index:                  \u003cspan style=\"color:#ae81ff\"\u003e282667\u003c/span\u003e              \u003cspan style=\"color:#ae81ff\"\u003e1462772\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eFurthermore, we can list all the backups made so far via \u003ccode\u003eborg list \u0026lt;backup_directory\u0026gt;\u003c/code\u003e, e.g.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003eadmin@cubi:~\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e$ borg list /data/backups/immich/postgres/\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eEnter passphrase \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e key /data/backups/immich/postgres:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emetropolis-postgres-2025-12-25T00:00:05 Thu, 2025-12-25 00:00:09 \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e6b8b217c7de989fffb9f74d906cc22e10ae86549444babe3ad71d2a0c6527b19\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emetropolis-postgres-2025-12-26T00:00:05 Fri, 2025-12-26 00:00:10 \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e9db373920bbf52979cdf87c2ec6dab0742dbc429d99884ccd4b63b1671b5bc7a\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emetropolis-postgres-2025-12-27T00:00:05 Sat, 2025-12-27 00:00:08 \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e813ab922e5eb4f871d53174c179a8413abf26927508c5c60e55f710e599348d9\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emetropolis-postgres-2025-12-28T00:00:07 Sun, 2025-12-28 00:00:10 \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003eeec9ad20cea37916db47f3d89782b135d45947518463c02ec1d743077a68c98c\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emetropolis-postgres-2025-12-29T00:00:07 Mon, 2025-12-29 00:00:11 \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003eb0501a933329191e60034aec10758c269230763495dd7bfbdb2a4589479c59dc\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emetropolis-postgres-2025-12-30T00:00:05 Tue, 2025-12-30 00:00:08 \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003ef48f4dd94ab683885494f0573c6ca9aabde5f4693f2782872874f6cbb95efe40\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emetropolis-postgres-2025-12-31T00:00:07 Wed, 2025-12-31 00:00:10 \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003ed4c9e4b107d2afd8fff9a9ac604b32e06e5ca6c0e82a70e4c3dadcc3ed2d31c5\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emetropolis-postgres-2026-01-01T00:00:07 Thu, 2026-01-01 00:00:10 \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e85364a08beb327edf646b3131eeeb57854a462c6f06d2ac0c9040037edc802ac\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAny of these backups can be restored in different ways via the \u003ccode\u003eborg\u003c/code\u003e client: \u003ccode\u003eextract\u003c/code\u003e, \u003ccode\u003emount\u003c/code\u003e or \u003ccode\u003eexport-tar\u003c/code\u003e. In the \u003ca href=\"#disaster-recovery\"\u003eDisaster recovery\u003c/a\u003e section further down, we\u0026rsquo;re going to explore the former.\u003c/p\u003e\n\u003ch3 id=\"systemd-notifications\"\u003eSystemd notifications\u003c/h3\u003e\n\u003cp\u003eSince most services run under systemd, robust monitoring is essential. While a full observability stack (e.g. Prometheus with \u003ccode\u003esystemd-exporter\u003c/code\u003e and Grafana) provides deep metrics, it is often resource-hungry.\u003c/p\u003e\n\u003cp\u003eTo maintain a minimal resource footprint, we will opt for a lightweight alternative: a \u003ca href=\"https://core.telegram.org/bots\"\u003eTelegram Bot\u003c/a\u003e integrated directly with systemd to provide real-time failure alerts via \u003ccode\u003eOnFailure\u003c/code\u003e triggers.\u003c/p\u003e\n\u003cp\u003eThe picture displayed below is a failure notification example for the \u003ccode\u003eimmich-machine-learning\u003c/code\u003e unit.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/immich/telegram-bot.png\" alt=\"telegram-bot\"\u003e\u003c/p\u003e\n\u003cp\u003eThe script that interacts with Telegram\u0026rsquo;s API and results in the message shown above is quite minimalistic.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  hostname \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enetworking\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehostName;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  telegram-script \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e { tpe \u003cspan style=\"color:#f92672\"\u003e?\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;system\u0026#34;\u003c/span\u003e }: pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ewriteShellScriptBin \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;run\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    TELEGRAM_CHAT_ID=$(cat /run/agenix/telegram-chat-id | tr -d \u0026#39;\\n\u0026#39;)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    TELEGRAM_TOKEN=$(cat /run/agenix/telegram-token | tr -d \u0026#39;\\n\u0026#39;)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    UNIT_NAME=$1\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    LOGS=$(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003epkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esystemd\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/journalctl --\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003etpe\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e -u \u0026#34;$UNIT_NAME\u0026#34; -n 15 --no-hostname --no-pager)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    MESSAGE=\u0026#34;⚠️ Service Failure @ \\`\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ehostname\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\\` ☠️\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    Service: $UNIT_NAME\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    Recent Logs:\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    \\`\\`\\`\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    $LOGS\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    \\`\\`\\`\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003epkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecurl\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/curl -s -X POST \u0026#34;https://api.telegram.org/bot$TELEGRAM_TOKEN/sendMessage\u0026#34; \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e      -d chat_id=\u0026#34;$TELEGRAM_CHAT_ID\u0026#34; \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e      --data-urlencode \u0026#34;text=$MESSAGE\u0026#34; \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e      -d parse_mode=\u0026#34;Markdown\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e  \u0026#39;\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAll specified systemd units will have set up this notification script on failure via a custom NixOS module.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ config\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  cfg \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esystemd\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emonitoring;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  systemNotifications \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;notify-telegram-system-fail@\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  userNotifications \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;notify-telegram-user-fail@\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  options\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esystemd\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emonitoring \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkEnableOption \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Enable systemd service failure notifications via the Telegram API\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    user\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eservices \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkOption {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etypes\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elistOf lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etypes\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estr;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      default \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;List of user-level services to enable failure notifications for.\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    system\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eservices \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkOption {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etypes\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elistOf lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etypes\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estr;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      default \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;List of system-level services to enable failure notifications for.\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  config \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkIf cfg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenable {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    systemd\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eservices \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egenAttrs cfg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esystem\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eservices\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      (name: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        unitConfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          OnFailure \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkDefault \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003esystemNotifications\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%n\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          DefaultDependencies \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          After \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;network-online.target\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          StartLimitIntervalSec \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkDefault \u003cspan style=\"color:#ae81ff\"\u003e300\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          StartLimitBurst \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkDefault \u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      }) \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003esystemNotifications\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;System-level Telegram notification for service %i\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        serviceConfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          Type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;oneshot\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          ExecStart \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003etelegram-script {}\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/run %i\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    systemd\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003euser\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eservices \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egenAttrs cfg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003euser\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eservices\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      (name: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        unitConfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          OnFailure \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkDefault \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003euserNotifications\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%n\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          DefaultDependencies \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          After \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;network-online.target\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          StartLimitIntervalSec \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkDefault \u003cspan style=\"color:#ae81ff\"\u003e300\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          StartLimitBurst \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkDefault \u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      }) \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003euserNotifications\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;User-level Telegram notification for service %i\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        serviceConfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          Type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;oneshot\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          ExecStart \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003etelegram-script { tpe \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;user\u0026#34;\u003c/span\u003e; }\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/run %i\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eInitially, I had a simpler approach where every single systemd service was configured to receive notifications on failure, but I later realized there are a lot of systemd units declared by NixOS for which I wouldn\u0026rsquo;t want to receive notifications. So, while this is a bit more manual, I find it more granular and easier to maintain.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  systemd\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emonitoring \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    disk\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ethreshold \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e80\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    user\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eservices \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    system\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eservices \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;borgbackup-job-immich-s3-media\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;borgbackup-job-postgres\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;immich-machine-learning\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;immich-server\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;postgresql\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;postgresql-backup\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;redis-immich\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;s3-replica\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;s3ql-fs\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;s3ql-mount\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eLast but not least, I also set up another service that \u003cem\u003emonitors disk usage\u003c/em\u003e via the \u003ccode\u003edf\u003c/code\u003e command and sends a warning over Telegram when it\u0026rsquo;s over the specified threshold (default to 80%).\u003c/p\u003e\n\u003ch2 id=\"google-photos-migration\"\u003eGoogle Photos migration\u003c/h2\u003e\n\u003cp\u003eI mentioned at the start of this blogpost that I\u0026rsquo;ve found \u003ca href=\"https://github.com/simulot/immich-go\"\u003eimmich-go\u003c/a\u003e to be very reliable to import Google Photos data, so I would like to share more details about my experience with the process.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/immich/immich-go.png\" alt=\"immich-go\"\u003e\u003c/p\u003e\n\u003cp\u003eThe command I ran to migrate \u003cstrong\u003e384 GBs\u003c/strong\u003e of compressed data is more or less the suggested one from the \u003ca href=\"https://github.com/simulot/immich-go/blob/main/docs/best-practices.md#google-photos-migration\"\u003edocs\u003c/a\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  packages\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003esystem\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    google-migration \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ewriteShellScriptBin \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;takeout-migration\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e      \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003epkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eimmich-go\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/immich-go upload from-google-photos \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e        --server $IMMICH_SERVER \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e        --api-key $IMMICH_API_KEY \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e        --concurrent-tasks 8 \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e        --client-timeout 60m \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e        --pause-immich-jobs=true \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e        --on-errors continue \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e        --session-tag \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e        --include-unmatched \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e        $1\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    \u0026#39;\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eBesides the two environment variables (the API key needs to be created in the Immich Web UI), we need to specify the directory where we stored the \u003ccode\u003ezip\u003c/code\u003e files we\u0026rsquo;ve got from \u003ca href=\"https://takeout.google.com\"\u003eGoogle Takeout\u003c/a\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix run .#google-migration -- ~/Downloads/google-photos/takeout-*.zip\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHowever, before I went through it, I did a few test runs with smaller datasets to get a better understanding of the process. I learned that if you\u0026rsquo;re migrating multiple albums, the import process will result in multiple errors on the first run, but don\u0026rsquo;t panic just yet! It all works out just fine by re-running the process; \u003cem\u003ethe operations are idempotent\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eAlso, before running the final migration, I wanted to make sure everything was set up correctly: from the S3QL file system to the services and the many backup jobs. So far, I\u0026rsquo;m pretty happy with the results!\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/immich/immich-stats.png\" alt=\"immich-stats\"\u003e\u003c/p\u003e\n\u003cp\u003eFor some reason the UI shows 256 GBs in one place, and 293 GBs in another; not sure what\u0026rsquo;s the reason for that discrepancy. In any case, the S3QL file system provides this information too.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-console\" data-lang=\"console\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ sudo s3qlstat /mnt/s3ql\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDirectory entries:    264278\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eInodes:               264280\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eData objects:         440142\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eTotal data size:      293 GiB\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAfter de-duplication: 293 GiB (99.97% of total)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAfter compression:    287 GiB (97.76% of total, 97.79% of de-duplicated)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDatabase size:        88.2 MiB (uncompressed)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCache size:           46.3 MiB, 257 entries\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCache size (dirty):   0 bytes, 0 entries\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eQueued object removals: 0\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe initial 384 GBs have been significantly reduced through two layers of optimization: initial de-duplication during the import process, followed by the native compression and de-duplication provided by the S3QL filesystem.\u003c/p\u003e\n\u003ch2 id=\"disaster-recovery\"\u003eDisaster recovery\u003c/h2\u003e\n\u003cp\u003eIt\u0026rsquo;s highly advisable to periodically exercise a full disaster recovery validation on a fresh install, which will keep us honest and ensure our process is rock solid. It\u0026rsquo;s going to cost us a few extra bucks $$ to run these tests, but it\u0026rsquo;s all worth it.\u003c/p\u003e\n\u003cp\u003eWe\u0026rsquo;ll start by creating a new server on Hetzner with the default values (Ubuntu image is fine), followed by creating a new S3 bucket, which is usually included in the \u003ca href=\"https://docs.hetzner.com/storage/object-storage/overview/#pricing\"\u003emonthly base price\u003c/a\u003e, so it shouldn\u0026rsquo;t incur extra charges.\u003c/p\u003e\n\u003cp\u003eOnce the machine is on (remember to set up the SSH key), we\u0026rsquo;ll use \u003ca href=\"https://github.com/nix-community/nixos-anywhere/\"\u003enixos-anywhere\u003c/a\u003e to install NixOS on it.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-console\" data-lang=\"console\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enix run github:nix-community/nixos-anywhere -- --flake .#mini-metropolis root@\u0026lt;IP_ADDRESS\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis assumes our NixOS configuration is properly set up to run on a new machine. It\u0026rsquo;s going to differ from the production configuration, so we\u0026rsquo;ll set up a \u003ccode\u003emini-metropolis\u003c/code\u003e host for this purpose.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  imports \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#e6db74\"\u003e../../metropolis/services/immich.nix\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  services\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etailscale\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  systemd\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emonitoring\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  services\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003es3ql \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    settings \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      bucket \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        url \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;s3c://hel1.your-objectstorage.com/test-immich/s3ql\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        name \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;hzfi\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      cache \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        directory \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/root/.s3ql\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        size \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e20000000\u003c/span\u003e; \u003cspan style=\"color:#75715e\"\u003e# 20 GBs\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      mountpoint \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/mnt/s3ql\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      skipMkfs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e; \u003cspan style=\"color:#75715e\"\u003e# only fsck is needed on restoration\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      threads \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e16\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe main purpose of the new install is to exercise all the different restoration methods:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eMount the S3QL file system by relying on the remote S3 bucket data.\n\u003cul\u003e\n\u003cli\u003eWe can use \u003ccode\u003emc mirror\u003c/code\u003e to replicate it to our test bucket.\u003c/li\u003e\n\u003cli\u003eRestore the Postgres backup from Borg and run it on our database instance.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003ePopulate the S3QL file system with the Borg media backup.\n\u003cul\u003e\n\u003cli\u003eWe rely on a new S3 bucket that can be later mounted on the new machine.\u003c/li\u003e\n\u003cli\u003eRestore the Postgres backup from Borg and run it on our database instance.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eSince our main goal is to test the restoration processes, we can disable all backup jobs from our NixOS configuration. Furthermore, the initial configuration we deploy needs to have all services disabled as well.\u003c/p\u003e\n\u003ch3 id=\"restoring-from-s3-replica\"\u003eRestoring from S3 replica\u003c/h3\u003e\n\u003cp\u003eWe\u0026rsquo;re going to begin with the (arguably) easier and recommended method, which requires some of the same steps we take for running backups, starting with stopping the Immich services.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003eadmin@metropolis:~\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e$ sudo systemctl stop immich-*\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eFollowed by running the same commands the \u003ccode\u003es3-replica\u003c/code\u003e service runs, except we modify the target bucket.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003eadmin@metropolis:~\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e$ sudo s3qlctrl flushcache /mnt/s3ql\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003eadmin@metropolis:~\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e$ sudo s3qlctrl backup-metadata /mnt/s3ql\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003eadmin@metropolis:~\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e$ mc mirror --overwrite --retry --summary hzfi/source hzfi/test-bucket\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e┌────────────┬─────────────┬──────────┬──────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│ Total      │ Transferred │ Duration │ Speed        │\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│ 287.69 GiB │ 287.69 GiB  │ 18m24s   │ 266.82 MiB/s │\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e└────────────┴─────────────┴──────────┴──────────────┘\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eNext step is to get the PostgreSQL backup made by Borg over to the new machine, which is possible to achieve in different ways \u0026mdash; We\u0026rsquo;re going to leverage the Tailscale mesh and do it directly in \u003ccode\u003emini-metropolis\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-console\" data-lang=\"console\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[admin@mini:~]$ export BORG_REPO\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ssh://admin@cubi/data/backups/immich/postgres\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[admin@mini:~]$ export BORG_RSH\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ssh -i /etc/ssh/ssh_host_ed25519_key\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[admin@mini:~]$ borg extract --progress ::metropolis-immich-postgres-2025-12-28T00:00:05\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[admin@mini:~]$ mkdir tmp \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e cd tmp\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[admin@mini:~/tmp]$ borg extract \u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ae81ff\"\u003e\u003c/span\u003e   /data/backups/immich/postgres/::metropolis-postgres-2025-12-28T00:00:05\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe backup name can be found via \u003ccode\u003eborg list\u003c/code\u003e in the machine where all of them are stored (Cubi in this case). Once we have the backup data ready to be restored, we can proceed with enabling all of our services \u0026mdash; especially S3QL, which will take care of recovering the file system from the remote S3 bucket.\u003c/p\u003e\n\u003cp\u003eBefore moving on, we must ensure the S3QL file system has been mounted correctly.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-console\" data-lang=\"console\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[admin@mini:~]$ df -h\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eFilesystem                                          Size  Used Avail Use% Mounted on\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edevtmpfs                                            388M     0  388M   0% /dev\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etmpfs                                               3.8G     0  3.8G   0% /dev/shm\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etmpfs                                               1.9G  3.3M  1.9G   1% /run\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/dev/sda3                                            75G  3.4G   67G   5% /\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etmpfs                                               3.8G  1.1M  3.8G   1% /run/wrappers\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/dev/sda2                                           511M   42M  470M   9% /boot\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etmpfs                                               776M  4.0K  776M   1% /run/user/1000\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003es3c://nbg1.your-objectstorage.com/test-immich/s3ql  1.0T  286G  739G  28% /mnt/s3ql\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eOnce we\u0026rsquo;re through this, we\u0026rsquo;re ready to restore the PostgreSQL backup in our database.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-console\" data-lang=\"console\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo -u postgres psql -c \u0026#34;DROP DATABASE IF EXISTS immich;\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo -u postgres psql -c \u0026#34;CREATE DATABASE immich;\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo -u postgres psql -d immich -c \u0026#34;DROP SCHEMA public CASCADE; CREATE SCHEMA public;\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo -u postgres psql -d immich -f /path/to/\u0026lt;full_backup_date\u0026gt;.sql\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo -u postgres psql -d immich -c \u0026#34;ALTER SCHEMA vectors OWNER TO immich;\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo -u postgres psql -d immich -c \u0026#34;ALTER SCHEMA public OWNER TO immich;\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWhile not strictly necessary, we restart the PostgreSQL service to ensure all changes take effect.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-console\" data-lang=\"console\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[admin@mini:~]$ sudo systemctl restart postgresql\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHaving just recovered our S3QL file system from the S3 bucket, Immich will likely still fail because of the missing \u003ccode\u003e.immich\u003c/code\u003e files (as the metadata is cleared via \u003ccode\u003efsck.s3ql --force-remote\u003c/code\u003e).\u003c/p\u003e\n\u003cp\u003eTo fix it, we recreate those \u0026ldquo;sentinel\u0026rdquo; files before restarting the Immich server.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e f in library upload thumbs encoded-video profile backups; \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  sudo mkdir -p /mnt/s3ql/immich/$f\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  sudo touch /mnt/s3ql/immich/$f/.immich\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edone\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo chown -R immich:immich /mnt/s3ql/immich\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAt last, we restart the Immich service and everything should be good!\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-console\" data-lang=\"console\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[admin@mini:~]$ sudo systemctl restart immich-server\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe ultimate check should be done in the Immich Web UI. Thumbnails should be rendered and we should be able to see photos and play videos. If all that works, we\u0026rsquo;re done here! 🥳\u003c/p\u003e\n\u003cp\u003eReflecting on this exercise, I would highly recommend not skipping it; only by going through it, I discovered multiple flaws in my setup. We still have to test the second method, though, which involves Borg backups directly.\u003c/p\u003e\n\u003ch3 id=\"restoring-from-borg-backups\"\u003eRestoring from Borg backups\u003c/h3\u003e\n\u003cp\u003eBesides the S3 replica bucket, we have a third copy of our media data backed up by Borg backup jobs in the Cubi machine. In order to restore the latest backup, we have two main options here:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eExtract it in the Mini host connected to Cubi via SSH.\u003c/li\u003e\n\u003cli\u003eExtract it directly in the Cubi host.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eIn both cases, we\u0026rsquo;ll need to create the S3QL file system pointing to a new S3 bucket and mount it, i.e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003eroot@cubi:~\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e$ mkfs.s3ql s3c://hel1.your-objectstorage.com/immich4/s3ql --authfile s3ql-auth\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003eroot@cubi:~\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e$ mkdir -p /mnt/s3ql\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003eroot@cubi:~\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e$ mount.s3ql \u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ae81ff\"\u003e\u003c/span\u003e   --allow-other \u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ae81ff\"\u003e\u003c/span\u003e   --cachedir /root/s3ql-recovery \u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ae81ff\"\u003e\u003c/span\u003e   --cachesize \u003cspan style=\"color:#ae81ff\"\u003e20000000\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ae81ff\"\u003e\u003c/span\u003e   --authfile s3ql-auth \u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ae81ff\"\u003e\u003c/span\u003e   s3c://hel1.your-objectstorage.com/immich4/s3ql /mnt/s3ql\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eUsing \u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e upload threads.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAutodetected \u003cspan style=\"color:#ae81ff\"\u003e524226\u003c/span\u003e file descriptors available \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e cache entries\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDownloading metadata...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDownloaded 1/1 metadata blocks \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e100%\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eMounting s3c://hel1.your-objectstorage.com/immich4/s3ql at /mnt/s3ql...\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eBoth approaches can take a long time to restore a large file system, so the S3 replica bucket will always be the first recommended approach. Having said that, we\u0026rsquo;ll exercise our second option and do it directly in the Cubi host, which consists of extracting the latest backup made by Borg directly in the S3QL mount point.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003eroot@cubi:~\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e$ cd /mnt/s3ql\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003eroot@cubi:/mnt/s3ql\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e$ borg extract --progress \u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ae81ff\"\u003e\u003c/span\u003e   /data/backups/immich/s3-media/::metropolis-immich-s3-media-2025-12-27T00:00:05\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eEnter passphrase \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e key /data/backups/immich/s3-media:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  0.4% Extracting: mnt/s3ql/immich/encoded-video/7730770f-aaa3-4002-9e2d-8eb3a283f278/4e/df/4edf459d-a469-47ea-a99e-1ade31e3dfba.mp4\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eDespite the many hours it may take \u0026mdash; it took me about 20 hours for an S3QL file system of ~293 GBs \u0026mdash; it\u0026rsquo;s the second most effective approach for the following reasons:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eEfficiency\u003c/strong\u003e: we only extract the data from the latest archive, not the entire history of the Borg repository.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eIntegrity\u003c/strong\u003e: Borg handles the decryption and decompression on the fly as it writes to the S3QL file system.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eNo Local Space Needed(*)\u003c/strong\u003e: the data goes straight from the network into the S3QL cache and then to the cloud.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003e(*)\u003c/strong\u003e We do need local storage for the S3QL cache size, which needs to be set accordingly (20 GBs in our example). Once the Borg media backup has completed the extraction and we have verified the S3 bucket has the expected size, we\u0026rsquo;ll unmount the file system from Cubi.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003eadmin@cubi:~\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e$ sudo umount.s3ql /mnt/s3ql\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003eadmin@cubi:~\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e$ sudo fusermount -uz /mnt/s3ql\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWe can now update the S3QL settings for the Mini machine to use the new S3 bucket and redeploy.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eservices \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  s3ql\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esettings \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    bucket\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eurl \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;s3c://hel1.your-objectstorage.com/immich4/s3ql\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    mountpoint \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/mnt/s3ql\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eBeware that when mounting an S3 bucket that has been populated from the Borg backup, we\u0026rsquo;ll more likely to find a nested directory data structure such as \u003ccode\u003e/mnt/s3ql/mnt/s3ql/immich\u003c/code\u003e, so that needs to be addressed first.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003eroot@mini:~\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#75715e\"\u003e# cd /mnt/s3ql\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003eroot@mini:~\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#75715e\"\u003e# mv mnt/s3ql/* .\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003eroot@mini:~\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#75715e\"\u003e# rm -rf mnt\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eOnce we ensure the file system is correctly mounted, we stop the Immich services one more time.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003eroot@mini:~\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#75715e\"\u003e# systemctl stop immich-*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eUltimately, we must also restore the PostgreSQL backup corresponding to the same date (see the previous section for instructions), restart the Immich server and we are done! 🛸\u003c/p\u003e\n\u003ch2 id=\"final-thoughts\"\u003eFinal thoughts\u003c/h2\u003e\n\u003cp\u003eWell that\u0026rsquo;s been quite the journey! Eventually, the technical challenge was too compelling to resist 🤓\u003c/p\u003e\n\u003cp\u003eImmich keeps growing on me, so I purchased a \u003ca href=\"https://buy.immich.app/\"\u003eserver license\u003c/a\u003e to support its ongoing development. The Android app is equally impressive — it’s robust and offers the closest experience to the Google Photos UI/UX currently available.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/immich/immich-supporter.png\" alt=\"supporter\"\u003e\u003c/p\u003e\n\u003cp\u003eI have been daily-driving it for a few weeks now, and every day I\u0026rsquo;m more confident it\u0026rsquo;s here to stay.\u003c/p\u003e\n\u003cp\u003eFull disclosure: \u003cem\u003eI haven\u0026rsquo;t ditched Google Photos yet\u003c/em\u003e, but it is inevitable.\u003c/p\u003e\n\u003cp\u003eMy annual subscription plan is valid until August 2026, so that gives me plenty of time to stress-test Immich Photos on S3QL and validate the system\u0026rsquo;s long-term reliability before the final cut off.\u003c/p\u003e\n\u003cp\u003eClosing out this post by sharing one more memory, peace out ✌️\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/immich/alakul.jpg\" alt=\"alakul\"\u003e\n\u003cem\u003eSnap from a 2025 hike in Ala Kul (Ала-көл), Kyrgyzstan 🇰🇬 \u0026mdash; up at 3,920 meters\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003eBest,\nGabriel.\u003c/p\u003e\n","date":"2026-01-01","dateFormatted":"2026.01.01","excerpt":"As someone who loves going for adventures abroad, the memories I\u0026rsquo;ve captured behind the lens have become extremely valuable to me. I\u0026rsquo;ve learned to …","featured":true,"mood":null,"permalink":"https://gvolpe.com/blog/immich-photos/","readingTime":31,"slug":"immich-photos","subtitle":null,"summary":"\u003cp\u003eAs someone who loves going for adventures abroad, the \u003ca href=\"https://gvolpe.com/classy-lens\"\u003ememories\u003c/a\u003e I\u0026rsquo;ve captured behind the lens have become extremely valuable to me. I\u0026rsquo;ve learned to appreciate that I can still cherish those memories many years later, regardless of whether the source was a DSLR, a smartphone, or a GoPro. The feelings these precious memories rekindle are priceless; as a tech person, it\u0026rsquo;s only reasonable to seek out a \u0026lsquo;safe haven\u0026rsquo; for them.\u003c/p\u003e","tags":["borg","cloud","immich","nixos","s3ql"],"title":"Immich Photos on S3 object storage","url":"https://gvolpe.com/blog/immich-photos/","wordCount":6569},{"comments":true,"content":"\u003cp\u003e\u003ca href=\"../xmonad-polybar-nixos\"\u003eXMonad\u003c/a\u003e has been my main driver for years, and I still think it\u0026rsquo;s the best X11 window manager ever. However, the X11 windowing system is quite dated, having originated about 40 years ago. Its number of limitations, among other things, has led to the creation of the Wayland protocol.\u003c/p\u003e\n\u003cp\u003eThis is a modern protocol that allows compositors to operate in a more efficient and lightweight manner. Since its inception in 2008, it has gained popularity to the point where prominent display managers such as GNOME and KDE have adopted it \u0026mdash; \u003ca href=\"https://mutter.gnome.org/\"\u003eMutter\u003c/a\u003e and \u003ca href=\"https://invent.kde.org/plasma/kwin\"\u003eKWin\u003c/a\u003e, respectively.\u003c/p\u003e\n\u003cp\u003eWhen it comes to modern tiling window managers, \u003ca href=\"https://swaywm.org/\"\u003eSway\u003c/a\u003e has led the way by implementing the Wayland protocol, while taking inspiration from \u003ca href=\"https://i3wm.org/\"\u003ei3\u003c/a\u003e for X11.\u003c/p\u003e\n\u003cp\u003ePersonally, it wasn\u0026rsquo;t until \u003ca href=\"https://hypr.land/\"\u003eHyprland\u003c/a\u003e came around that I felt adventurous enough to consider Wayland a serious time investment. While I didn\u0026rsquo;t specifically write about it in this blog, you can see it mentioned in a \u003ca href=\"../home-manager-dotfiles-management\"\u003erecent blogpost\u003c/a\u003e. I had a good ride with it, but ultimately, I wasn\u0026rsquo;t entirely satisfied with the overall experience.\u003c/p\u003e\n\u003cp\u003eSo it was only a matter of time until the inevitable happened\u0026hellip; I had a very interesting project on my radar for a while, and finally the universes aligned to allow me the time to give it a try \u0026mdash; and holy cows, there\u0026rsquo;s no way back.\u003c/p\u003e\n\u003ch2 id=\"a-new-tiling-wayland-compositor\"\u003eA new tiling Wayland compositor\u003c/h2\u003e\n\u003cp\u003e\u003cimg src=\"../../images/niri/niri-logo.svg\" alt=\"logo\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/YaLTeR/niri\"\u003eNiri\u003c/a\u003e is written in Rust, and it is simply described as a \u0026ldquo;scrollable-tiling Wayland compositor\u0026rdquo; which is heavily inspired by \u003ca href=\"httpspaperwm/PaperWM\"\u003ePaperWM\u003c/a\u003e. By default, windows are spawned next to each other on an infinite horizontal space! 🤯\u003c/p\u003e\n\u003cp\u003eWhile it may not sound very appealing, I can tell you from experience that it\u0026rsquo;s liberating. It would be counter-productive, however, if we limited ourselves to a single scrollable horizontal tile. The good news is that Niri also supports workspaces, and multiple monitors are first-class citizens.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/niri/workspace.png\" alt=\"workspace\"\u003e\u003c/p\u003e\n\u003cp\u003eMoreover, \u003ca href=\"https://yalter.github.io/niri/Screencasting.html\"\u003escreencasting\u003c/a\u003e is supported via \u003ca href=\"https://gitlab.gnome.org/GNOME/xdg-desktop-portal-gnome\"\u003exdg-desktop-portal-gnome\u003c/a\u003e, which makes sense, given the author is a GNOME contributor.\u003c/p\u003e\n\u003cp\u003eAnd there are a lot of other features such as layers, tabs, gestures, and the ever important \u003ca href=\"https://yalter.github.io/niri/Xwayland.html\"\u003eXWayland\u003c/a\u003e support via \u003ca href=\"https://github.com/Supreeeme/xwayland-satellite\"\u003exwayland-satellite\u003c/a\u003e.\u003c/p\u003e\n\u003ch3 id=\"scratchpads\"\u003eScratchpads\u003c/h3\u003e\n\u003cp\u003eScratchpads are a crucial feature of any tiling window manager for my workflows. I mainly use it for Spotify and a File Manager, but it\u0026rsquo;s good to know we have the option of starting any application as a scratchpad.\u003c/p\u003e\n\u003cp\u003eUnfortunately, this is not a native Niri feature. However, it recently gained support for \u003ca href=\"https://yalter.github.io/niri/Floating-Windows.html\"\u003efloating windows\u003c/a\u003e, which combined with \u003ca href=\"https://yalter.github.io/niri/IPC.html\"\u003eIPC socket communication\u003c/a\u003e with the running Niri instance, gives us what we need to build scratchpad support.\u003c/p\u003e\n\u003cp\u003eAnd that\u0026rsquo;s exactly what I\u0026rsquo;ve done: \u003ca href=\"https://github.com/gvolpe/niri-scratchpad\"\u003egvolpe/niri-scratchpad\u003c/a\u003e. All we need is a rather simple script, so users are encouraged to play around with it and customize it.\u003c/p\u003e\n\u003ch2 id=\"showcase\"\u003eShowcase\u003c/h2\u003e\n\u003cp\u003eWriting more about it would be boring. Let me show you what it feels like to use it!\u003c/p\u003e\n\u003cdiv style=\"position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;\"\u003e\n      \u003ciframe allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen\" loading=\"lazy\" referrerpolicy=\"strict-origin-when-cross-origin\" src=\"../../images/niri/showcase.mp4?autoplay=0\u0026amp;controls=1\u0026amp;end=0\u0026amp;loop=0\u0026amp;mute=0\u0026amp;start=0\" style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;\" title=\"Local video\"\u003e\u003c/iframe\u003e\n    \u003c/div\u003e\n\n\u003cp\u003eIt is also possible to view it on \u003ca href=\"https://youtu.be/Mo4nO6mI9DU\"\u003eYouTube\u003c/a\u003e if that\u0026rsquo;s preferred.\u003c/p\u003e\n\u003cp\u003eThese are only some of the features that Niri offers, but there\u0026rsquo;s so much more I didn\u0026rsquo;t cover in this video such as resizing windows, moving windows between workspaces, etc. The overview is perhaps my favorite of them all!\u003c/p\u003e\n\u003cp\u003eYou can also check out \u003ca href=\"https://www.youtube.com/watch?v=DeYx2exm04M\"\u003ethis excellent review video\u003c/a\u003e by Brodie Robertson.\u003c/p\u003e\n\u003ch2 id=\"closing-remarks\"\u003eClosing remarks\u003c/h2\u003e\n\u003cp\u003eIt\u0026rsquo;s only been two weeks using Niri and I\u0026rsquo;m extremely happy with it. It feels fast, snappy and beautiful. I love it when a tiling window manager is lightweight and efficient, enabling the user to become one with it \u0026mdash; increasing productivity.\u003c/p\u003e\n\u003cp\u003eThat\u0026rsquo;s all I have. If you like the project, go give it a star. You can also sponsor its main developer \u003ca href=\"https://github.com/sponsors/YaLTeR\"\u003eIvan Molodetskikh\u003c/a\u003e, he would definitely appreciate it.\u003c/p\u003e\n\u003cp\u003eBest,\nGabriel.\u003c/p\u003e\n","date":"2025-09-14","dateFormatted":"2025.09.14","excerpt":"XMonad has been my main driver for years, and I still think it\u0026rsquo;s the best X11 window manager ever. However, the X11 windowing system is quite dated, …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/niri/","readingTime":3,"slug":"niri","subtitle":null,"summary":"\u003cp\u003e\u003ca href=\"../xmonad-polybar-nixos\"\u003eXMonad\u003c/a\u003e has been my main driver for years, and I still think it\u0026rsquo;s the best X11 window manager ever. However, the X11 windowing system is quite dated, having originated about 40 years ago. Its number of limitations, among other things, has led to the creation of the Wayland protocol.\u003c/p\u003e\n\u003cp\u003eThis is a modern protocol that allows compositors to operate in a more efficient and lightweight manner. Since its inception in 2008, it has gained popularity to the point where prominent display managers such as GNOME and KDE have adopted it \u0026mdash; \u003ca href=\"https://mutter.gnome.org/\"\u003eMutter\u003c/a\u003e and \u003ca href=\"https://invent.kde.org/plasma/kwin\"\u003eKWin\u003c/a\u003e, respectively.\u003c/p\u003e","tags":["niri","nixos","wm","windowmanager","wayland"],"title":"The perfect tiling window manager","url":"https://gvolpe.com/blog/niri/","wordCount":605},{"comments":true,"content":"\u003cp\u003eI have been following the \u003ca href=\"https://www.unison-lang.org/\"\u003eUnison programming language\u003c/a\u003e for as long as I can remember, given that \u003ca href=\"https://www.unison-lang.org/unison-computing/\"\u003eits authors\u003c/a\u003e are very well-known in the Scala ecosystem. Though, it took me a good few years to finally learn it by immersing myself into their amazing documentation and tutorials.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eTL;DR: I love it more and more every single day!\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eThe language itself is easy to learn for anyone coming from a functional programming background. Even their implementation of \u003ca href=\"https://arxiv.org/pdf/1611.09259\"\u003ealgebraic effects\u003c/a\u003e \u0026mdash; called \u003ca href=\"https://www.unison-lang.org/docs/fundamentals/abilities/\"\u003eabilities\u003c/a\u003e \u0026mdash; are quite straightforward. What sets the language apart from others is their interactive approach to writing code.\u003c/p\u003e\n\u003cp\u003eUnison stores all code on a local database (under \u003ccode\u003e~/.unison\u003c/code\u003e by default), which can be shared with others on their platform: \u003ca href=\"https://share.unison-lang.org\"\u003eUnison Share\u003c/a\u003e. Thus, our code doesn\u0026rsquo;t live on files tracked on a git repository: instead, they need to be fetched from the database to be edited. This is incredibly different from what we, programmers, are used to. It took me a while to get comfortable with this process \u0026mdash; forgetting about my vim fuzzy-finder in between \u0026mdash; and I would even say it was more challenging than learning the language itself, but it clicked\u0026hellip; eventually.\u003c/p\u003e\n\u003cp\u003eI will spare you the details, however, as I find it quite hard to convey with words how it feels to experience it. So I would leave that to you to explore and find out for yourself. Instead, I would like to share more about my experience using the features that excite me the most in the Unison multiverse.\u003c/p\u003e\n\u003ch2 id=\"learn-by-doing\"\u003eLearn by doing\u003c/h2\u003e\n\u003cp\u003eA while ago now (~6 years ago), I wrote an \u003ca href=\"https://github.com/gvolpe/exchange-rates\"\u003eexchange rates\u003c/a\u003e application in Haskell for fun that exposes an API endpoint to query specific currencies. Internally, it relies on a third-party API that\u0026rsquo;s rate-limited, so the results are cached in Redis with a configurable expiration time.\u003c/p\u003e\n\u003cp\u003eI thought this could be good candidate for a rewrite in the Unison language, leveraging their \u003ca href=\"https://www.unison.cloud/\"\u003ecloud infrastructure\u003c/a\u003e and native \u003ca href=\"https://www.unison.cloud/docs/storage-solutions/\"\u003estorage solutions\u003c/a\u003e, so I could get acquainted with the language and its radical approach to writing code. After all, learning by doing is always more fun and rewarding.\u003c/p\u003e\n\u003cp\u003eIt all started by creating a new project via UCM: Unison Codebase Manager.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/unison/ucm.png\" alt=\"ucm\"\u003e\u003c/p\u003e\n\u003cp\u003eAnd with me being a nix-head, of course I ended up creating a \u003ca href=\"https://github.com/gvolpe/unison-dev\"\u003edevelopment shell\u003c/a\u003e that provides the \u003ccode\u003eucm\u003c/code\u003e program when I enter the directory where I write Unison code. All made possible thanks to \u003ca href=\"https://github.com/ceedubs/unison-nix/\"\u003eunison-nix\u003c/a\u003e.\u003c/p\u003e\n\u003ch2 id=\"the-project\"\u003eThe Project\u003c/h2\u003e\n\u003cp\u003eThe requirements for this Foreign Exchange Rates API are rather simple. All we need is a single GET endpoint that returns the exchange rate between two supported currencies.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ curl https://gvolpe.unison-services.cloud/s/forex/api/rates?from\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eUSD\u0026amp;to\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eEUR  | jq\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;rate\u0026#34;\u003c/span\u003e: 0.8885\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAnd a healthcheck endpoint for good measure.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ curl https://gvolpe.unison-services.cloud/s/forex/api/healthcheck  | jq\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;db\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;6fcf7ba3-4a3b-440e-a225-cc3d8d1917e8 (currencies)\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe response body is irrelevant here. We mostly care about the HTTP response status being 200 OK.\u003c/p\u003e\n\u003cp\u003eInternally, we will use a \u003ca href=\"https://www.exchangerate-api.com/docs/standard-requests\"\u003ethird-party API\u003c/a\u003e that\u0026rsquo;s rate-limited to 1500 requests per month, and it requires an API key used as an authorization bearer token to authenticate HTTP requests.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/unison/ext-api.png\" alt=\"ext-api\"\u003e\u003c/p\u003e\n\u003cp\u003eThe challenge will be to cache as many results as possible for a period of 24 hours (see \u003ca href=\"https://www.exchangerate-api.com/terms\"\u003eData Caching Policy\u003c/a\u003e) to avoid stale data, but also to optimize the number of external HTTP requests we need to make, while keeping away from getting rate-limited.\u003c/p\u003e\n\u003cp\u003eIn the next few sections, we\u0026rsquo;ll cover the main components of the \u003ca href=\"https://share.unison-lang.org/@gvolpe/forex\"\u003eforex\u003c/a\u003e system. Feel free to follow along by navigating directly through the source code for a more interactive approach. However, before we do, I would like to share my personal wish list from my first pain points I experienced with the language.\u003c/p\u003e\n\u003ch3 id=\"wish-list\"\u003eWish list\u003c/h3\u003e\n\u003cp\u003eUnison doesn\u0026rsquo;t support bounded polymorphism as languages like Haskell do via typeclasses, but different options \u003ca href=\"https://www.unison-lang.org/docs/usage-topics/general-faqs/#does-unison-have-haskell-style-type-classes\"\u003eare being considered\u003c/a\u003e. Having a functional programming background, I really miss a few things you can do with typeclasses. See, for example, the \u003ca href=\"https://share.unison-lang.org/@gvolpe/forex/code/main/latest/types/domain/Currency\"\u003eCurrency\u003c/a\u003e definition.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e domain\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAED\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAFN\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eALL\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAMD\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eANG\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAOA\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eARS\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAUD\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt consists of an enumeration of 166 values (trimmed here for brevity). However, in our API endpoint we parse the currencies as \u003ccode\u003eText\u003c/code\u003e, and need to be able to convert them to a valid \u003ccode\u003eCurrency\u003c/code\u003e. To do this, I had to write a \u003ca href=\"https://share.unison-lang.org/@gvolpe/forex/code/main/latest/terms/domain/Currency/fromText\"\u003every lengthy and tedious string pattern-matching\u003c/a\u003e for every single currency \u0026mdash; I wouldn\u0026rsquo;t have bothered with this if vim macros weren\u0026rsquo;t a thing 😅\u003c/p\u003e\n\u003cp\u003eConversely, in the Haskell version, this is easily achieved via typeclass derivation and enum support.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edata\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAED\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAFN\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eALL\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAMD\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eANG\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAOA\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eARS\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAUD\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAWG\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAZN\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003ederiving\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eEnum\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eGeneric\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eEq\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eOrd\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eShow\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003einstance\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eFromJSON\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003einstance\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eToJSON\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ecurrencies\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e [\u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ecurrencies\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e enumFrom \u003cspan style=\"color:#66d9ef\"\u003eAED\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eparseCurrency\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eText\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eMaybe\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eparseCurrency\u003c/span\u003e t \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e s \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pack \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e show \u003cspan style=\"color:#f92672\"\u003e\u0026lt;$\u0026gt;\u003c/span\u003e currencies\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      i \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e elemIndex (toUpper t) s\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e  (currencies \u003cspan style=\"color:#f92672\"\u003e!!\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e\u0026lt;$\u0026gt;\u003c/span\u003e i\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eTypeclasses here are only a means to an end, so I would love to have the same kind of power while writing Unison code, whether that\u0026rsquo;s done via typeclasses or something from the future.\u003c/p\u003e\n\u003cp\u003eDid I miss something obvious in Unison APIs or documentation that would simplify this?\u003c/p\u003e\n\u003cp\u003eAnother thing I miss compared to the usual Git repo approach is having a CI/CD job running regression and smoke tests. With Unison having its own platform for sharing code, deployments are left to running manual commands in \u003ccode\u003eucm\u003c/code\u003e (like \u003ccode\u003erun deploy\u003c/code\u003e) instead of having an automated job taking care of it. I\u0026rsquo;m sure this will be supported at some point, but until then, it\u0026rsquo;ll remain on my wish list.\u003c/p\u003e\n\u003cp\u003eNow with this out of the way, let\u0026rsquo;s move on with the exciting bits of writing my first Unison project!\u003c/p\u003e\n\u003ch3 id=\"http-routes\"\u003eHTTP routes\u003c/h3\u003e\n\u003cp\u003eIt all starts by defining the API endpoints that are combined via \u003ccode\u003e\u0026lt;|\u0026gt;\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight unison-lang\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eapi\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eroutes \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eHttpRequest\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e{\u003cspan style=\"color:#66d9ef\"\u003eException\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eHttp\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eLog\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eAsk\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAppCtx\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eForexResponse\u003c/span\u003e} \u003cspan style=\"color:#66d9ef\"\u003eHttpResponse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eapi\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eroutes \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  use \u003cspan style=\"color:#66d9ef\"\u003eRoute\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;|\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eRoute\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erun (getExchangeRate \u003cspan style=\"color:#f92672\"\u003e\u0026lt;|\u0026gt;\u003c/span\u003e healthcheck)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt essentially returns a \u003ccode\u003eHttpRequest -\u0026gt; HttpResponse\u003c/code\u003e function where the abilities expressed in \u003ccode\u003e{}\u003c/code\u003e need to be handled. The \u003ccode\u003e{Exception, Http, Log}\u003c/code\u003e abilities allow us to express what this program needs in order to run. The \u003ccode\u003eAsk AppCtx\u003c/code\u003e should be familiar to functional programmers: it lets us carry context around functions. Finally, the \u003ccode\u003eCache Currency ForexResponse\u003c/code\u003e is the ability that lets us cache responses for each currency. We\u0026rsquo;ll dive deeper into this topic in the next few sections.\u003c/p\u003e\n\u003cp\u003eLet\u0026rsquo;s skip the \u003ccode\u003ehealthcheck\u003c/code\u003e endpoint for now and let\u0026rsquo;s look at the \u003ccode\u003eGET /rates\u003c/code\u003e endpoint.\u003c/p\u003e\n\u003cdiv class=\"highlight unison-lang\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eapi\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egetExchangeRate \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{Route,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eException\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eHttp\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eLog\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eAsk\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAppCtx\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eForexResponse\u003c/span\u003e} ()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eapi\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egetExchangeRate \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  use \u003cspan style=\"color:#66d9ef\"\u003eParseQuery\u003c/span\u003e currency\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  use \u003cspan style=\"color:#66d9ef\"\u003eParser\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  queries \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e (currency \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;from\u0026#34;\u003c/span\u003e, currency \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;to\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  rate \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    match \u003cspan style=\"color:#66d9ef\"\u003eRoute\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eroute \u003cspan style=\"color:#66d9ef\"\u003eGET\u003c/span\u003e (s \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;api\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003e s \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;rates\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e queries) with\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      (\u003cspan style=\"color:#66d9ef\"\u003eSome\u003c/span\u003e from, \u003cspan style=\"color:#66d9ef\"\u003eSome\u003c/span\u003e to) \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e service\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eforex from to\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e throw (\u003cspan style=\"color:#66d9ef\"\u003eBadRequest\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;invalid from/to query params\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  forex\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehandler (toEither rate)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWe can observe the \u003ccode\u003eRoute\u003c/code\u003e ability here, which is eliminated in the \u003ccode\u003eapi.routes\u003c/code\u003e function by the \u003ccode\u003eRoute.run\u003c/code\u003e handler. This is essentially how abilities are handled in Unison. In a way, they are similar to interfaces for which we provide a specific implementation when the components are wired up.\u003c/p\u003e\n\u003cp\u003eThe \u003ccode\u003eforex.handler\u003c/code\u003e function is responsible for building HTTP responses, capturing domain errors.\u003c/p\u003e\n\u003cdiv class=\"highlight unison-lang\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e http\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eForexError\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUnknownError\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eNat\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eText\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eInvalidApiKey\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eText\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eApiLimitReached\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eText\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eNotFound\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eText\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eBadRequest\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eText\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eapi\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eforex\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehandler \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eEither\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eForexError\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eExchangeRate\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e{\u003cspan style=\"color:#66d9ef\"\u003eRoute\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eLog\u003c/span\u003e} ()\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"forex-service\"\u003eForex Service\u003c/h3\u003e\n\u003cp\u003eUltimately, we have the \u003ccode\u003eservice.forex\u003c/code\u003e function that, given two currencies (from and to, respectively), returns an \u003ccode\u003eExchangeRate\u003c/code\u003e with domain errors expressed via the \u003ccode\u003eThrow\u003c/code\u003e ability.\u003c/p\u003e\n\u003cdiv class=\"highlight unison-lang\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eservice\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eforex \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{Exception,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eHttp\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eLog\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eThrow\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eForexError\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eAsk\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAppCtx\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eForexResponse\u003c/span\u003e} \u003cspan style=\"color:#66d9ef\"\u003eExchangeRate\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eservice\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eforex from to \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  use \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e text\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  fromText \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e text from\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  toText \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e text to\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  match \u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget from with\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eSome\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eForexResponse\u003c/span\u003e kv)\u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e isSome (\u003cspan style=\"color:#66d9ef\"\u003eMap\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget toText kv)  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003eLog\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003einfo \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Cache hit!\u0026#34;\u003c/span\u003e [(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;from\u0026#34;\u003c/span\u003e, fromText), (\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;to\u0026#34;\u003c/span\u003e, toText)]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003eExchangeRate\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eOptional\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egetOrBug \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;unreachable\u0026#34;\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eMap\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget toText kv)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003eLog\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ewarn\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Cache miss. Making an external HTTP request.\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        [(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;from\u0026#34;\u003c/span\u003e, fromText), (\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;to\u0026#34;\u003c/span\u003e, toText)]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      response\u003cspan style=\"color:#f92672\"\u003e@\u003c/span\u003e(\u003cspan style=\"color:#66d9ef\"\u003eForexResponse\u003c/span\u003e kv) \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e http\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eforex from to ()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eset from response\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      match \u003cspan style=\"color:#66d9ef\"\u003eOptional\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emap \u003cspan style=\"color:#66d9ef\"\u003eExchangeRate\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eMap\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget toText kv) with\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eSome\u003c/span\u003e rate \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e rate\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eNone\u003c/span\u003e      \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e throw (\u003cspan style=\"color:#66d9ef\"\u003eNotFound\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;cache 404\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt attempts to fetch the currency data from the cache via \u003ccode\u003eCache.get\u003c/code\u003e. If there\u0026rsquo;s no cached value, it falls-back to calling the third-party API via the \u003ccode\u003ehttp.forex\u003c/code\u003e client \u0026mdash; caching the response for subsequent use.\u003c/p\u003e\n\u003cp\u003eThe \u003ca href=\"https://share.unison-lang.org/@gvolpe/forex/code/main/latest/types/Cache\"\u003eCache\u003c/a\u003e ability is defined as follows:\u003c/p\u003e\n\u003cdiv class=\"highlight unison-lang\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eability\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e k v \u003cspan style=\"color:#66d9ef\"\u003ewhere\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  get \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e k \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e{\u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e k v} \u003cspan style=\"color:#66d9ef\"\u003eOptional\u003c/span\u003e v\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  set \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e k \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e v \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e{\u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e k v} ()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  evict \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e k \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e{\u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e k v} ()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  expire \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e {\u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e k v} ()\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe service only uses the first two functions, but it also allows us to remove a specific key or remove all key-values that have expired. We\u0026rsquo;ll dive into the internals of the handler shortly.\u003c/p\u003e\n\u003ch3 id=\"http-client\"\u003eHTTP client\u003c/h3\u003e\n\u003cp\u003eAt last, we have the HTTP client that lets us fetch data from the third-party API.\u003c/p\u003e\n\u003cdiv class=\"highlight unison-lang\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ehttp\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eforex \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{Exception, Http, Log, Throw ForexError, Ask AppCtx} ForexResponse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ehttp\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eforex from to \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    (\u003cspan style=\"color:#66d9ef\"\u003eAppCtx\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eApiKey\u003c/span\u003e apiKey)) \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e ask\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    use \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e text\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    use \u003cspan style=\"color:#66d9ef\"\u003eText\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    uri \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eURI\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eparse (\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;https://v6.exchangerate-api.com/v6/latest/\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003e text from)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    request \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003eHttpRequest\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eaddHeader\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Authorization\u0026#34;\u003c/span\u003e (\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Bearer \u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003e apiKey) (\u003cspan style=\"color:#66d9ef\"\u003eHttpRequest\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget uri)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    response \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eHttp\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erequest request\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    match \u003cspan style=\"color:#66d9ef\"\u003eHttpResponse\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estatus response with\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003eStatus\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e200\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        responseBody \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e bodyText response\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        info \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;External Forex Response: \u0026#34;\u003c/span\u003e [(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;body\u0026#34;\u003c/span\u003e, responseBody)]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        match catch \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eDecoder\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erun \u003cspan style=\"color:#66d9ef\"\u003eForexResponse\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efromJson responseBody with\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#66d9ef\"\u003eLeft\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eFailure\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e e \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e)          \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003eLog\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eerror\u003c/span\u003e e \u003cspan style=\"color:#66d9ef\"\u003e[]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            throw (\u003cspan style=\"color:#66d9ef\"\u003eUnknownError\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e500\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;JSON decoding failure\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#66d9ef\"\u003eRight\u003c/span\u003e resp\u003cspan style=\"color:#f92672\"\u003e@\u003c/span\u003e(\u003cspan style=\"color:#66d9ef\"\u003eForexResponse\u003c/span\u003e kv) \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            match \u003cspan style=\"color:#66d9ef\"\u003eMap\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget (text to) kv with\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e              \u003cspan style=\"color:#66d9ef\"\u003eSome\u003c/span\u003e v \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e resp\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e              \u003cspan style=\"color:#66d9ef\"\u003eNone\u003c/span\u003e   \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e throw (\u003cspan style=\"color:#66d9ef\"\u003eNotFound\u003c/span\u003e (text from \u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34; -\u0026gt; \u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003e text to))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003eStatus\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e403\u003c/span\u003e e  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e throw (\u003cspan style=\"color:#66d9ef\"\u003eInvalidApiKey\u003c/span\u003e e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003eStatus\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e404\u003c/span\u003e e  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e throw (\u003cspan style=\"color:#66d9ef\"\u003eNotFound\u003c/span\u003e e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003eStatus\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e429\u003c/span\u003e e  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e throw (\u003cspan style=\"color:#66d9ef\"\u003eApiLimitReached\u003c/span\u003e e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003eStatus\u003c/span\u003e code e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e throw (\u003cspan style=\"color:#66d9ef\"\u003eUnknownError\u003c/span\u003e code e)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn order to authorize requests, an API key is needed, which we can fetch from the context via the \u003ccode\u003eAsk\u003c/code\u003e ability. We will soon learn about the rest of the data our application context holds.\u003c/p\u003e\n\u003cp\u003eOnce the request is fired up, we pattern-match on the HTTP response status code and model each response accordingly. Domain errors are expressed via the \u003ccode\u003eThrow\u003c/code\u003e ability.\u003c/p\u003e\n\u003ch3 id=\"cloud-deployments\"\u003eCloud Deployments\u003c/h3\u003e\n\u003cp\u003eFor the fun part, let\u0026rsquo;s see how easy it is to deploy our service to the \u003ca href=\"https://www.unison.cloud/\"\u003ecloud\u003c/a\u003e!\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/unison/deploy.png\" alt=\"deploy\"\u003e\u003c/p\u003e\n\u003cp\u003eLet\u0026rsquo;s ignore the \u003ccode\u003eforex-cache-cleanser\u003c/code\u003e service for a minute, and let\u0026rsquo;s focus on the \u003ccode\u003eforex\u003c/code\u003e service instead. We can observe the latest version of the service exposed at \u003ccode\u003ehttps://gvolpe.unison-services.cloud/s/forex\u003c/code\u003e now points to a specific service hash showed right above. Unison will ensure this URL stays up-to-date.\u003c/p\u003e\n\u003cp\u003eThe \u003ccode\u003edeploy\u003c/code\u003e function is our main entry point based on the \u003ccode\u003eCloud.main\u003c/code\u003e handler.\u003c/p\u003e\n\u003cdiv class=\"highlight unison-lang\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edeploy\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{IO, Exception} ()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edeploy\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eCloud\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emain \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    use base ignore\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    env \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eEnvironment\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enamed \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;forex\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    db \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eDatabase\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enamed \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;currencies\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eDatabase\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eassign db env\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    apiKey \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCloud\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esubmit env \u003cspan style=\"color:#66d9ef\"\u003eApiKey\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efetch\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    cacheConfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCacheConfig\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;currencies\u0026#34;\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eRemote\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eDuration\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehours \u003cspan style=\"color:#ae81ff\"\u003e24\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    appCtx \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAppCtx\u003c/span\u003e db cacheConfig apiKey\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    httpServiceHash \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003eCloud\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edeployHttp env (req \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eAsk\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eprovide appCtx \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epersistent \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e api\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eroutes req)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    httpServiceName \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eServiceName\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enamed \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;forex\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ignore \u003cspan style=\"color:#f92672\"\u003e\u0026lt;|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eServiceName\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eassign httpServiceName httpServiceHash\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHere\u0026rsquo;s where all the abilities need to be eliminated, resulting only in \u003ccode\u003e{IO, Exception}\u003c/code\u003e, which are handled by the Unison runtime.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eCache k v\u003c/code\u003e is eliminated by \u003ccode\u003eCache.persistent\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eAsk ctx\u003c/code\u003e is eliminated by \u003ccode\u003eAsk.provide\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eHttp, Log, Remote\u003c/code\u003e are eliminated by \u003ccode\u003eCloud.deployHttp\u003c/code\u003e, which introduces the \u003ccode\u003eCloud\u003c/code\u003e ability.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eCloud\u003c/code\u003e is eliminated by the \u003ccode\u003eCloud.main\u003c/code\u003e handler.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eNow we can monitor our application logs in the Unison Cloud console.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/unison/api-logs.png\" alt=\"forex\"\u003e\u003c/p\u003e\n\u003cp\u003eQuite neat! Isn\u0026rsquo;t it? Now let\u0026rsquo;s move on to the next exciting topic.\u003c/p\u003e\n\u003ch3 id=\"caching\"\u003eCaching\u003c/h3\u003e\n\u003cp\u003eThe main handler for the \u003ccode\u003eCache\u003c/code\u003e ability is based on an internal \u003ca href=\"https://share.unison-lang.org/@unison/cloud/code/main/latest/types/durable/Cell\"\u003eCell\u003c/a\u003e \u0026mdash; a table that stores a single durable value, which requires a \u003ca href=\"https://share.unison-lang.org/@unison/cloud/code/releases/20.7.2/latest/types/Database\"\u003eDatabase\u003c/a\u003e to be created.\u003c/p\u003e\n\u003cp\u003eWithout further ado, let\u0026rsquo;s look at the implementation and later discuss the relevant pieces.\u003c/p\u003e\n\u003cdiv class=\"highlight unison-lang\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epersistent \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{g, Cache k v} r -\u0026gt;{g, Exception, Remote, Ask AppCtx} r\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epersistent interactions \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  use \u003cspan style=\"color:#66d9ef\"\u003eCell\u003c/span\u003e modify\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  use \u003cspan style=\"color:#66d9ef\"\u003eInstant\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  use \u003cspan style=\"color:#66d9ef\"\u003eMap\u003c/span\u003e delete\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  (\u003cspan style=\"color:#66d9ef\"\u003eAppCtx\u003c/span\u003e db (\u003cspan style=\"color:#66d9ef\"\u003eCacheConfig\u003c/span\u003e cacheName cacheExpiration) \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e ask\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  expTime \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e toBaseDuration cacheExpiration\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  hasNotExpired \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eInstant\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e (v, \u003cspan style=\"color:#66d9ef\"\u003eInstant\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eBoolean\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  hasNotExpired now \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e cases (value, ts) \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e ts \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e expTime \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e now\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  impl \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCell\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eMap\u003c/span\u003e k (v, \u003cspan style=\"color:#66d9ef\"\u003eInstant\u003c/span\u003e)) \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eRequest\u003c/span\u003e {\u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e k v} r \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e r\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  impl cell \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e cases\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    { pure }                          \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e pure\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    { \u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget key \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e resume }       \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      now \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e now\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      lookup \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e match \u003cspan style=\"color:#66d9ef\"\u003eMap\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget key (\u003cspan style=\"color:#66d9ef\"\u003eCell\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eread cell) with\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eSome\u003c/span\u003e x\u003cspan style=\"color:#f92672\"\u003e@\u003c/span\u003e(v, \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e hasNotExpired now x \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eSome\u003c/span\u003e v\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eSome\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e        \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          modify cell (kv \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e (delete key kv, ()))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#66d9ef\"\u003eNone\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eNone\u003c/span\u003e          \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eNone\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      handle resume lookup with impl cell\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    { \u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eset key value \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e resume } \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      ts \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e now\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      modify cell (kv \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eMap\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eput key (value, ts) kv, ()))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      handle resume() with impl cell\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    { evict key \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e resume }           \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      modify cell (kv \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e (delete key kv, ()))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      handle resume() with impl cell\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    { expire \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e resume }              \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      now \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e now\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      modify cell (kv \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eMap\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efilter (hasNotExpired now) kv, ()))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      handle resume() with impl cell\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  handle interactions() with impl (\u003cspan style=\"color:#66d9ef\"\u003eCell\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enamed db cacheName \u003cspan style=\"color:#66d9ef\"\u003eMap\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eempty)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eA \u003ccode\u003eCache k v\u003c/code\u003e is internally represented as a \u003ccode\u003eMap k (v, Instant)\u003c/code\u003e stored in a \u003ccode\u003eCell\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight unison-lang\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eimpl\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCell\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eMap\u003c/span\u003e k (v, \u003cspan style=\"color:#66d9ef\"\u003eInstant\u003c/span\u003e)) \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eRequest\u003c/span\u003e {\u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e k v} r \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e r\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe \u003ccode\u003eInstant\u003c/code\u003e value represents the time when the value was written to the cache (see how \u003ccode\u003eCache.set\u003c/code\u003e is handled). When we look-up a key-value from the cache, we also look at the timestamp and check if it hasn\u0026rsquo;t expired yet. If it did, then the key-value is removed from the cache.\u003c/p\u003e\n\u003cp\u003eThis method of \u003ca href=\"https://redis.io/glossary/cache-invalidation/\"\u003ecache invalidation\u003c/a\u003e (one of my favorite topics in software engineering) is also known as \u0026ldquo;passive\u0026rdquo;, where only data that\u0026rsquo;s being accessed is checked for expirations. However, in bigger systems, data that is not accessed can account for considerable storage space when neglected.\u003c/p\u003e\n\u003cp\u003eArguably, in this simple application we don\u0026rsquo;t need an \u0026ldquo;active\u0026rdquo; cache invalidation method. In the original Forex application written in Haskell, cache expiration was already taken for granted, as Redis handles everything. However, I absolutely love a good challenge, and this brings up a great opportunity to further explore what Unison has got to offer.\u003c/p\u003e\n\u003cp\u003eTo finish up with the implementation of this handler, the \u003ccode\u003eevict\u003c/code\u003e function is based on \u003ccode\u003eMap.delete\u003c/code\u003e whereas the \u003ccode\u003eexpire\u003c/code\u003e function is based on \u003ccode\u003eMap.filter\u003c/code\u003e, filtering out the expired key-values from the internal map.\u003c/p\u003e\n\u003ch4 id=\"active-cache-invalidation\"\u003eActive Cache Invalidation\u003c/h4\u003e\n\u003cp\u003eA daily cron job calling \u003ccode\u003eCache.expire\u003c/code\u003e will be one valid way of implementing active cache invalidation. Now can we do this with Unison services that are deployed via the \u003ccode\u003eCloud\u003c/code\u003e handlers? Unfortunately, this only suits applications which follow a request-response cycle.\u003c/p\u003e\n\u003cp\u003eFor repeated scheduled jobs or queueing systems, we can leverage the \u003ca href=\"https://share.unison-lang.org/@unison/cloud/code/releases/20.7.2/latest/types/Daemon\"\u003eDaemon\u003c/a\u003e ability, which is considered a low-level cloud utility and it\u0026rsquo;s a \u003ca href=\"https://www.unison.cloud/pricing/\"\u003epaid feature\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eWe could start defining a simple scheduled job that calls itself every 24 hours.\u003c/p\u003e\n\u003cdiv class=\"highlight unison-lang\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edaemon\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ejob \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{Exception, Remote, Ask AppCtx, Cache Currency ForexResponse} ()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edaemon\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ejob \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  use \u003cspan style=\"color:#66d9ef\"\u003eText\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  (\u003cspan style=\"color:#66d9ef\"\u003eAppCtx\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eCacheConfig\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e cacheExpiration) \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e ask\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  durationText \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eDuration\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etoText (toBaseDuration cacheExpiration)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  impl \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{Exception, Remote, Cache Currency ForexResponse} ()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  impl \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003eLog\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003einfo (\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Next cache removal in \u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003e durationText) \u003cspan style=\"color:#66d9ef\"\u003e[]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003eRemote\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esleep cacheExpiration\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eexpire\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003eLog\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ewarn \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Expired key-values have been removed\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e[]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  forever impl\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHowever, there\u0026rsquo;s currently a big limitation and we can\u0026rsquo;t monitor daemon logs in the Unison Cloud console as we do with regular services; its API is limited to streaming to the \u003ccode\u003eucm\u003c/code\u003e console. For this reason, we\u0026rsquo;ll use a \u0026ldquo;logging service\u0026rdquo;, which the daemon can natively call via the \u003ccode\u003eServices\u003c/code\u003e ability.\u003c/p\u003e\n\u003cp\u003eFirstly, we\u0026rsquo;ll create the program that will log the messages.\u003c/p\u003e\n\u003cdiv class=\"highlight unison-lang\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e domain\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eLogMsg\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e { level \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLevel\u003c/span\u003e, text \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eText\u003c/span\u003e, json \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eJson\u003c/span\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edaemon\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elogJson \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLogMsg\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e{\u003cspan style=\"color:#66d9ef\"\u003eException\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eLog\u003c/span\u003e} ()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edaemon\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elogJson \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e cases\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eLogMsg\u003c/span\u003e level msg json \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e atLevel\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elazyJson level (\u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e msg) \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e json\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWe will then write a service named \u0026ldquo;forex-cache-cleanser\u0026rdquo; whose only resposibility is to run the \u003ccode\u003elogJson\u003c/code\u003e program. The \u003ccode\u003eCloud.deploy\u003c/code\u003e handler can be used to deploy this service and get its hash.\u003c/p\u003e\n\u003cdiv class=\"highlight unison-lang\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edeploy\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{IO, Exception} ()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edeploy\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eCloud\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emain \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    logServiceHash \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eServiceHash\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLogMsg\u003c/span\u003e ()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    logServiceHash \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCloud\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edeploy env daemon\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elogJson\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    logServiceName \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eServiceName\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enamed \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;forex-cache-cleanser\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ignore \u003cspan style=\"color:#f92672\"\u003e\u0026lt;|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eServiceName\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eassign logServiceName logServiceHash\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    daemon\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edeploy env \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      provide appCtx \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e persistent (cacheCleanser logServiceHash)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eNext, we invoke the \u003ccode\u003edaemon.deploy\u003c/code\u003e function with the \u003ccode\u003ecacheCleanser\u003c/code\u003e program that takes the logging service hash as input. It uses the \u003ccode\u003eDaemon.deploy\u003c/code\u003e handler, which is analogous to \u003ccode\u003eCloud.deploy\u003c/code\u003e for regular services.\u003c/p\u003e\n\u003cdiv class=\"highlight unison-lang\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edaemon\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edeploy \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eEnvironment\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{Exception, Services, Remote} () -\u0026gt;{Exception, Cloud} ()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edaemon\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edeploy env program \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  cleanserDaemon \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eDaemon\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enamed \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;forex-cache-cleanser-daemon\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  cleanserHash \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eDaemon\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edeploy cleanserDaemon env program\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eDaemon\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eassign cleanserDaemon cleanserHash\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe final definition for the daily scheduled job is called \u003ccode\u003ecacheCleanser\u003c/code\u003e and is defined as follows.\u003c/p\u003e\n\u003cdiv class=\"highlight unison-lang\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edaemon\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecacheCleanser \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eServiceHash\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLogMsg\u003c/span\u003e ()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{Exception, Services, Remote, Ask AppCtx, Cache k v} ()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edaemon\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecacheCleanser logService \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  use \u003cspan style=\"color:#66d9ef\"\u003eServices\u003c/span\u003e call\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  use \u003cspan style=\"color:#66d9ef\"\u003eText\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  logMsg \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eText\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{Remote} LogMsg\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  logMsg msg \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    time \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e now\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eInstant\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etoRFC2822\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eLogMsg\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eInfo\u003c/span\u003e msg (\u003cspan style=\"color:#66d9ef\"\u003eJson\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eobject [(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;timestamp\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eJson\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etext time)])\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  (\u003cspan style=\"color:#66d9ef\"\u003eAppCtx\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eCacheConfig\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e cacheExpiration) \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e ask\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  durationText \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eDuration\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etoText (toBaseDuration cacheExpiration)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  impl \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{Exception, Services, Remote, Cache k v} ()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  impl \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    call logService (logMsg (\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Next cache removal in \u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003e durationText) ())\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eRemote\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esleep cacheExpiration\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eexpire\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    call logService (logMsg \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Expired key-values have been removed\u0026#34;\u003c/span\u003e ())\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  forever impl\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAt last, we can now monitor the daemon logs in the Unison Cloud console.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/unison/daemon-logs.png\" alt=\"daemon-logs\"\u003e\u003c/p\u003e\n\u003cp\u003eThis is it! We\u0026rsquo;ve effectively implemented active cache invalidation by running a daily scheduled job using Unison\u0026rsquo;s Daemon API. It\u0026rsquo;s still early days, so I believe it will continue to evolve, and I\u0026rsquo;m personally hoping that in the near future we are able to easily monitor logs like we do with regular services.\u003c/p\u003e\n\u003ch3 id=\"local-deployments\"\u003eLocal Deployments\u003c/h3\u003e\n\u003cp\u003eGuess what? Cloud programs can be run locally too!\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/unison/local.png\" alt=\"run-local\"\u003e\u003c/p\u003e\n\u003cp\u003eFor this purpose, the \u003ccode\u003edeploy\u003c/code\u003e function has been refactored, so that our main \u003ccode\u003eCloud\u003c/code\u003e program becomes \u003ccode\u003edeploy.api\u003c/code\u003e, while introducing two new functions.\u003c/p\u003e\n\u003cdiv class=\"highlight unison-lang\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edeploy\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elocal \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{IO, Exception} ()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edeploy\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elocal \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCloud\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emain\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elocal\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eserve \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  deploy\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eapi (\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e getEnv \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;FOREX_API_KEY\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eApiKey\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edeploy\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecloud \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{IO, Exception} ()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edeploy\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecloud \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCloud\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emain \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  deploy\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eapi (env \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCloud\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esubmit env \u003cspan style=\"color:#66d9ef\"\u003eApiKey\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efetch)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edeploy\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eapi \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eEnvironment\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e {\u003cspan style=\"color:#66d9ef\"\u003eCloud\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eIO\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eException\u003c/span\u003e} \u003cspan style=\"color:#66d9ef\"\u003eApiKey\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e {\u003cspan style=\"color:#66d9ef\"\u003eCloud\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eIO\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eException\u003c/span\u003e} ()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edeploy\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eapi getApiKey \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn the cloud, we submit the job of fetching the API key from the \u003ccode\u003eEnvironment.Config\u003c/code\u003e ability, whereas locally we simply fetch it from our local environment variables.\u003c/p\u003e\n\u003cp\u003eI was very impressed to learn that the Daemon API is also supported locally!\u003c/p\u003e\n\u003ch3 id=\"testing\"\u003eTesting\u003c/h3\u003e\n\u003cp\u003eWe couldn\u0026rsquo;t finish up this blog-post without talking about tests!\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/unison/tests-ok.png\" alt=\"tests-ok\"\u003e\u003c/p\u003e\n\u003cp\u003eThere are a bunch of tests defined in this project, and the image above shows what it looks like to run the entire test suite in the UCM console. We can observe that 13 tests correspond to cached test results, whereas the remaining 5 tests were run once again.\u003c/p\u003e\n\u003cp\u003eThe reason is that Unison doesn\u0026rsquo;t run tests until a function that\u0026rsquo;s being used changes its hash. When this is the case, all those tests affected by the hash change will be re-run. Otherwise, the tests results displayed in the console will be those that have been previously cached.\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s one of the tests for the HTTP client that expects a successful response.\u003c/p\u003e\n\u003cdiv class=\"highlight unison-lang\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ehttp\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eforex\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etest\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eok \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e [\u003cspan style=\"color:#66d9ef\"\u003eResult\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ehttp\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eforex\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etest\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eok \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e test\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003everify \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  body \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003econversion_rates\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e: {\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003eEUR\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e: 1.2, \u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003eJPY\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e: 5.24}}\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  resp \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eHttpResponse\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eok (\u003cspan style=\"color:#66d9ef\"\u003eBody\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efromText body)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  response \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e logAskThrow \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eHttp\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estub resp (http\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eforex \u003cspan style=\"color:#66d9ef\"\u003eUSD\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eEUR\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  expected \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eForexResponse\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eList\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etoMap [(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;EUR\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e1.2\u003c/span\u003e), (\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;JPY\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e5.24\u003c/span\u003e)])\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  test\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eensureEqual response expected\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt invokes the \u003ccode\u003ehttp.forex\u003c/code\u003e function, which has the following type signature (recap):\u003c/p\u003e\n\u003cdiv class=\"highlight unison-lang\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ehttp\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eforex \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{Exception, Http, Log, Throw ForexError, Ask AppCtx} ForexResponse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn this case, we\u0026rsquo;re only interested in the \u003ccode\u003eHttp\u003c/code\u003e ability. We don\u0026rsquo;t want to deal with the other ones, so we can eliminate them with \u0026ldquo;stub\u0026rdquo; handlers defined under the \u003ccode\u003etests\u003c/code\u003e namespace, e.g.\u003c/p\u003e\n\u003cdiv class=\"highlight unison-lang\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003etests\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehandler\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elogAsk \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{g, Log, Ask AppCtx} a -\u0026gt;{g} a\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003etests\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehandler\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elogAsk program \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  db \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eDatabase\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eDatabase\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eId\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eId\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;test\u0026#34;\u003c/span\u003e) \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;test\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  cfg \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCacheConfig\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;test\u0026#34;\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eRemote\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eDuration\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehours \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ctx \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAppCtx\u003c/span\u003e db cfg (\u003cspan style=\"color:#66d9ef\"\u003eApiKey\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;test\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  provide ctx \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLog\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estub program\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003etests\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehandler\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elogAskThrow \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{g, Log, Throw e, Ask AppCtx} a -\u0026gt;{g} a\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003etests\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehandler\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elogAskThrow program \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eThrow\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etoBug \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e logAsk program\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHere\u0026rsquo;s another interesting test that requires a different set of abilities.\u003c/p\u003e\n\u003cdiv class=\"highlight unison-lang\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eapi\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egetExchangeRate\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etest\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecachedOk \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e [\u003cspan style=\"color:#66d9ef\"\u003eResult\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eapi\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egetExchangeRate\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etest\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecachedOk \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e test\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003everify \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  keyValue \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eUSD\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eForexResponse\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eList\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etoMap [(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;EUR\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e1.25\u003c/span\u003e)]))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  route \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e cacheRoute getExchangeRate keyValue\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  request \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eHttpRequest\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget (\u003cspan style=\"color:#66d9ef\"\u003eURI\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eparse \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/api/rates?from=USD\u0026amp;to=EUR\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  response \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e runRoute route request\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  test\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eensureEqual (\u003cspan style=\"color:#66d9ef\"\u003eHttpResponse\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estatus response) (\u003cspan style=\"color:#66d9ef\"\u003eStatus\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e200\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;OK\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt verifies the result comes from the cache, for which we use the \u003ccode\u003eCache.inMemory\u003c/code\u003e handler.\u003c/p\u003e\n\u003cdiv class=\"highlight unison-lang\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003etests\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ebuilder\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecacheRoute \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{Route, Exception, Http, Remote, Log, Ask AppCtx, Cache k v} ()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e (k, v)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{Route, Exception, Remote} ()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003etests\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ebuilder\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecacheRoute api kv \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003einMemory \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eset (at1 kv) (at2 kv)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eHttp\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estub\u0026#39; \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e logAsk api\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eTo summarize this section, the following image shows that tests can sometimes fail.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/unison/tests-fail.png\" alt=\"tests-fail\"\u003e\u003c/p\u003e\n\u003cp\u003eHave a look at the other available tests and let me know if we can do better!\u003c/p\u003e\n\u003ch2 id=\"final-thoughts\"\u003eFinal thoughts\u003c/h2\u003e\n\u003cp\u003eQuoting their official website, I couldn\u0026rsquo;t agree more with it.\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '📜';\"\u003e\n  \u003ch4\u003eUnison Lang\u003c/h4\u003e\n  \u003cp\u003e\n\"Unison is a friendly programming language from the future: statically-typed, functional, and a lot of fun 😄\"\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cp\u003e\u003cimg src=\"../../images/gifs/future.webp\" alt=\"future\"\u003e\u003c/p\u003e\n\u003cp\u003eI will certainly continue to learn what this great language has to offer while having lots of fun. There are multiple applications that can be written in it, but if there\u0026rsquo;s an area where you should seriously consider it, is for building distributed systems deployed to their cloud infrastructure.\u003c/p\u003e\n\u003cp\u003eLast but not least, I would like to give a big shout out to the Unison team for all the great work they are doing. I had a few questions when going through the documentation and you guys have been awesome!\u003c/p\u003e\n\u003cp\u003eCheck out the source code and discover more: \u003ca href=\"https://share.unison-lang.org/@gvolpe/forex\"\u003ehttps://share.unison-lang.org/@gvolpe/forex\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eBest,\nGabriel.\u003c/p\u003e\n","date":"2025-05-11","dateFormatted":"2025.05.11","excerpt":"I have been following the Unison programming language for as long as I can remember, given that its authors are very well-known in the Scala ecosystem. Though, …","featured":true,"mood":null,"permalink":"https://gvolpe.com/blog/unison-forex/","readingTime":17,"slug":"unison-forex","subtitle":null,"summary":"\u003cp\u003eI have been following the \u003ca href=\"https://www.unison-lang.org/\"\u003eUnison programming language\u003c/a\u003e for as long as I can remember, given that \u003ca href=\"https://www.unison-lang.org/unison-computing/\"\u003eits authors\u003c/a\u003e are very well-known in the Scala ecosystem. Though, it took me a good few years to finally learn it by immersing myself into their amazing documentation and tutorials.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eTL;DR: I love it more and more every single day!\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eThe language itself is easy to learn for anyone coming from a functional programming background. Even their implementation of \u003ca href=\"https://arxiv.org/pdf/1611.09259\"\u003ealgebraic effects\u003c/a\u003e \u0026mdash; called \u003ca href=\"https://www.unison-lang.org/docs/fundamentals/abilities/\"\u003eabilities\u003c/a\u003e \u0026mdash; are quite straightforward. What sets the language apart from others is their interactive approach to writing code.\u003c/p\u003e","tags":["unison","cloud","programming","cache"],"title":"Unison: Forex API \u0026 Caching","url":"https://gvolpe.com/blog/unison-forex/","wordCount":3586},{"comments":true,"content":"\u003ch2 id=\"what-is-home-manager\"\u003eWhat is Home Manager?\u003c/h2\u003e\n\u003cp\u003eQuoting the official description from their \u003ca href=\"https://github.com/nix-community/home-manager/\"\u003ewebsite\u003c/a\u003e:\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '📜';\"\u003e\n  \u003ch4\u003eHome Manager\u003c/h4\u003e\n  \u003cp\u003e\n\"This project provides a basic system for managing a user environment using the Nix package manager together with the Nix libraries found in Nixpkgs. It allows declarative configuration of user specific (non-global) packages and dotfiles.\"\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cp\u003eWait a minute! If you had been following my blog for a while, you know a lot has been written about Home Manager already, so why am I going back to basics? Mainly because I noticed plenty of confusion out in the wild, thus I would like to demystify some of the misconceptions that exist, and provide you with ideas and examples so that you can reap the benefits of this magnificent tool.\u003c/p\u003e\n\u003ch3 id=\"nix-is-hard\"\u003eNix is hard\u003c/h3\u003e\n\u003cp\u003eLet\u0026rsquo;s start by setting the record straight: \u003cem\u003e\u003cstrong\u003eNix has an incredible steep learning curve\u003c/strong\u003e\u003c/em\u003e. There are no shortcuts to getting productive with it, though, it very much pays off in the end.\u003c/p\u003e\n\u003cp\u003eLearning Nix compares to learning functional programming coming from an object-oriented / primitive programming background. Your brain needs to be rewired. You are now dealing with immutable data structures that go as far as the operating system \u0026mdash; if you dare swimming in \u003ca href=\"https://nixos.org/\"\u003eNixOS\u003c/a\u003e waters.\u003c/p\u003e\n\u003cp\u003eSo yes, Nix is hard, but it\u0026rsquo;s all worth it once it clicks. However, you can\u0026rsquo;t expect to fully grasp it without committing 100%. \u003cem\u003e\u003cstrong\u003eNix requires you to go all the way in\u003c/strong\u003e\u003c/em\u003e. Failure to do so may quickly lead you into an inevitable outburst of rage and frustration against it.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/gifs/rage.webp\" alt=\"rage\"\u003e\u003c/p\u003e\n\u003cp\u003eSo keep that in mind if you\u0026rsquo;re only taking your first steps with Nix and Home Manager 😁\u003c/p\u003e\n\u003ch2 id=\"use-cases\"\u003eUse cases\u003c/h2\u003e\n\u003cp\u003eHome Manager can be used incrementally. It is not necessary to use its every feature all at once. My recommendation is to progress with the tool by taking smaller steps instead, for which I think the following ordered list of features is a good start right after you install it.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eSoftware installation.\u003c/li\u003e\n\u003cli\u003eDeclarative programs.\u003c/li\u003e\n\u003cli\u003eDeclarative services.\u003c/li\u003e\n\u003cli\u003eDotfiles management.\u003c/li\u003e\n\u003cli\u003eOverlays, overrides, and custom modules.\u003c/li\u003e\n\u003cli\u003eFull desktop management.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eGet familiar with each step as you move forward. Don\u0026rsquo;t rush trying to understand everything in one go as that can quickly become a very daunting task, so take your time learning how things work.\u003c/p\u003e\n\u003cp\u003eThis is more or less the path I have taken with it, but the cool thing is that it can be used in a sort of mix-and-match fashion. For instance, you can only use it to install packages (1) and run services (3), or to install declarative programs (2) and override some packages (6), and so on.\u003c/p\u003e\n\u003ch2 id=\"software-installation\"\u003eSoftware installation\u003c/h2\u003e\n\u003cp\u003eInstalling software from \u003ccode\u003enixpkgs\u003c/code\u003e \u0026mdash; the \u003ca href=\"https://repology.org/repositories/graphs\"\u003elargest and most up-to-date package repository in the entire universe\u003c/a\u003e \u0026mdash; is arguably the simplest use case.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  home\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epackages \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ewith\u003c/span\u003e pkgs; [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    chromium \u003cspan style=\"color:#75715e\"\u003e# browser\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    nemo \u003cspan style=\"color:#75715e\"\u003e# file manager\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    reaper \u003cspan style=\"color:#75715e\"\u003e# digital audio workstation (DAW)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThat\u0026rsquo;s all it takes to install these three packages with default settings.\u003c/p\u003e\n\u003cp\u003eA lot of them may require custom configuration, which are part of the so-called \u0026ldquo;dotfiles\u0026rdquo;. In the last few sections of this post, we\u0026rsquo;ll learn how to efficiently manage them via Home Manager. Until then, it is recommended that you manage them as you would without it.\u003c/p\u003e\n\u003ch3 id=\"pro-tips\"\u003ePro tips\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003eHead over to \u003ca href=\"https://search.nixos.org/packages\"\u003esearch.nixos.org/packages\u003c/a\u003e to search for a given package.\u003c/li\u003e\n\u003cli\u003eUse \u003ca href=\"https://github.com/diamondburned/nix-search\"\u003enix-search\u003c/a\u003e from your terminal if you don\u0026rsquo;t like Web UIs.\n\u003cul\u003e\n\u003cli\u003eAlternatively, \u003ccode\u003enix search nixpkgs \u0026lt;package-name\u0026gt;\u003c/code\u003e also works, albeit being much slower.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/nix-community/nix-index\"\u003enix-index\u003c/a\u003e is a good alternative to \u003ccode\u003ecommand-not-found\u003c/code\u003e (invoked when you mistype a command).\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"declarative-programs\"\u003eDeclarative programs\u003c/h2\u003e\n\u003cp\u003ePrograms bring the NixOS module system into Home Manager, making it extremely easy and safe to install software declaratively, e.g.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ pkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  programs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efoot \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    server\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    settings \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      main \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        shell \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003epkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efish\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/fish\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        font \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;JetBrainsMono Nerdfont:size=10\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        pad \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;12x12\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        dpi-aware \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;yes\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        selection-target \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;both\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      colors \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        alpha \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e.5\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis configuration installs the \u003ca href=\"https://codeberg.org/dnkl/foot\"\u003eFoot terminal emulator\u003c/a\u003e by generating the right configuration file (dotfile) based on the given settings. You don\u0026rsquo;t need to worry about whether the software needs a YAML or TOML file in a specific directory \u0026mdash; it\u0026rsquo;s all Nix as far as we are concerned.\u003c/p\u003e\n\u003ch3 id=\"pro-tips-1\"\u003ePro tips\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003eHead over to \u003ca href=\"https://nix-community.github.io/home-manager/options.xhtml\"\u003eHome Manager configuration options\u003c/a\u003e to find out the program\u0026rsquo;s settings.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eIn cases where further customization is needed for a package, we have a few options:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eInstall it via \u003ccode\u003ehome.packages\u003c/code\u003e (1) and do proper dotfiles management (4).\u003c/li\u003e\n\u003cli\u003eWrite a custom module similar to the original one that does what we need.\n\u003cul\u003e\n\u003cli\u003eUpstream a PR if you can allow the time and think other users can benefit. It\u0026rsquo;s OSS after all 😊\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"declarative-services\"\u003eDeclarative services\u003c/h2\u003e\n\u003cp\u003eWhen a program needs to run as a daemon with certain permissions, a declarative service is what we need, which on Linux are supported via \u003ca href=\"https://systemd.io/\"\u003esystemd\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eExamples of services include databases (PostgreSQL), analytics (Plausible), notification daemon (Dunst), etc. For instance, here\u0026rsquo;s my declarative configuration for \u003ca href=\"https://github.com/coldfix/udiskie\"\u003eudiskie\u003c/a\u003e: automounter for removable media.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ pkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  services\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eudiskie \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    automount \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    notify \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    settings \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      program_options \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        file_manager \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003epkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emimeo\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/mimeo\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    tray \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;always\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe same caveats regarding customizations described for programs apply here as well.\u003c/p\u003e\n\u003ch3 id=\"pro-tips-2\"\u003ePro tips\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003eServices are also documented in the \u003ca href=\"https://nix-community.github.io/home-manager/options.xhtml\"\u003eHome Manager configuration options\u003c/a\u003e site.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eWhen in need of a custom service that\u0026rsquo;s not available upstream, it\u0026rsquo;s always useful to look at source code examples to understand how we can create our own. For instance, here\u0026rsquo;s the code for \u003ca href=\"https://github.com/nix-community/home-manager/blob/83ecd50915a09dca928971139d3a102377a8d242/modules/services/dunst.nix#L180\"\u003edunst service\u003c/a\u003e. You can land at it directly from the configuration options site, it\u0026rsquo;s all linked there.\u003c/p\u003e\n\u003ch2 id=\"dotfiles-management\"\u003eDotfiles management\u003c/h2\u003e\n\u003cp\u003eThis is where I\u0026rsquo;ve seen the biggest confusion when using Home Manager, so let\u0026rsquo;s have a deeper look at the options we have when it comes to managing dotfiles.\u003c/p\u003e\n\u003cp\u003eTo make things easier to follow, we are going to look at a concrete example: installing \u003ca href=\"https://github.com/dylanaraps/neofetch\"\u003eneofetch\u003c/a\u003e.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eSide note: \u003cem\u003eneofetch is highly customizable and still works flawlessly, even if it\u0026rsquo;s now archived and there are alternatives such as \u003ca href=\"https://github.com/hykilpikonna/hyfetch\"\u003ehyfetch\u003c/a\u003e and \u003ca href=\"https://github.com/fastfetch-cli/fastfetch\"\u003efastfetch\u003c/a\u003e\u003c/em\u003e.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eMost people would start with something like this:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ pkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  home\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epackages \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eneofetch ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  xdg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003econfigFile\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;neofetch/config.conf\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esource \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e./neofetch.conf\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#75715e\"\u003e# alternatively, one could write the configuration inline as follows\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#75715e\"\u003e# xdg.configFile.\u0026#34;neofetch/config.conf\u0026#34;.text = \u0026#39;\u0026#39; #content here \u0026#39;\u0026#39;;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWhere \u003ccode\u003eneofetch.conf\u003c/code\u003e is your standard \u003ca href=\"https://github.com/dylanaraps/neofetch/wiki/Customizing-Info\"\u003econfiguration file\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eHowever, this means that the actual dotfile used by the software that lives under \u003ccode\u003e~/.config/neofetch/config.conf\u003c/code\u003e now \u003cem\u003ebelongs to Home Manager\u003c/em\u003e and you don\u0026rsquo;t have write permissions.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ readlink -f ~/.config/neofetch/config.conf\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/nix/store/21h3kkyipjdcsgv72vbd2i1h6vzcl0pk-hm_electric.conf\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ ls -la /nix/store/21h3kkyipjdcsgv72vbd2i1h6vzcl0pk-hm_electric.conf\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e.r--r--r-- 17k root  \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e Jan  \u003cspan style=\"color:#ae81ff\"\u003e1970\u003c/span\u003e /nix/store/21h3kkyipjdcsgv72vbd2i1h6vzcl0pk-hm_electric.conf\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe only way to make changes is by modifying your Nix configuration, running \u003ccode\u003ehome-manager switch\u003c/code\u003e ⏳, and hoping that the changes you made were correct. Otherwise, it\u0026rsquo;s rinse and repeat, which is less than ideal when you\u0026rsquo;re tinkering around with dotfiles and want to tweak little things.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/gifs/father.webp\" alt=\"father\"\u003e\u003c/p\u003e\n\u003cp\u003eThis begs the question: \u003cem\u003e\u003cstrong\u003eIs Home Manager the wrong tool for managing dotfiles?\u003c/strong\u003e\u003c/em\u003e I guess most deserters found themselves at this crossroad at some point, so let\u0026rsquo;s find out more.\u003c/p\u003e\n\u003ch3 id=\"light-at-the-end-of-the-tunnel\"\u003eLight at the end of the tunnel\u003c/h3\u003e\n\u003cp\u003eLet me introduce you to the unsung hero: \u003cstrong\u003e\u003ca href=\"https://github.com/nix-community/home-manager/blob/83ecd50915a09dca928971139d3a102377a8d242/modules/files.nix#L64-L69\"\u003emkOutOfStoreSymlink\u003c/a\u003e\u003c/strong\u003e \u0026mdash; a simple function hidden in plain sight deep in the Home Manager source code, defined as follows:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efile\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkOutOfStoreSymlink \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e path:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    pathStr \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e toString path;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    name \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e hm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estrings\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estoreFileName (baseNameOf pathStr);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erunCommandLocal name {} \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;ln -s \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003eescapeShellArg pathStr\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e $out\u0026#39;\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt\u0026rsquo;s heavily under-documented, which doesn\u0026rsquo;t make things easier for beginners, so that\u0026rsquo;s something we should improve within the Nix community, but I hope this blog post is a good start.\u003c/p\u003e\n\u003cp\u003eSo, how does it help? Let\u0026rsquo;s get back to the \u003ccode\u003eneofetch\u003c/code\u003e example and refactor it to use this function.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ pkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  filePath \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/home/gvolpe/workspace/nix-config/programs/neofetch/electric.conf\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  configSrc \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efile\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkOutOfStoreSymlink filePath;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  home\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epackages \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eneofetch ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  xdg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003econfigFile\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;neofetch/config.conf\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esource \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e configSrc;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAfter one last \u003ccode\u003ehome-manager switch\u003c/code\u003e run, let\u0026rsquo;s now check the dotfile symlink target:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ readlink -f ~/.config/neofetch/config.conf\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/home/gvolpe/workspace/nix-config/home/programs/neofetch/electric.conf\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eSuccess! 💪 For the curious reader, this is not a direct symlink; it\u0026rsquo;s a chain of symlinks.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ readlink ~/.config/neofetch/config.conf\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/nix/store/qxvvj5v1w8za9wfb80cnl6g5ja7w9qzz-home-manager-files/.config/neofetch/config.conf\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ readlink /nix/store/qxvvj5v1w8za9wfb80cnl6g5ja7w9qzz-home-manager-files/.config/neofetch/config.conf\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/nix/store/d0cmjnz9advx1irqq1c6slrl0gjklipf-hm_electric.conf\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ readlink /nix/store/d0cmjnz9advx1irqq1c6slrl0gjklipf-hm_electric.conf\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/home/gvolpe/workspace/nix-config/home/programs/neofetch/electric.conf\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ ls -la /home/gvolpe/workspace/nix-config/home/programs/neofetch/electric.conf\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e.rw-r--r-- 17k gvolpe \u003cspan style=\"color:#ae81ff\"\u003e16\u003c/span\u003e Dec 21:44 /home/gvolpe/workspace/nix-config/home/programs/neofetch/electric.conf\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWe can now tweak our dotfiles as much as we want without Home Manager getting in the way. Once we\u0026rsquo;re happy with the results, we may want to go back to the immutable version (or not).\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/gifs/understanding.webp\" alt=\"understanding\"\u003e\u003c/p\u003e\n\u003cp\u003eIt\u0026rsquo;s worth noticing that even if we decide to keep the mutable version, the dotfiles that we modify can still be versioned in our git repository, as it is the case with the \u003ccode\u003eneofetch\u003c/code\u003e example.\u003c/p\u003e\n\u003cp\u003eA good declarative alternative could be \u003ca href=\"https://github.com/nix-community/impermanence\"\u003eimpermanence\u003c/a\u003e, which goes deep into handling persistent state, but it may be an over-killer solution if you\u0026rsquo;re only after dotfiles management.\u003c/p\u003e\n\u003ch3 id=\"pro-tips-3\"\u003ePro tips\u003c/h3\u003e\n\u003cp\u003eCan we do better? Certainly! We can leverage custom Home Manager modules, which may seem an advanced feature at first, but it\u0026rsquo;s a trivial concept once it clicks.\u003c/p\u003e\n\u003cp\u003eExamples always go a long way, so let\u0026rsquo;s dive right into it and define a new module named \u003ccode\u003edotfiles.nix\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ config\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  options \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    dotfiles \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      mutable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkEnableOption \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;mutable dotfiles\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      path \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkOption {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etypes\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epath;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        apply \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e toString;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        default \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003econfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehome\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehomeDirectory\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/workspace/nix-config/home\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        example \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003econfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehome\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehomeDirectory\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/.dotfiles\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Location of the dotfiles working copy\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt consists of two different options:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003emutable\u003c/code\u003e: a boolean flag that dictates whether we choose to build our entire Home Manager configuration via \u003ccode\u003emkOutOfStoreSymlink\u003c/code\u003e or as usual (the immutable version).\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003epath\u003c/code\u003e: the path where you keep your dotfiles working copy.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThe latter has a default value that points to where I keep my dotfiles, which is also a git repository. We\u0026rsquo;ll come back to this bit later on when we compare it to other solutions.\u003c/p\u003e\n\u003cp\u003eThis module can then be imported in the Home Manager configuration modules and we should be able to use it as any other module, such as \u003ccode\u003ehome.packages\u003c/code\u003e and other options.\u003c/p\u003e\n\u003cp\u003eThe main piece of Home Manager configuration would look something like this now:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  homeConfigurations\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;username\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e home-manager\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehomeManagerConfiguration {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e pkgs;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    modules \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e./dotfiles.nix\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e./home.nix\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      { dotfiles\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emutable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e; }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWe are now able to utilize this information in our \u003ccode\u003eneofetch\u003c/code\u003e configuration (and anywhere else too):\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ pkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  filePath \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003econfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edotfiles\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epath\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/programs/neofetch/electric.conf\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  configSrc \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003econfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edotfiles\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emutable \u003cspan style=\"color:#66d9ef\"\u003ethen\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e./electric.conf\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efile\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkOutOfStoreSymlink filePath;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  home\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epackages \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eneofetch ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  xdg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003econfigFile\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;neofetch/config.conf\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esource \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e configSrc;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe first thing we do is to evaluate the \u003ccode\u003edotfiles.mutable\u003c/code\u003e flag: if it\u0026rsquo;s set to \u003ccode\u003efalse\u003c/code\u003e, we import the configuration file into the Nix store; otherwise, we fallback to the \u003ccode\u003emkOutOfStoreSymlink\u003c/code\u003e function.\u003c/p\u003e\n\u003cp\u003eI was previously passing these values via \u003ccode\u003especialArgs\u003c/code\u003e, but I think having a dedicated \u003ccode\u003edotfiles\u003c/code\u003e module is a much better solution, inspired by \u003ca href=\"https://github.com/nix-community/home-manager/issues/2085#issuecomment-2022239332\"\u003ethis Github comment\u003c/a\u003e.\u003c/p\u003e\n\u003ch2 id=\"comparison\"\u003eComparison\u003c/h2\u003e\n\u003cp\u003eIn a nutshell, relying on \u003ccode\u003emkOutOfStoreSymlink\u003c/code\u003e ends up being comparable to managing your dotfiles with tools such as \u003ca href=\"https://www.gnu.org/software/stow/\"\u003eGNU stow\u003c/a\u003e or \u003ca href=\"https://www.chezmoi.io/\"\u003echezmoi\u003c/a\u003e \u003cem\u003eif we look exclusively at dotfiles management\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eTo understand whether using Home Manager for this is for you or not, we also need to look at what else it has in store for us, which goes back to leveraging the power of Nix.\u003c/p\u003e\n\u003cp\u003eFor instance, here are a few things we can easily achieve with Nix+HM that would be much harder with primitive stateful solutions. Following the \u003ccode\u003eneofetch\u003c/code\u003e example, we could:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eEnable image support.\u003c/li\u003e\n\u003cli\u003eOverride specific version.\u003c/li\u003e\n\u003cli\u003eBuild with different compiler flags.\u003c/li\u003e\n\u003cli\u003eGuarantee reproducibility across machines.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eEnabling image support is something I worked on recently using the foot terminal emulator on Hyprland, which supports image rendering via the \u003ca href=\"https://en.wikipedia.org/wiki/Sixel\"\u003eSixel protocol\u003c/a\u003e. Here\u0026rsquo;s all I had to do:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ pkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  neofetchPath \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emakeBinPath (\u003cspan style=\"color:#66d9ef\"\u003ewith\u003c/span\u003e pkgs; [ chafa imagemagick ]);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  neofetchSixelSupport \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eneofetch\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eoverrideAttrs (old: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    postInstall \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e      wrapProgram $out/bin/neofetch --prefix PATH : \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003eneofetchPath\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    \u0026#39;\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  });\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  home\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epackages \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ neofetchSixelSupport ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  xdg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003econfigFile\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;neofetch/config.conf\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esource \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e configSrc;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt adds the necessary dependencies (\u003ca href=\"https://hpjansson.org/chafa/\"\u003echafa\u003c/a\u003e and \u003ca href=\"https://imagemagick.org/\"\u003eimagemagick\u003c/a\u003e) to the PATH of \u003ccode\u003eneofetch\u003c/code\u003e, without making them globally available. It really doesn\u0026rsquo;t get any easier than this!\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/neofetch-image.png\" alt=\"neofetch\"\u003e\u003c/p\u003e\n\u003cp\u003eStrictly speaking about dotfiles management with Home Manager, the main benefit is the ability to freeze or lock our files to make them reproducible in other machines, or also to serve as a reproducible snapshot in time if we ever want to get back to that specific version.\u003c/p\u003e\n\u003cp\u003eSwitching back and forth between the immutable and mutable versions becomes an easy task with the \u003ccode\u003edotfiles\u003c/code\u003e module. Nonetheless, one has to invest time in the initial configuration to get to this point.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/gifs/makes-sense.webp\" alt=\"sense\"\u003e\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s how I switch between the two on my machine.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ home-manager switch --flake .#homeConfigurations.hyprland-hdmi.activationPackage\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ home-manager switch --flake .#homeConfigurations.hyprland-hdmi-mutable.activationPackage\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIf you understand all I wrote about it so far and still choose another solution like Stow, fair enough, it\u0026rsquo;s a great tool! Moreover, it can be combined with Home Manager; you can manage your dotfiles with Stow, and still take advantage of all the other features such as overlays/overrides and modules.\u003c/p\u003e\n\u003cp\u003eFeel free to dig into my \u003ca href=\"https://github.com/gvolpe/nix-config\"\u003eNix configuration\u003c/a\u003e files for more and leave any questions / comments here.\u003c/p\u003e\n\u003ch2 id=\"closing-remarks\"\u003eClosing remarks\u003c/h2\u003e\n\u003cp\u003eHome Manager rocks! 🤘 I hope you find this article useful and give yourself time to learn these tools the proper way. With the right amount of patience and dedication, you\u0026rsquo;ll become productive soon enough.\u003c/p\u003e\n\u003cp\u003eLast but not least, I would like to use this space to thank \u003ca href=\"https://rycee.net/\"\u003eRobert Helgesson\u003c/a\u003e for his invaluable work on Home Manager since its inception. It certainly takes considerable devotion to carry on with maintaining this project after so many years! 🙇\u003c/p\u003e\n\u003cp\u003eBest,\nGabriel.\u003c/p\u003e\n","date":"2024-12-17","dateFormatted":"2024.12.17","excerpt":"What is Home Manager? Quoting the official description from their website:\nHome Manager \"This project provides a basic system for managing a user environment …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/home-manager-dotfiles-management/","readingTime":11,"slug":"home-manager-dotfiles-management","subtitle":null,"summary":"\u003ch2 id=\"what-is-home-manager\"\u003eWhat is Home Manager?\u003c/h2\u003e\n\u003cp\u003eQuoting the official description from their \u003ca href=\"https://github.com/nix-community/home-manager/\"\u003ewebsite\u003c/a\u003e:\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '📜';\"\u003e\n  \u003ch4\u003eHome Manager\u003c/h4\u003e\n  \u003cp\u003e\n\"This project provides a basic system for managing a user environment using the Nix package manager together with the Nix libraries found in Nixpkgs. It allows declarative configuration of user specific (non-global) packages and dotfiles.\"\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cp\u003eWait a minute! If you had been following my blog for a while, you know a lot has been written about Home Manager already, so why am I going back to basics? Mainly because I noticed plenty of confusion out in the wild, thus I would like to demystify some of the misconceptions that exist, and provide you with ideas and examples so that you can reap the benefits of this magnificent tool.\u003c/p\u003e","tags":["nix","nixos","flakes","home-manager","dotfiles"],"title":"Home Manager: dotfiles management","url":"https://gvolpe.com/blog/home-manager-dotfiles-management/","wordCount":2254},{"comments":true,"content":"\u003ch2 id=\"introduction\"\u003eIntroduction\u003c/h2\u003e\n\u003cp\u003eThe folks at \u003ca href=\"https://garnix.io\"\u003eGarnix\u003c/a\u003e have done it again! You can now deploy a \u003ca href=\"https://nixos.org/\"\u003eNixOS server\u003c/a\u003e in minutes, just by defining your system on a \u003ccode\u003eflake.nix\u003c/code\u003e and \u003ccode\u003egit push\u003c/code\u003e\u0026lsquo;ing your changes (Garnix needs to be enabled for the repo).\u003c/p\u003e\n\u003cp\u003eIn their latest blog post \u003ca href=\"https://garnix.io/blog/hosting-nixos\"\u003eHands-on NixOS servers\u003c/a\u003e, they explain all the necessary steps to deploy your own server, so I won\u0026rsquo;t repeat things over. Furthermore, there\u0026rsquo;s \u003ca href=\"https://garnix.io/docs/hosting\"\u003eofficial documentation\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eInstead, I will explain how I used this opportunity to set up a web analytics server for this blog.\u003c/p\u003e\n\u003ch2 id=\"web-analytics\"\u003eWeb Analytics\u003c/h2\u003e\n\u003cp\u003eI first looked into the different open-source solutions that allow self-hosting, and ended up choosing \u003ca href=\"https://plausible.io/\"\u003ePlausible\u003c/a\u003e, briefly defined as follows:\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '📜';\"\u003e\n  \u003ch4\u003eOfficial\u003c/h4\u003e\n  \u003cp\u003e\n\"Plausible is intuitive, lightweight and open source web analytics. No cookies and fully compliant with GDPR, CCPA and PECR. Made and hosted in the EU, powered by European-owned cloud infrastructure 🇪🇺\"\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cp\u003eYou can find the source code for the self-hosting community edition \u003ca href=\"https://github.com/plausible/community-edition/\"\u003ehere\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eSo, how do we set it up to run as a service with NixOS? Here are the relevant pieces:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  host \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;analytics.gvolpe.com\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  internalPort \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e8000\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  imports \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e./agenix.nix\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  services\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eplausible \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    adminUser \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      name \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;admin\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      email \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;admin@garnix.io\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      activate \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      passwordFile \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/run/agenix/admin\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    server \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      baseUrl \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;https://\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ehost\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      port \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e internalPort;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      secretKeybaseFile \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/run/agenix/keybase\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  services\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enginx \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    virtualHosts\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ehost\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      locations\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eproxyPass \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;http://localhost:\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003etoString internalPort\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis configuration is written to \u003ccode\u003ehosts/default.nix\u003c/code\u003e. The Plausible server runs on \u003ccode\u003elocalhost:8000\u003c/code\u003e, which is proxied via \u003ca href=\"https://nginx.org/en/\"\u003enginx\u003c/a\u003e to ports 80 and 443 (the latter handled directly by Garnix).\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  networking\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efirewall\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eallowedTCPPorts \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#ae81ff\"\u003e80\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e443\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt also uses a \u003ca href=\"https://www.postgresql.org/\"\u003ePostgreSQL\u003c/a\u003e instance \u003ca href=\"https://github.com/NixOS/nixpkgs/blob/345c263f2f53a3710abe117f28a5cb86d0ba4059/nixos/modules/services/web-apps/plausible.nix#L308\"\u003eauto-magically set up by its NixOS module\u003c/a\u003e \u0026mdash; gotta love NixOS! 🤩\u003c/p\u003e\n\u003cp\u003eMoreover, note that I used a \u003ca href=\"https://garnix.io/docs/hosting/custom-domain\"\u003ecustom domain\u003c/a\u003e, but this is not a requirement. You can get started by setting it to a value that adheres to the following format:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt;HOST\u0026gt;.\u0026lt;BRANCH\u0026gt;.\u0026lt;REPONAME\u0026gt;.\u0026lt;GITHUB ORG/USER\u0026gt;.garnix.me\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eSo, for this example in particular, it would be as follows:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehost \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;web.main.web-analytics.gvolpe.garnix.me\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"secrets\"\u003eSecrets\u003c/h3\u003e\n\u003cp\u003eThe Plausible service requires two secrets: the admin user\u0026rsquo;s password, and the keybase secret. To set them up safely, we use \u003ca href=\"https://github.com/ryantm/agenix\"\u003eagenix\u003c/a\u003e, as documented in the \u003ca href=\"https://garnix.io/docs/hosting/secrets\"\u003eGarnix docs\u003c/a\u003e \u0026mdash; though, keep in mind its caveats!\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s the \u003ccode\u003eagenix.nix\u003c/code\u003e file imported in the previous code snippet:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  age \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    secrets \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      admin\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efile \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e../secrets/admin.age\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      keybase\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efile \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e../secrets/keybase.age\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    identityPaths \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/var/garnix/keys/repo-key\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis requires the agenix NixOS module to be imported first:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  nixosConfigurations\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eweb \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e nixpkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enixosSystem {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    modules \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      garnix-lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enixosModules\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egarnix\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      agenix\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enixosModules\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edefault\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e./hosts\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAlso note that every secret needs to be manually set up via \u003ccode\u003eagenix\u003c/code\u003e (see documentation), resulting in two encrypted files that can only be decrypted with the supplied SSH keys.\u003c/p\u003e\n\u003ch3 id=\"garnix-configuration\"\u003eGarnix configuration\u003c/h3\u003e\n\u003cp\u003eWe can opt for \u003ca href=\"https://garnix.io/docs/hosting/branch\"\u003econtinuous deployment\u003c/a\u003e, so that our server is re-deployed on every commit to the main branch. Here\u0026rsquo;s what we need to set in our \u003ccode\u003egarnix.yaml\u003c/code\u003e file:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-yaml\" data-lang=\"yaml\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eservers\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  - \u003cspan style=\"color:#f92672\"\u003econfiguration\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003eweb\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003edeployment\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003etype\u003c/span\u003e: \u003cspan style=\"color:#66d9ef\"\u003eon\u003c/span\u003e-\u003cspan style=\"color:#ae81ff\"\u003ebranch\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003ebranch\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003emain\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe configuration name \u003ccode\u003eweb\u003c/code\u003e corresponds to the exposed NixOS configuration:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix flake show\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egit+file:///home/gvolpe/workspace/web-analytics\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e├───nixosConfigurations\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   └───web: NixOS configuration\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e└───packages\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    └───x86_64-linux\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        └───default: package \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;agenix-0.15.0\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAnd of course, we could deploy on every \u003ca href=\"https://garnix.io/docs/hosting/pr\"\u003epull request\u003c/a\u003e too!\u003c/p\u003e\n\u003ch3 id=\"see-it-live\"\u003eSee it live!\u003c/h3\u003e\n\u003cp\u003eSure enough, after a few hours of traffic, we can already see active metrics:\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/plausible.png\" alt=\"analytics\"\u003e\u003c/p\u003e\n\u003cp\u003eYou can see it \u003ca href=\"https://analytics.gvolpe.com/gvolpe.com\"\u003elive here\u003c/a\u003e \u0026mdash; these analytics are publicly available.\u003c/p\u003e\n\u003cp\u003eNOTE: By default, signing up via the web is disabled; the only way to access it is with the \u0026ldquo;admin\u0026rdquo; user.\u003c/p\u003e\n\u003ch2 id=\"persistence\"\u003ePersistence\u003c/h2\u003e\n\u003cp\u003eLet\u0026rsquo;s now discuss a crucial aspect of server deployments: \u003cem\u003estate\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eI tried a few deployments until I got the service up and running, and by default, a new server with unique IP address would be provisioned on every commit to the configured branch, as we can see in the list of servers below (it can be accessed via your \u003ca href=\"https://garnix.io/servers\"\u003eGarnix account\u003c/a\u003e):\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/hosting-garnix.png\" alt=\"servers\"\u003e\u003c/p\u003e\n\u003cp\u003eThis is great for zero-downtime deployments, but Plausible persists its data in PostgreSQL, so we need that data to still be available on new deployments.\u003c/p\u003e\n\u003cp\u003eFortunately, Garnix \u003ca href=\"https://garnix.io/docs/hosting/persistence\"\u003esupports persistence\u003c/a\u003e via the following NixOS configuration:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  garnix\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eserver\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epersistence \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    name \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;plausible\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAs long as the \u003ccode\u003ename\u003c/code\u003e remains the same, Garnix will deploy new commits to the same machine (same IP address). This is great for quick solutions, but for anything serious, please consider backing up your data.\u003c/p\u003e\n\u003cp\u003eI might give \u003ca href=\"https://www.borgbackup.org/\"\u003eBorg\u003c/a\u003e a try soon enough, as it\u0026rsquo;s \u003ca href=\"https://search.nixos.org/options?channel=unstable\u0026amp;from=0\u0026amp;size=50\u0026amp;sort=relevance\u0026amp;type=packages\u0026amp;query=services.borgbackup\"\u003ealready packaged\u003c/a\u003e for Nix :)\u003c/p\u003e\n\u003ch2 id=\"machine\"\u003eMachine\u003c/h2\u003e\n\u003cp\u003eIt is worth mentioning a few things about the \u003cstrong\u003eFREE machine\u003c/strong\u003e we get by default on the free account, which you can connect to via SSH with its assigned IP address.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/ssh-machine.png\" alt=\"ssh-machine\"\u003e\u003c/p\u003e\n\u003cp\u003eWe\u0026rsquo;re obviously running NixOS! Furthermore, we have an Intel Xeon CPU with two cores and 4Gb of RAM, which I find very generous already, but what stands out the most to me is the free 40Gb of disk space!\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/ssh-monitor.png\" alt=\"ssh-monitor\"\u003e\u003c/p\u003e\n\u003cp\u003eHere we can observe a few services like Clickhouse and Postgres up and running, among other metrics.\u003c/p\u003e\n\u003ch2 id=\"final-words\"\u003eFinal words\u003c/h2\u003e\n\u003cp\u003eIf you have followed my blog for a while, it shouldn\u0026rsquo;t come as a surprise to see Garnix featured \u003ca href=\"../categories/#garnix\"\u003eonce again\u003c/a\u003e; they keep on delivering great features!\u003c/p\u003e\n\u003cp\u003eI think of this one as \u003cstrong\u003ehosting and continuous deployment made simple™️\u003c/strong\u003e. Nevertheless, bear in mind that this feature is currently in beta, so please report any issues you may find.\u003c/p\u003e\n\u003cp\u003eHave you tried Garnix yet? If not, what are you waiting for? 😉\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDISCLAIMER\u003c/strong\u003e: I am not affiliated to Garnix; this is an independent review.\u003c/p\u003e\n\u003cp\u003eBest,\nGabriel.\u003c/p\u003e\n\u003chr\u003e\n\u003ch3 id=\"addendum\"\u003eAddendum\u003c/h3\u003e\n\u003cp\u003eWell, apparently someone shared this blog-post on \u003ca href=\"https://news.ycombinator.com/item?id=41558865\"\u003eHacker News\u003c/a\u003e and the metrics have blown up, so this was actually a great test for the Plausible server, thank you all! 🙏\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/hn-metrics.png\" alt=\"hacker-news\"\u003e\u003c/p\u003e\n","date":"2024-09-15","dateFormatted":"2024.09.15","excerpt":"Introduction The folks at Garnix have done it again! You can now deploy a NixOS server in minutes, just by defining your system on a flake.nix and git …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/nixos-server/","readingTime":5,"slug":"nixos-server","subtitle":null,"summary":"\u003ch2 id=\"introduction\"\u003eIntroduction\u003c/h2\u003e\n\u003cp\u003eThe folks at \u003ca href=\"https://garnix.io\"\u003eGarnix\u003c/a\u003e have done it again! You can now deploy a \u003ca href=\"https://nixos.org/\"\u003eNixOS server\u003c/a\u003e in minutes, just by defining your system on a \u003ccode\u003eflake.nix\u003c/code\u003e and \u003ccode\u003egit push\u003c/code\u003e\u0026lsquo;ing your changes (Garnix needs to be enabled for the repo).\u003c/p\u003e\n\u003cp\u003eIn their latest blog post \u003ca href=\"https://garnix.io/blog/hosting-nixos\"\u003eHands-on NixOS servers\u003c/a\u003e, they explain all the necessary steps to deploy your own server, so I won\u0026rsquo;t repeat things over. Furthermore, there\u0026rsquo;s \u003ca href=\"https://garnix.io/docs/hosting\"\u003eofficial documentation\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eInstead, I will explain how I used this opportunity to set up a web analytics server for this blog.\u003c/p\u003e","tags":["nix","nixos","flakes","garnix","hosting"],"title":"NixOS server up in minutes!","url":"https://gvolpe.com/blog/nixos-server/","wordCount":953},{"comments":true,"content":"\u003cp\u003eFlake schemas were \u003ca href=\"https://determinate.systems/posts/flake-schemas/\"\u003eintroduced by Determinate Systems\u003c/a\u003e (written by \u003ca href=\"https://github.com/edolstra\"\u003eEelco Dolstra\u003c/a\u003e \u0026mdash; creator of Nix) about half a year ago now (August 2023), which is quite a promising feature, but what is the current status? Is it usable yet?\u003c/p\u003e\n\u003cp\u003eThe \u003ca href=\"https://github.com/NixOS/nix/pull/8892\"\u003ePR\u003c/a\u003e that introduces flake schemas was submitted to \u003ccode\u003eNixOS/nix\u003c/code\u003e the same day the announcement was made, and it sadly remains in DRAFT mode until today. However, if you look at the comments, some expert users reported using this feature successfully, so I thought it was about time to give it a shot myself.\u003c/p\u003e\n\u003cp\u003eSo here\u0026rsquo;s where the fun starts: in order to use it, we need \u003cem\u003ethat\u003c/em\u003e specific \u003ccode\u003enix\u003c/code\u003e client version (from the PR) with flake schemas support, which fortunately for us, is conveniently provided on \u003ca href=\"https://github.com/DeterminateSystems/nix-src/tree/flake-schemas\"\u003ethis flake\u003c/a\u003e.\u003c/p\u003e\n\u003ch2 id=\"sneak-preview\"\u003eSneak preview\u003c/h2\u003e\n\u003cp\u003eBefore we look into the shenanigans we need to get through to get this working, let\u0026rsquo;s appreciate for a second what it brings to the table.\u003c/p\u003e\n\u003cp\u003eShown below is a side-by-side comparison of \u003ccode\u003enix flake show\u003c/code\u003e\u0026rsquo;s output on my \u003ca href=\"https://github.com/gvolpe/nix-config\"\u003eNixOS configuration\u003c/a\u003e flake:\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/flake-show-schemas.png\" alt=\"schemas\"\u003e\u003c/p\u003e\n\u003cp\u003eNeat! Don\u0026rsquo;t you think? I was irked by the usual \u003ccode\u003ehomeConfigurations: unknown\u003c/code\u003e output.\u003c/p\u003e\n\u003ch2 id=\"making-it-work\"\u003eMaking it work\u003c/h2\u003e\n\u003cp\u003eOne would need to add \u003ca href=\"https://github.com/DeterminateSystems/flake-schemas\"\u003eDeterminateSystems/flake-schemas\u003c/a\u003e as an input to \u003ccode\u003eflake.nix\u003c/code\u003e, but you\u0026rsquo;ll find there are a few useful open PRs since last year that solve real problems, but pretty much like the entire feature, they also seem inactive. So to try this out, I\u0026rsquo;ve made \u003ca href=\"https://github.com/gvolpe/flake-schemas\"\u003ea fork\u003c/a\u003e and applied some of those patches myself.\u003c/p\u003e\n\u003cp\u003eWith that out of the way, we can now define the schema inputs in our flake:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  inputs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    nixpkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eurl \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;nixpkgs/nixos-unstable\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    flake-schemas\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eurl \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;github:gvolpe/flake-schemas\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e## nix client with schema support: see https://github.com/NixOS/nix/pull/8892\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    nix-schema \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      inputs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eflake-schemas\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efollows \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;flake-schemas\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      url \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;github:DeterminateSystems/nix-src/flake-schemas\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAnd as outputs, we define the standard schemas plus a custom one we\u0026rsquo;ll see soon:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  schemas \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    inputs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eflake-schemas\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eschemas \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e./lib/schemas.nix\u003c/span\u003e { \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e (inputs) flake-schemas; };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAs pointed out by Greg in \u003ca href=\"https://github.com/DeterminateSystems/flake-schemas/issues/14\"\u003eone of the open issues\u003c/a\u003e, the \u003ccode\u003enix\u003c/code\u003e client from the fork doesn\u0026rsquo;t build straight away, so we need some tweaks that the following overlay takes care of:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ef: p: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  nix-schema \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e inputs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enix-schema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epackages\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003esystem\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enix\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eoverrideAttrs (old: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    doCheck \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    doInstallCheck \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    postInstall \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e old\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epostInstall \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e      rm $out/bin/nix-*\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e      mv $out/bin/nix $out/bin/nix-schema\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    \u0026#39;\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  });\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e};\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThen we can add it to our system packages, conveniently named \u003ccode\u003enix-schema\u003c/code\u003e to avoid conflicts:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  environment\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esystemPackages \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enix-schema\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003eNOTE\u003c/strong\u003e: the \u003ccode\u003erm $out/bin/nix-*\u003c/code\u003e bit ensures that all the usual commands such as \u003ccode\u003enix-build\u003c/code\u003e, \u003ccode\u003enix-shell\u003c/code\u003e, etc don\u0026rsquo;t get overridden, which is \u003ca href=\"https://discourse.nixos.org/t/the-program-nix-build-is-not-in-your-path/42943\"\u003eone of the issues I got myself into\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eNext we have the \u003ccode\u003elib/schemas.nix\u003c/code\u003e file referenced above, which defines the schema for the \u003ccode\u003eout\u003c/code\u003e custom output in my \u003ccode\u003enix-config\u003c/code\u003e repo, which is a rather simple example of a flake schema:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ flake-schemas }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  out \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    version \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    doc \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Exports custom attrsets...\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    inventory \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e output:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      flake-schemas\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkChildren (builtins\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emapAttrs\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        (_: _: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          what \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;custom instance to be used by consumers of this flake\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        })\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        output);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eExplaining how flake schemas are actually defined is out of the scope of this post, but the meaning of \u0026ldquo;inventory\u0026rdquo;, \u0026ldquo;children\u0026rdquo; and other fields is briefly documented in the linked nix PR.\u003c/p\u003e\n\u003cp\u003eMoreover, the \u003ccode\u003enix\u003c/code\u003e client is handily exposed as a flake app:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  apps\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003esystem\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;nix\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;app\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    program \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003epkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enix-schema\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/nix-schema\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eSo we can run it directly from this flake without installing the special \u003ccode\u003enix\u003c/code\u003e version.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix run github:gvolpe/nix-config#nix -- --version\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enix-schema \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eNix\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e 2.21.0pre20240311_d76e5fb\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"give-it-a-try-linux-only\"\u003eGive it a try (Linux only)\u003c/h3\u003e\n\u003cp\u003eYou can run the following command in any of your flake repositories to see the new \u0026ldquo;show\u0026rdquo; output:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix run github:gvolpe/nix-config#nix -- flake show\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eNotice that the \u003ccode\u003enix\u003c/code\u003e client exposed on my flake is only compiled for Linux; though, you can follow the same steps and build it for other systems in the same fashion.\u003c/p\u003e\n\u003cp\u003eOne thing I noticed is that if your flake doesn\u0026rsquo;t explicitly define \u003ccode\u003eflake-schemas\u003c/code\u003e as an input, then \u003ccode\u003ehomeManagerConfigurations\u003c/code\u003e is not properly detected, but it could be something wrong with \u003ca href=\"https://github.com/gvolpe/flake-schemas/blob/7d762079449d0ca63e92b0128c885021168c0c79/flake.nix#L242\"\u003emy implementation\u003c/a\u003e too, no idea (if you know what\u0026rsquo;s wrong, please let me know!).\u003c/p\u003e\n\u003cp\u003eAnother caveat of using the new command is that it\u0026rsquo;s quite slow, as it seems to evaluate all outputs. For instance, it took about \u003cstrong\u003e~33.7 seconds\u003c/strong\u003e to evaluate my \u003ccode\u003enix-config\u003c/code\u003e flake the first time, as shown in \u003ca href=\"https://github.com/gvolpe/nix-config/pull/254#issuecomment-2042471639\"\u003ethis PR comment\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eFurthermore, there are a few schemas like \u003ccode\u003ehomeModules\u003c/code\u003e and \u003ccode\u003enixosModules\u003c/code\u003e that are unsupported at the moment, but that\u0026rsquo;s just a matter of adding the right schemas in place.\u003c/p\u003e\n\u003ch2 id=\"final-thoughts\"\u003eFinal thoughts\u003c/h2\u003e\n\u003cp\u003eI love this proposal and I wish it moves forward soon, so that every Nix user out there gets to enjoy it! However, considering how long it has been since its announcement, I\u0026rsquo;m not sure what to think.\u003c/p\u003e\n\u003cp\u003eFrankly, I find the inactivity in the main \u003ccode\u003eflake-schemas\u003c/code\u003e repository as well as in the initial \u003ccode\u003enix\u003c/code\u003e pull request a bit worrying, but I\u0026rsquo;m hoping it all turns out in a positive way.\u003c/p\u003e\n\u003cp\u003eHave you tried flake schemas yet? What was your experience like?\u003c/p\u003e\n\u003cp\u003eBest,\nGabriel.\u003c/p\u003e\n","date":"2024-04-09","dateFormatted":"2024.04.09","excerpt":"Flake schemas were introduced by Determinate Systems (written by Eelco Dolstra \u0026mdash; creator of Nix) about half a year ago now (August 2023), which is quite a …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/flake-schemas/","readingTime":4,"slug":"flake-schemas","subtitle":null,"summary":"\u003cp\u003eFlake schemas were \u003ca href=\"https://determinate.systems/posts/flake-schemas/\"\u003eintroduced by Determinate Systems\u003c/a\u003e (written by \u003ca href=\"https://github.com/edolstra\"\u003eEelco Dolstra\u003c/a\u003e \u0026mdash; creator of Nix) about half a year ago now (August 2023), which is quite a promising feature, but what is the current status? Is it usable yet?\u003c/p\u003e\n\u003cp\u003eThe \u003ca href=\"https://github.com/NixOS/nix/pull/8892\"\u003ePR\u003c/a\u003e that introduces flake schemas was submitted to \u003ccode\u003eNixOS/nix\u003c/code\u003e the same day the announcement was made, and it sadly remains in DRAFT mode until today. However, if you look at the comments, some expert users reported using this feature successfully, so I thought it was about time to give it a shot myself.\u003c/p\u003e","tags":["nix","nixos","flakes"],"title":"Flake Schemas","url":"https://gvolpe.com/blog/flake-schemas/","wordCount":836},{"comments":true,"content":"\u003ch2 id=\"introduction\"\u003eIntroduction\u003c/h2\u003e\n\u003cp\u003eI blogged about \u003ca href=\"../garnix\"\u003eGarnix\u003c/a\u003e before, and it\u0026rsquo;s the solution I\u0026rsquo;ve been using ever since for continuous integration. However, one thing I was missing was that private flakes were unsupported, so I had to live with a private local flake that I was commenting out on my public flake for a long time.\u003c/p\u003e\n\u003cp\u003eGithub Actions does support building private flakes by providing an access token, but it\u0026rsquo;s not very secure if your builds end up on a public cache anyway (not my case, though)\u0026hellip; Garnix recently added \u003ca href=\"https://garnix.io/docs/private_inputs\"\u003esupport for private inputs\u003c/a\u003e, but it comes with some limitations (mainly due to security), so I could not take advantage of this new feature.\u003c/p\u003e\n\u003cp\u003eUnhappy with my current situation, and armed with some extra motivation, I decided to take a completely different approach\u0026hellip; Also, I realized I hadn\u0026rsquo;t blogged in more than year! Time to fix it.\u003c/p\u003e\n\u003ch3 id=\"former-settings\"\u003eFormer settings\u003c/h3\u003e\n\u003cp\u003eThe flake I was commenting out on my public configuration only exposed an overlay, and looked as follows on my inputs:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprivate-flake \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  url \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003egithub:gvolpe/private-flake\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e};\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThen I would add the private overlay only when the private flake was present in my inputs, so it could also build on CI.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprivateOverlay \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (builtins\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehasAttr \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;private-flake\u0026#34;\u003c/span\u003e inputs)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003ethen\u003c/span\u003e private-flake\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eoverlays\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edefault\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e (f: p: { });\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt served its purpose, but I got tired of git-stashing these changes every time I had to update my flake.\u003c/p\u003e\n\u003ch3 id=\"rethinking-public-flake\"\u003eRethinking public flake\u003c/h3\u003e\n\u003cp\u003eThe solution was clear: \u003cem\u003einvert the dependency graph\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eInstead of pulling my private flake\u0026rsquo;s overlays into my public configuration, why not doing it the other way around? To be frank, this is something I\u0026rsquo;ve thought of many times, but I was always too lazy to do anything about it ¯\\_(ツ)_/¯\u003c/p\u003e\n\u003cp\u003eStrangely enough, not this time around, so I was ready to tackle it once and for all.\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s what my public flake\u0026rsquo;s outputs look like before:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  homeConfigurations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e./outputs/home-conf.nix\u003c/span\u003e { \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e inputs system pkgs extraArgs; };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  nixosConfigurations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e./outputs/nixos-conf.nix\u003c/span\u003e { \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e inputs system pkgs extraArgs; };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eI wanted my private flake to be able to build these configurations with a custom \u003ccode\u003epkgs\u003c/code\u003e instance, so the first step was to refactor these and make them builder methods on my main overlay.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  buildersOverlay \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e f: p: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    mkHomeConfigurations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e { pkgs \u003cspan style=\"color:#f92672\"\u003e?\u003c/span\u003e f }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e../outputs/home-conf.nix\u003c/span\u003e { \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e inputs pkgs system; };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    mkNixosConfigurations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e { pkgs \u003cspan style=\"color:#f92672\"\u003e?\u003c/span\u003e f }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e../outputs/nixos-conf.nix\u003c/span\u003e { \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e inputs pkgs system; };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAnd here\u0026rsquo;s how my public flake defines its outputs right now (simple enough, no?):\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  outputs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e inputs:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      system \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;x86_64-linux\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      overlays \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e./lib/overlays.nix\u003c/span\u003e { \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e inputs system; };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      pkgs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e inputs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enixpkgs {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e overlays system;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eallowUnfree \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e pkgs overlays;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      homeConfigurations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkHomeConfigurations { };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      nixosConfigurations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkNixosConfigurations { };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt can still be built independently with sane defaults, but it\u0026rsquo;s also extendable from other flakes.\u003c/p\u003e\n\u003ch3 id=\"private-flake\"\u003ePrivate flake\u003c/h3\u003e\n\u003cp\u003eRefactor done on the public side, now it was time to focus on the private flake. The entire \u003ccode\u003eflake.nix\u003c/code\u003e definition is only 17 lines long! Neat, don\u0026rsquo;t you think?\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;My private flake\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  inputs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enix-config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eurl \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003egithub:gvolpe/nix-config\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  outputs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e { nix-config\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      pkgs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e nix-config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eextend (\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003emodules/overlay.nix\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      homeConfigurations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkHomeConfigurations { };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      nixosConfigurations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkNixosConfigurations { };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eBoth Home Manager and NixOS configurations can be built by reusing the public config\u0026rsquo;s \u003ccode\u003epkgs\u003c/code\u003e instance, which is extended with a custom overlay.\u003c/p\u003e\n\u003cp\u003eThe custom overlay used to contain only private stuff, but now it also contains a secrets override (more on this below):\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ef: p: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  secrets \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e p\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecallPackage \u003cspan style=\"color:#e6db74\"\u003e./secrets.nix\u003c/span\u003e { };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  private\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estuff \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e./private-stuff.nix\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eI realized that my system was building, but that all my secrets were not present! \\_(o_o)_/\u003c/p\u003e\n\u003cp\u003eThis was the case because I use \u003ccode\u003egit-crypt\u003c/code\u003e on my public repo to encrypt them; one more obstacle on the road to finally nail this\u0026hellip;\u003c/p\u003e\n\u003cp\u003eThe next step was to refactor all my public encrypted secrets and centralize them in a single overlay, so here\u0026rsquo;s how I define them now:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  githubToken \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;OVERRIDE_ME\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  mimeoAssociations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ngrokToken \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;OVERRIDE_ME\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  openaiApiKey \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;OVERRIDE_ME\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis means I no longer need \u003ccode\u003egit-crypt\u003c/code\u003e in my public repo, but it \u003ca href=\"https://gist.github.com/marcopaganini/62fc51a679f8985c10c3ca5d0c84031c\"\u003edoesn\u0026rsquo;t\u003c/a\u003e seem \u003ca href=\"https://github.com/AGWA/git-crypt/issues/170\"\u003etrivial\u003c/a\u003e to \u003ca href=\"https://github.com/AGWA/git-crypt/issues/137\"\u003eremove it\u003c/a\u003e, so I\u0026rsquo;ll get to that another day 🙄\u003c/p\u003e\n\u003cp\u003eThus, all I have to do in my private flake is to override all these secrets in the custom overlay. Here\u0026rsquo;s the \u003ccode\u003esecrets.nix\u003c/code\u003e file you can see referenced in the overlay above \u0026mdash; only showing a single secret for brevity.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ lib\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  githubToken \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esecretManager {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    filepath \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e../secrets/github-token\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    fileAction \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ereadFile;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    encryptedSha256 \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ce08397723eb2c8ae9673de34fab11db8799062c2659c673b4eddbfa4e05b8e1\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    emptyValue \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eI continue to use the \u003ca href=\"https://github.com/gvolpe/nix-config/blob/a1db9d16c403cad14a0ec98c6fc55b491920806d/lib/default.nix#L23\"\u003esecretsManager\u003c/a\u003e function from my public flake, which also depends on the file being encrypted with \u003ccode\u003egit-crypt\u003c/code\u003e. This is how my secrets don\u0026rsquo;t end up on a public cache.\u003c/p\u003e\n\u003cp\u003eThus, I had to move all my secrets from the public repo to my private one, and set up \u003ccode\u003egit-crypt\u003c/code\u003e once again. Though, that\u0026rsquo;s a one-time thing and I\u0026rsquo;m much happier they now live in a private repository instead.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eREPL superpower\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eTo quickly debug if the secrets are being set as expected, you can\u0026rsquo;t beat the REPL.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix repl\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eWelcome to Nix 2.20.3. Type :? \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e help.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enix-repl\u0026gt; :lf .\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAdded \u003cspan style=\"color:#ae81ff\"\u003e15\u003c/span\u003e variables.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enix-repl\u0026gt; inputs.nix-config.pkgs.secrets.githubToken\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;OVERRIDE_ME\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enix-repl\u0026gt; outputs.homeConfigurations.gvolpe-hdmi.pkgs.secrets.githubToken\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;this-secret-has-been-set-in-the-overlay\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enix-repl\u0026gt; outputs.nixosConfigurations.tongfang-amd.pkgs.secrets.githubToken\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;this-secret-has-been-set-in-the-overlay\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eI absolutely love this extra capability nix gives us! It feels like a superpower 🥷\u003c/p\u003e\n\u003ch3 id=\"continuous-integration\"\u003eContinuous integration\u003c/h3\u003e\n\u003cp\u003eWith Garnix now supporting private flakes, I just needed to ensure the Garnix App had access to my private repo, as well as setting the right options on my \u003ccode\u003egarnix.yaml\u003c/code\u003e file.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-yaml\" data-lang=\"yaml\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eenableGithubOrgAccessTokens\u003c/span\u003e: \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003ebuilds\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003eexclude\u003c/span\u003e: []\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003einclude\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e - \u003cspan style=\"color:#ae81ff\"\u003ehomeConfigurations.*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e - \u003cspan style=\"color:#ae81ff\"\u003enixosConfigurations.*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eOnce it was all set, I had my private flake successfully building my Home Manager and NixOS configurations on Garnix in no time!\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/garnix-private-build.png\" alt=\"summary\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://garnix.io/\"\u003eGarnix\u003c/a\u003e rocks! Now even more with a revamped website and UI :)\u003c/p\u003e\n\u003ch3 id=\"closing-remarks\"\u003eClosing remarks\u003c/h3\u003e\n\u003cp\u003eI must say I\u0026rsquo;m quite satisfied with my current solution, but we all know this is an eternal tinkering process, so I won\u0026rsquo;t lie to you (and to myself) by claiming this is its final form :)\u003c/p\u003e\n\u003cp\u003ePerhaps, the only area that could use some improvement would be the management of secrets, but I don\u0026rsquo;t have a big need for it as my secrets are trivial and I don\u0026rsquo;t do remote server deployments and provisioning, so for now that\u0026rsquo;s not something I\u0026rsquo;m willing to spend time on.\u003c/p\u003e\n\u003cp\u003eWhat do you think? Anything else you would improve or do differently? Let me know in the comments below!\u003c/p\u003e\n\u003cp\u003eBest,\nGabriel.\u003c/p\u003e\n","date":"2024-02-27","dateFormatted":"2024.02.27","excerpt":"Introduction I blogged about Garnix before, and it\u0026rsquo;s the solution I\u0026rsquo;ve been using ever since for continuous integration. However, one thing I was …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/private-flake/","readingTime":6,"slug":"private-flake","subtitle":null,"summary":"\u003ch2 id=\"introduction\"\u003eIntroduction\u003c/h2\u003e\n\u003cp\u003eI blogged about \u003ca href=\"../garnix\"\u003eGarnix\u003c/a\u003e before, and it\u0026rsquo;s the solution I\u0026rsquo;ve been using ever since for continuous integration. However, one thing I was missing was that private flakes were unsupported, so I had to live with a private local flake that I was commenting out on my public flake for a long time.\u003c/p\u003e\n\u003cp\u003eGithub Actions does support building private flakes by providing an access token, but it\u0026rsquo;s not very secure if your builds end up on a public cache anyway (not my case, though)\u0026hellip; Garnix recently added \u003ca href=\"https://garnix.io/docs/private_inputs\"\u003esupport for private inputs\u003c/a\u003e, but it comes with some limitations (mainly due to security), so I could not take advantage of this new feature.\u003c/p\u003e","tags":["nix","nixos","flakes","ci","garnix"],"title":"Private Nix flake 🔒","url":"https://gvolpe.com/blog/private-flake/","wordCount":1095},{"comments":true,"content":"\u003cp\u003eRemote builds enable interesting use cases and experiments. Besides building for different architectures, another use case that comes to mind would be having a low-resource machine building a derivation that would require heavy CPU usage (e.g. a Rust application) on the fly, without having to rely on CI builds or binary caches, effectively used as a development environment.\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s what the \u003ca href=\"https://nixos.org/manual/nix/stable/advanced-topics/distributed-builds.html\"\u003eNixOS official documentation\u003c/a\u003e has to say on the topic:\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '📜';\"\u003e\n  \u003ch4\u003eRemote Builds\u003c/h4\u003e\n  \u003cp\u003e\n\"Nix supports remote builds, where a local Nix installation can forward Nix builds to other machines. This allows multiple builds to be performed in parallel and allows Nix to perform multi-platform builds in a semi-transparent way. For instance, if you perform a build for a \u003ccode\u003e86_64-darwin\u003c/code\u003e on an \u003ccode\u003ei686-linux\u003c/code\u003e machine, Nix can automatically forward the build to a \u003ccode\u003ex86_64-darwin\u003c/code\u003e machine, if available.\"\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cp\u003eRemote builds enable interesting use cases and experiments. Besides building for different architectures, another use case that comes to mind would be having a low-resource machine building a derivation that would require heavy CPU usage (e.g. a Rust application) on the fly, without having to rely on CI builds or binary caches, effectively used as a development environment.\u003c/p\u003e\n\u003cp\u003eWhile this may sound too good to be truth, not everyone out there has a powerful remote machine to connect to. Moreover, we may want to give remote builds a try to find out whether it could help us solve our problems before considering purchasing a new computer. To fill this gap (and much more!), let me introduce you to Nixbuild.\u003c/p\u003e\n\u003ch3 id=\"nixbuildnet\"\u003eNixbuild.net\u003c/h3\u003e\n\u003cp\u003eIn my \u003ca href=\"../garnix\"\u003eprevious post\u003c/a\u003e, I promised I would write a follow-up post about \u003ca href=\"https://nixbuild.net/\"\u003eNixbuild\u003c/a\u003e, officially promoted as follows:\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '📜';\"\u003e\n  \u003ch4\u003eOfficial\u003c/h4\u003e\n  \u003cp\u003e\n\"The one-stop solution for efficient builds, smart caching and ultra-fast CI pipelines. Scales by default, pay-per-use, no vendor lock-in. Supports x86 and Arm builds.\"\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cp\u003eIf you scroll down a bit on their site, you\u0026rsquo;ll find the two fields required to sign up for a free trial: an email and an SSH key. Once you get the email to activate your account, you should head over to the \u003ca href=\"https://docs.nixbuild.net/getting-started/\"\u003eGetting started\u003c/a\u003e section of the documentation.\u003c/p\u003e\n\u003cp\u003eThe free account includes 25 free CPU hours of build time at the moment of writing, which should be more than enough to evaluate whether this could be a good fit for us. Or even to just experiment (and have fun!) with it and learn about remote builds :)\u003c/p\u003e\n\u003cp\u003eTo configure a NixOS machine to run remote builds, we need to do exactly what the documentation says:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  programs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003essh\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eextraConfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    Host eu.nixbuild.net\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e      PubkeyAcceptedKeyTypes ssh-ed25519\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e      IdentityFile /home/user/.ssh/my-nixbuild-key\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e  \u0026#39;\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  programs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003essh\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eknownHosts \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    nixbuild \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      hostNames \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;eu.nixbuild.net\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      publicKey \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPIQCZc54poJ8vqawd8TraNryQeJnvH1eLpIDgbiqymM\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  nix \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    distributedBuilds \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    buildMachines \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        hostName \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;eu.nixbuild.net\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        system \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;x86_64-linux\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        maxJobs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e100\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        supportedFeatures \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;benchmark\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;big-parallel\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eMake sure to replace \u003ccode\u003eIdentityFile\u003c/code\u003e with the path to your SSH key. That\u0026rsquo;s everything that\u0026rsquo;s needed, as this \u003ca href=\"https://github.com/gvolpe/nix-config/pull/166/files\"\u003epull request\u003c/a\u003e on my repo demonstrates.\u003c/p\u003e\n\u003ch3 id=\"ci-builds\"\u003eCI builds\u003c/h3\u003e\n\u003cp\u003eContinuing with my \u003ca href=\"https://github.com/gvolpe/nix-config\"\u003eNixOS configuration\u003c/a\u003e as example, we can learn how to leverage Nixbuild as a CI service by using \u003ca href=\"https://github.com/nixbuild/nixbuild-action\"\u003enixbuild-action\u003c/a\u003e. Here are the outputs of my Nix flake.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix flake show --allow-import-from-derivation\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egit+file:///home/gvolpe/workspace/nix-config?ref\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e├───checks\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   └───x86_64-linux\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│       ├───dell-xps: derivation \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;nixos-system-dell-xps-15-9560-23.05.20230202.a100acd\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│       ├───gvolpe-edp: derivation \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;home-manager-generation\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│       ├───gvolpe-hdmi: derivation \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;home-manager-generation\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│       └───tongfang-amd: derivation \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;nixos-system-tongfang-amd-23.05.20230202.a100acd\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e├───homeConfigurations: unknown\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e├───nixosConfigurations\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   ├───dell-xps: NixOS configuration\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   └───tongfang-amd: NixOS configuration\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e└───packages\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    └───x86_64-linux\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ├───metals: package \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;metals-0.11.10+117-476976c1-SNAPSHOT\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        └───metals-updater: package \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;metals-updater-script\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eTo build all the \u003ccode\u003epackages\u003c/code\u003e and \u003ccode\u003echecks\u003c/code\u003e, we need the following \u003ccode\u003e.github/workflows/nixbuild.yml\u003c/code\u003e configuration:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-yaml\" data-lang=\"yaml\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003ename\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003eNixbuild demo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eon\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003epull_request\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003ejobs\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003ebuild\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003euses\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003enixbuild/nixbuild-action/.github/workflows/ci-workflow.yml@master\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003esecrets\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003enixbuild_ssh_key\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e${ { secrets.NIXBUILD_SSH_KEY }}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003ewith\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003enix_conf\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;allow-import-from-derivation = true\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003efilter_builds\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;.top_attr == \u0026#34;packages\u0026#34; or .top_attr == \u0026#34;checks\u0026#34;\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt expects the \u003ccode\u003eNIXBUILD_SSH_KEY\u003c/code\u003e environment variable to be set with your private SSH key value used to sign up for Nixbuild. Note that both \u003ccode\u003ehomeConfigurations\u003c/code\u003e and \u003ccode\u003enixosConfigurations\u003c/code\u003e are exposed as checks, so these will also be built, resulting in the following workflow.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/nixbuild-jobs.png\" alt=\"jobs\"\u003e\u003c/p\u003e\n\u003cp\u003eOnce it finishes, it may produce some artifacts.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/nixbuild-artifacts.png\" alt=\"artifacts\"\u003e\u003c/p\u003e\n\u003cp\u003eAs well as a final summary.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/nixbuild-summary.png\" alt=\"summary\"\u003e\u003c/p\u003e\n\u003cp\u003eI found the summaries to be extremely helpful understanding what was built on the remote machine, which can also help us understand costs.\u003c/p\u003e\n\u003ch3 id=\"final-words\"\u003eFinal words\u003c/h3\u003e\n\u003cp\u003eI gave Nixbuild a try mainly to learn about remote builds and evaluate their CI service mainly because of curiosity. However, I don\u0026rsquo;t have a use case for it besides building my NixOS configuration, for which I find a CI build + a binary cache to be sufficient, but who knows? I may have a more exciting use case for it someday.\u003c/p\u003e\n\u003cp\u003eTalking specifically about CI builds, I found Garnix to be much faster compared to the free trial of Nixbuild, thus it will continue to be my daily driver. Nevertheless, Nixbuild is such a cool project and it\u0026rsquo;s much more than a CI service, so I definitely encourage everyone to have a look at their extensive documentation and give it a try.\u003c/p\u003e\n\u003cp\u003eBest,\nGabriel.\u003c/p\u003e\n","date":"2023-02-13","dateFormatted":"2023.02.13","excerpt":"Remote builds enable interesting use cases and experiments. Besides building for different architectures, another use case that comes to mind would be having a …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/nix-remote-builds/","readingTime":4,"slug":"nix-remote-builds","subtitle":null,"summary":"\u003cp\u003eRemote builds enable interesting use cases and experiments. Besides building for different architectures, another use case that comes to mind would be having a low-resource machine building a derivation that would require heavy CPU usage (e.g. a Rust application) on the fly, without having to rely on CI builds or binary caches, effectively used as a development environment.\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s what the \u003ca href=\"https://nixos.org/manual/nix/stable/advanced-topics/distributed-builds.html\"\u003eNixOS official documentation\u003c/a\u003e has to say on the topic:\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '📜';\"\u003e\n  \u003ch4\u003eRemote Builds\u003c/h4\u003e\n  \u003cp\u003e\n\"Nix supports remote builds, where a local Nix installation can forward Nix builds to other machines. This allows multiple builds to be performed in parallel and allows Nix to perform multi-platform builds in a semi-transparent way. For instance, if you perform a build for a \u003ccode\u003e86_64-darwin\u003c/code\u003e on an \u003ccode\u003ei686-linux\u003c/code\u003e machine, Nix can automatically forward the build to a \u003ccode\u003ex86_64-darwin\u003c/code\u003e machine, if available.\"\n\u003c/p\u003e","tags":["nix","nixos","flakes","ci","garnix","cachix","nixbuild"],"title":"Nix remote builds","url":"https://gvolpe.com/blog/nix-remote-builds/","wordCount":816},{"comments":true,"content":"\u003cp\u003e\u003ca href=\"https://garnix.io/\"\u003eGarnix\u003c/a\u003e is a continuous integration (CI) service for Nix flakes that has been in Beta for a while.\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '📜';\"\u003e\n  \u003ch4\u003eIn their own words\u003c/h4\u003e\n  \u003cp\u003e\n\"With Nix-specific optimizations, Garnix makes CI with Nix fast — a few seconds for no-op changes. Easy to setup, too — if you have a flake.nix, you are ready to go. And we provide the build results as a cache, so you don't have to build things locally. To top it all up, we run on 100% renewable energy. Because CI should bring you peace of mind.\"\nNotice the extensions we had to enable to make this compile.\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cp\u003eMy \u003ca href=\"https://github.com/settings/installations\"\u003eGithub settings\u003c/a\u003e tell me I have installed the Garnix CI app in March 2022 and it\u0026rsquo;s been running alongside my GHA (Github Actions) workflows ever since. However, I haven\u0026rsquo;t really reaped its benefits until now.\u003c/p\u003e\n\u003cp\u003eOnce you install the Garnix CI app in your Github account and enable the repositories where you want it to run, you\u0026rsquo;ll notice that it will automatically detect a \u003ccode\u003eflake.nix\u003c/code\u003e and it will evaluate its packages, dev shells, checks and NixOS configurations. Still, this is all configurable per repository via a \u003ca href=\"https://github.com/gvolpe/nix-config/blob/master/garnix.yaml\"\u003egarnix.yaml\u003c/a\u003e file.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/garnix-app.png\" alt=\"app\"\u003e\u003c/p\u003e\n\u003cp\u003eThe \u003ccode\u003ehomeConfigurations\u003c/code\u003e output is \u003ca href=\"https://github.com/garnix-io/issues/issues/24\"\u003enot yet supported\u003c/a\u003e, but it\u0026rsquo;s in the plans. A current workaround is to expose these as \u003ccode\u003epackages\u003c/code\u003e or \u003ccode\u003echecks\u003c/code\u003e instead, as I have in my flake\u0026rsquo;s outputs.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  outputs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e inputs:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      system \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;x86_64-linux\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      ci \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e./outputs/ci.nix\u003c/span\u003e { \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e inputs system; };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e (inputs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enixpkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elib) mapAttrs;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003erec\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      homeConfigurations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e./outputs/home-conf.nix\u003c/span\u003e { \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e inputs system; };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      nixosConfigurations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e./outputs/nixos-conf.nix\u003c/span\u003e { \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e inputs system; };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      packages\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003esystem\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e (ci) metals metals-updater;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      checks\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003esystem\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          os \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e mapAttrs (_: c: c\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003econfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esystem\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ebuild\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etoplevel) nixosConfigurations;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          hm \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e mapAttrs (_: c: c\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eactivationPackage) homeConfigurations;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        os \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e hm;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe downside of doing this is that building the Home Manager configurations requires \u003ca href=\"https://nixos.wiki/wiki/Import_From_Derivation\"\u003eImport From Derivation (IFD)\u003c/a\u003e (at least in my case) even for \u003ccode\u003enix flake show\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix flake show --allow-import-from-derivation\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egit+file:///home/gvolpe/workspace/nix-config?ref\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e├───checks\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   └───x86_64-linux\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│       ├───dell-xps: derivation \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;nixos-system-dell-xps-15-9560-23.05.20230202.a100acd\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│       ├───gvolpe-edp: derivation \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;home-manager-generation\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│       ├───gvolpe-hdmi: derivation \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;home-manager-generation\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│       └───tongfang-amd: derivation \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;nixos-system-tongfang-amd-23.05.20230202.a100acd\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e├───homeConfigurations: unknown\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e├───nixosConfigurations\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   ├───dell-xps: NixOS configuration\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   └───tongfang-amd: NixOS configuration\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e└───packages\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    └───x86_64-linux\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ├───metals: package \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;metals-0.11.10+117-476976c1-SNAPSHOT\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        └───metals-updater: package \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;metals-updater-script\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eNevertheless, I believe the pros greatly outweigh the cons.\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s the \u003ca href=\"https://github.com/gvolpe/nix-config/pull/162/files\"\u003epull request\u003c/a\u003e I made to completely remove the GHA workflows for my configuration (including Cachix) and use Garnix instead. The build times are insanely fast!\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/garnix-actions.png\" alt=\"actions\"\u003e\u003c/p\u003e\n\u003cp\u003eBuilding my Home Manager configuration used to take about \u003cstrong\u003e7m 30s\u003c/strong\u003e with my previous setup. With Garnix, it built in \u003cstrong\u003e1m 45s\u003c/strong\u003e the first time, but it only took about \u003cstrong\u003e26s\u003c/strong\u003e in subsequent runs by leveraging its binary cache!\u003c/p\u003e\n\u003cp\u003eThe dashboard is quite minimalistic and could use some improvements, but the service is still in Beta :)\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/garnix-home.png\" alt=\"home\"\u003e\u003c/p\u003e\n\u003cp\u003eIf we click on the status of any build, we can see the logs whenever available.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/garnix-logs.png\" alt=\"logs\"\u003e\u003c/p\u003e\n\u003cp\u003eThere are a few tickets raised in their \u003ca href=\"https://github.com/garnix-io/issues/issues\"\u003eissue tracker\u003c/a\u003e to make it nicer and provide a better user experience, thus I believe it can only get better from now 💪\u003c/p\u003e\n\u003ch3 id=\"how-does-it-work\"\u003eHow does it work?\u003c/h3\u003e\n\u003cp\u003eHow come Garnix does so much better than using plain Nix with a binary cache? It is vaguely explained it in the \u003ca href=\"https://garnix.io/docs/steps\"\u003ewhat Garnix CI does\u003c/a\u003e page, which I understand it involves the following steps.\u003c/p\u003e\n\u003cp\u003eFirst of all, it gets all the attribute names of the supported outputs, e.g.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix eval .#packages.x86_64-linux --apply builtins.attrNames --json\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;metals\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;metals-updater\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix eval .#checks.x86_64-linux --apply builtins.attrNames --json\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;dell-xps\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gvolpe-edp\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gvolpe-hdmi\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;tongfang-amd\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eNext, it builds every attribute previously collected in \u003cem\u003eparallel\u003c/em\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix build .#metals --json\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[{\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;drvPath\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/nix/store/4kql56jcvr6q0fihw2wqq56gqpf6h9gl-metals-0.11.10+111-59a1b932-SNAPSHOT.drv\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;outputs\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;out\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/nix/store/q65x0gng6ni18hfyfbshzr910vlw28rp-metals-0.11.10+111-59a1b932-SNAPSHOT\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;startTime\u0026#34;\u003c/span\u003e:0,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;stopTime\u0026#34;\u003c/span\u003e:0\u003cspan style=\"color:#f92672\"\u003e}]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix flake check \u003cspan style=\"color:#75715e\"\u003e# don\u0026#39;t really know what happens after this\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eFinally, it uses the derivation (\u003ccode\u003edrvPath\u003c/code\u003e) of the outputs to query the nix log (or other heuristics when not available). I don\u0026rsquo;t know exactly what this means, but by running the following command:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix show-derivation /nix/store/4kql56jcvr6q0fihw2wqq56gqpf6h9gl-metals-0.11.10+111-59a1b932-SNAPSHOT.drv\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWe get the following attributes: \u003ccode\u003eargs\u003c/code\u003e, \u003ccode\u003ebuilder\u003c/code\u003e, \u003ccode\u003eenv\u003c/code\u003e, \u003ccode\u003einputDrvs\u003c/code\u003e, \u003ccode\u003einputSrcs\u003c/code\u003e, \u003ccode\u003eoutputs\u003c/code\u003e, and \u003ccode\u003esystem\u003c/code\u003e. I assume it cleverly skips anything that hasn\u0026rsquo;t changed and builds only what did change, keeping the build times as short as possible, but it\u0026rsquo;s only a guess.\u003c/p\u003e\n\u003ch3 id=\"chat-with-julian\"\u003eChat with Julian\u003c/h3\u003e\n\u003cp\u003eI reached out to the author of Garnix, \u003ca href=\"https://github.com/jkarni\"\u003eJulian Arni\u003c/a\u003e, and he kindly got back to me with a very detailed and speedy response, answering all my silly questions. Find them adapted a bit in the following sections.\u003c/p\u003e\n\u003ch4 id=\"why-is-it-faster\"\u003eWhy is it faster?\u003c/h4\u003e\n\u003cp\u003eThere are a few reasons it\u0026rsquo;s faster:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003eThe cache is already in the disk, so builds don\u0026rsquo;t need to push and pull from the cache. Pulling, in particular, tends to take quite a long time, since essentially nothing is there yet. I\u0026rsquo;ve seen 4-5 minutes of 20 minute CI runs be due to that. By default, Nix will pull even if nothing needs to be built, so even in \u0026ldquo;do nothing\u0026rdquo; runs you might end up paying that cost.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eThe cache can be shared across projects. With Cachix, each cache is kept separate, because the cache owner can push whatever they want to their cache. Garnix only allows builds that are pure by design, and that it itself has built, to enter the cache. This way, if any project or user has built the package, it\u0026rsquo;s safe to re-use those artifacts. This is a lot like the \u003ccode\u003ecache.nixos.org\u003c/code\u003e builders, and it becomes particularly helpful if packages you depend on are also using Garnix; but even more significant with forks.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eGarnix has more powerful machines than Github, and that in turn, enables higher \u003cstrong\u003eparallelism\u003c/strong\u003e. Additionally, rather than vCPUs, we use CPU shares. The idea is, if the machine is not busy, you get a really fast machine; that works well for everyone, since then your build is done sooner and the machine is again free (or free-er) for the next build. Throttling the CPU is terrible for overall latency, and the only advantages I can see are more predictable times for users, and easier implementation if you\u0026rsquo;re using bin-packing schedulers/orchestrators.\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch4 id=\"whats-the-cache-retention-policy\"\u003eWhat\u0026rsquo;s the cache retention policy?\u003c/h4\u003e\n\u003cp\u003eRegarding the cache, it just serves its own Nix store. A clear retention policy is still in the works, so relying on the cache being available longer term would be a bad idea. However, the current idea looks as follows:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e2-3 days for commits that are no longer the head of any branch.\u003c/li\u003e\n\u003cli\u003e2 weeks for the head of non-main branches\u003c/li\u003e\n\u003cli\u003e2-3 months for the head of \u003ccode\u003emain\u003c/code\u003e / \u003ccode\u003emaster\u003c/code\u003e.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eOnce this is determined, it should be properly documented.\u003c/p\u003e\n\u003ch3 id=\"alternatives\"\u003eAlternatives\u003c/h3\u003e\n\u003cp\u003eThe obvious alternative is Github Actions, which I just migrated away from. Furthermore, there\u0026rsquo;s also \u003ca href=\"https://nixbuild.net/\"\u003eNixbuild\u003c/a\u003e, which is much more than a CI service, but I plan to cover it in a follow-up blog post as I\u0026rsquo;m still experimenting with it.\u003c/p\u003e\n\u003ch3 id=\"final-words\"\u003eFinal words\u003c/h3\u003e\n\u003cp\u003eGarnix is a very promising piece of software that can save you plenty of time. If you also enjoy using it and would like to see it succeed like I do, please consider \u003ca href=\"https://opencollective.com/garnix_io\"\u003edonating\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eJulian has also acknowledged that the sign-up process is still a bit confusing, so there are plans to improve it, but honestly I can\u0026rsquo;t remember about it since I\u0026rsquo;ve signed up a year ago. If you are giving Garnix a try and find this to be the case, feel free to share your ideas to make it better in their issue tracker.\u003c/p\u003e\n\u003cp\u003eBest,\nGabriel.\u003c/p\u003e\n","date":"2023-02-07","dateFormatted":"2023.02.07","excerpt":"Garnix is a continuous integration (CI) service for Nix flakes that has been in Beta for a while.\nIn their own words \"With Nix-specific optimizations, Garnix …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/garnix/","readingTime":6,"slug":"garnix","subtitle":null,"summary":"\u003cp\u003e\u003ca href=\"https://garnix.io/\"\u003eGarnix\u003c/a\u003e is a continuous integration (CI) service for Nix flakes that has been in Beta for a while.\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '📜';\"\u003e\n  \u003ch4\u003eIn their own words\u003c/h4\u003e\n  \u003cp\u003e\n\"With Nix-specific optimizations, Garnix makes CI with Nix fast — a few seconds for no-op changes. Easy to setup, too — if you have a flake.nix, you are ready to go. And we provide the build results as a cache, so you don't have to build things locally. To top it all up, we run on 100% renewable energy. Because CI should bring you peace of mind.\"\nNotice the extensions we had to enable to make this compile.\n\u003c/p\u003e","tags":["nix","nixos","flakes","ci","garnix","cachix"],"title":"Garnix CI","url":"https://gvolpe.com/blog/garnix/","wordCount":1207},{"comments":true,"content":"\u003cp\u003eIt is no secret that \u003ca href=\"https://neovim.io/\"\u003eNeovim\u003c/a\u003e is my favorite text editor. I use it on a daily basis for multiple purposes:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eTo write Scala code (and the occasional Typescript) for \u003ccode\u003e$work\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eTo write the extensive text and code for \u003ca href=\"https://leanpub.com/u/gvolpe\"\u003emy books\u003c/a\u003e.\u003c/li\u003e\n\u003cli\u003eTo fine-tune my ever-changing \u003ca href=\"https://github.com/gvolpe/nix-config\"\u003eNixOS configuration\u003c/a\u003e.\u003c/li\u003e\n\u003cli\u003eTo write blog posts such as this one.\u003c/li\u003e\n\u003cli\u003eFor the casual Haskell and Rust code I sometimes need to write.\u003c/li\u003e\n\u003cli\u003eLiterally for any other text file I need to explore and modify.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eAll this functionality is enabled by many plugins configured and tailored to my needs. Now there exist a bunch of plugin managers to handle the installation of plugins, from the classic \u003ca href=\"https://github.com/junegunn/vim-plug\"\u003evim-plug\u003c/a\u003e to the more esoteric \u003ca href=\"https://github.com/folke/lazy.nvim\"\u003elazy.nvim\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eOn the other side we have Nix, which strives for reproducibility at all costs. Among the many packages in \u003ccode\u003enixpkgs\u003c/code\u003e, \u003ca href=\"https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/editors/neovim/default.nix\"\u003eNeovim\u003c/a\u003e has been around for a while. Furthermore, there exist \u003ca href=\"https://github.com/NixOS/nixpkgs/tree/master/pkgs/applications/editors/vim/plugins\"\u003emany derivations\u003c/a\u003e so that plugins can also be installed via Nix.\u003c/p\u003e\n\u003cp\u003eHome Manager users can install and configure Neovim with any desired plugin via its \u003ca href=\"https://nix-community.github.io/home-manager/options.html#opt-programs.neovim.enable\"\u003eofficially supported module\u003c/a\u003e. Or it can also be installed directly on NixOS. In either case, everything comes from the \u003ccode\u003enixpkgs\u003c/code\u003e repository.\u003c/p\u003e\n\u003cp\u003eThe only downside of relying on plugins defined in \u003ccode\u003enixpkgs\u003c/code\u003e is that they may get quickly outdated, forcing us to resort to overlays or overrides to actually use the latest version, or that specific fork you would like to test. This problem goes away with the introduction \u003ca href=\"https://nixos.wiki/wiki/Flakes\"\u003eNix flakes\u003c/a\u003e.\u003c/p\u003e\n\u003ch2 id=\"neovim-flake\"\u003eNeovim Flake\u003c/h2\u003e\n\u003cp\u003eUp to August 2022, my Neovim configuration was tightly coupled to my Home Manager configuration. It now lives at \u003ca href=\"https://github.com/gvolpe/neovim-coc\"\u003eneovim-coc\u003c/a\u003e, and it is only there for archival reasons; it is no longer maintained.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/neovim-flake.png\" alt=\"flake\"\u003e\u003c/p\u003e\n\u003cp\u003eIt was back then when I started \u003ca href=\"https://github.com/gvolpe/neovim-flake\"\u003eNeovim Flake\u003c/a\u003e as a fork of \u003ca href=\"https://github.com/jordanisaacs\"\u003eJordan Isaacs\u003c/a\u003e\u0026rsquo; flake. I liked the flakes approach for individual applications so much that I soon started making countless changes to adapt it to my needs and liking. It changed so much that you wouldn\u0026rsquo;t recognize it from the fork.\u003c/p\u003e\n\u003cp\u003eNowadays, it is my default Neovim configuration, and I would like to share with you what\u0026rsquo;s so special about it.\u003c/p\u003e\n\u003cp\u003eFlakes consist of \u003cem\u003einputs\u003c/em\u003e and \u003cem\u003eoutputs\u003c/em\u003e. For instance, here are some inputs declared in the \u003ccode\u003eflake.nix\u003c/code\u003e file.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  inputs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    nixpkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eurl \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003egithub:nixos/nixpkgs/nixpkgs-unstable\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    flake-utils\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eurl \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003egithub:numtide/flake-utils\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    nmd \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      url \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003egitlab:rycee/nmd\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      flake \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    nvim-lspconfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      url \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003egithub:neovim/nvim-lspconfig\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      flake \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    nvim-treesitter \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      url \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003egithub:nvim-treesitter/nvim-treesitter\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      flake \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe first two inputs correspond to \u003ccode\u003enixpkgs\u003c/code\u003e (the version we want to use for all the packages) and \u003ccode\u003eflake-utils\u003c/code\u003e (to make it compatible with Linux and Mac, among other platforms). The third input \u003ccode\u003enmd\u003c/code\u003e serves the purpose of generating documentation for the Nix modules, as we will see soon. Ultimately, the remaining inputs correspond to Neovim plugins pointing directly to their source (there are many more declared in this flake).\u003c/p\u003e\n\u003cp\u003eThe outputs can be displayed by running \u003ccode\u003enix flake show\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-console\" data-lang=\"console\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix flake show\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egit+file:///home/gvolpe/workspace/neovim-flake\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e├───apps\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   ├───aarch64-darwin\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   │   ├───default: app\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   │   └───nvim: app\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   ├───aarch64-linux\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   │   ├───default: app\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   │   └───nvim: app\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   ├───x86_64-darwin\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   │   ├───default: app\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   │   └───nvim: app\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   └───x86_64-linux\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│       ├───default: app\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│       └───nvim: app\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e├───devShells\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   └───x86_64-linux\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│       └───default: development environment \u0026#39;nix-shell\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e├───nixosModules\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   └───x86_64-linux: NixOS module\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e├───overlays\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   └───x86_64-linux: Nixpkgs overlay\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e└───packages\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    └───x86_64-linux\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ├───default: package \u0026#39;neovim-0.8.1\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ├───docs: package \u0026#39;html-manual\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ├───metals: package \u0026#39;metals-0.11.9\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        └───neovim-ide: package \u0026#39;neovim-0.8.1\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWe will find five different things, all with multi-platform support (only shown in \u003ccode\u003eapps\u003c/code\u003e, and omitted for brevity in all the other cases).\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eapps\u003c/code\u003e: any program that can run via \u003ccode\u003enix run\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003edevShells\u003c/code\u003e: development shells that can be used via \u003ccode\u003enix develop\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003enixosModules\u003c/code\u003e: modules that can be imported into your Nix system.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eoverlays\u003c/code\u003e: custom packages and functions you can import into your configuration.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003epackages\u003c/code\u003e: any package that can be built via \u003ccode\u003enix build\u003c/code\u003e.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThis is the command we use when the project is checked out locally, but it can be done remotely too.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix flake show github:gvolpe/neovim-flake\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn the same spirit, you can try this Neovim out with its \u003ca href=\"https://github.com/gvolpe/neovim-flake/blob/main/lib/neovim-ide-full.nix\"\u003edefault configuration and enabled plugins\u003c/a\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix run github:gvolpe/neovim-flake\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHowever, we all have different preferences, so it is normal to wish we could tweak a few settings here and there. This is made possible by the module explained in the following section.\u003c/p\u003e\n\u003ch3 id=\"home-manager-module\"\u003eHome Manager module\u003c/h3\u003e\n\u003cp\u003eThe \u003ccode\u003enixosModules\u003c/code\u003e output exposes a Home Manager module we can import into our configuration.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  imports \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ neovim-flake\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enixosModules\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehm ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eOnce imported, we can use the module in our configuration as follows.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  programs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eneovim-ide \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    settings \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      vim\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eviAlias \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      vim\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003evimAlias \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      vim\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elsp \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt\u0026rsquo;s called \u003ccode\u003eneovim-ide\u003c/code\u003e because it is a batteries included Neovim configuration with LSP support for various languages, but emphasizing the Scala programming language. Here\u0026rsquo;s an example:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  vim\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elsp \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    scala \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      metals \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emetalsBuilder {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        version \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;0.11.9+235-2f0e69f8-SNAPSHOT\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        outputHash \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;sha256-9ZJ5bew+ttAr+K9vsM6DKP4G0dcAEXyBYizNA7BeeG8=\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;nvim-metals\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt is set to use the amazing \u003ca href=\"https://github.com/scalameta/nvim-metals\"\u003envim-metals\u003c/a\u003e, and the latest snapshot of Metals. We use \u003ccode\u003epkgs.metalsBuilder\u003c/code\u003e to build a custom Metals version, which is a function exposed by the \u003ccode\u003eoverlays\u003c/code\u003e output. In order to use it, we need to add it to our configuration. For example:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  pkgs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e nixpkgs {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e system;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    overlays \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ neovim-flake\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eoverlays\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edefault ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe module is not final by any means, and it could certainly be improved, but it is perfect for my use cases. Ultimately, adding new modules is quite straightforward via a pull request.\u003c/p\u003e\n\u003cp\u003eFor example, here\u0026rsquo;s the module for the \u003ccode\u003ehop\u003c/code\u003e plugin.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ config\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ewith\u003c/span\u003e lib;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  cfg \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003evim\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehop;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  options\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003evim\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehop \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e mkOption {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e types\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ebool;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Enable Hop plugin (easy motion)\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  config \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e mkIf cfg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenable ({\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    vim\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estartPlugins \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eneovimPlugins\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehop ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    vim\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ennoremap \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026lt;leader\u0026gt;h\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026lt;cmd\u0026gt; HopPattern\u0026lt;CR\u0026gt;\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    vim\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eluaConfigRC \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e      require(\u0026#39;hop\u0026#39;).setup()\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    \u0026#39;\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  });\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHave a look at all the existing \u003ca href=\"https://github.com/gvolpe/neovim-flake/tree/main/modules\"\u003emodules\u003c/a\u003e for more details.\u003c/p\u003e\n\u003ch4 id=\"documentation\"\u003eDocumentation\u003c/h4\u003e\n\u003cp\u003eHome Manager users are accustomed to access the \u003ca href=\"https://nix-community.github.io/home-manager/options.html\"\u003eDocBook documentation\u003c/a\u003e for all the available options in a specific module. NixOS users enjoy an even \u003ca href=\"https://search.nixos.org/options\"\u003ebetter experience\u003c/a\u003e. Thanks to Robert Helgesson\u0026rsquo;s \u003ca href=\"https://gitlab.com/rycee/nmd/\"\u003enmd\u003c/a\u003e, this Neovim Flake HM module is also documented at \u003ca href=\"https://gvolpe.com/neovim-flake/\"\u003egvolpe.com/neovim-flake\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eFor instance, the \u003ca href=\"https://gvolpe.com/neovim-flake/options.html#opt-vim.lsp.scala.enable\"\u003evim.lsp.scala\u003c/a\u003e options look as follows.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/vim-scala-docs.png\" alt=\"docs\"\u003e\u003c/p\u003e\n\u003ch3 id=\"updates\"\u003eUpdates\u003c/h3\u003e\n\u003cp\u003eSo far, we have seen how it can be used and configured, but haven\u0026rsquo;t talked about one of its greatest benefits: updates. To update all inputs to their latest version, we can run the following command.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-console\" data-lang=\"console\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix flake update\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHowever, this is undesirable most of the time, as some plugins may break and we would not notice until we test that particular plugin, which may happen weeks after this update was performed.\u003c/p\u003e\n\u003cp\u003eIn most cases, it is recommended to update a specific plugin at a time, to test it in isolation. For example:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-console\" data-lang=\"console\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix flake lock --update-input nvim-metals\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ewarning: updating lock file \u0026#39;/home/gvolpe/workspace/neovim-flake/flake.lock\u0026#39;:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e• Updated input \u0026#39;nvim-metals\u0026#39;:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u0026#39;github:scalameta/nvim-metals/b7587a9155d22761f1b28c18f7927e6df0d08387\u0026#39; (2022-09-05)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  → \u0026#39;github:scalameta/nvim-metals/d1c01907256dae7c9d55ba1fcfb8cf6b4f583325\u0026#39; (2022-12-09)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe \u003ccode\u003eflake.lock\u003c/code\u003e file is the one keeping track of the specific versions we declare in our \u003ccode\u003eflake.nix\u003c/code\u003e file. Once this plugin is updated, we can try to build it via \u003ccode\u003enix build\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eStill, it would be more interesting to test the plugin actually works with a quick manual test. To do so, we can head to any Scala project directory, and run this version of the Neovim flake there.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-console\" data-lang=\"console\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ cd ../my-scala-project\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix run ../neovim-flake\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThat is all it takes! Isn\u0026rsquo;t this wonderful?\u003c/p\u003e\n\u003cp\u003eThis approach allows us to use whatever plugin we want in whatever version (at least most of them).\u003c/p\u003e\n\u003ch3 id=\"tree-sitter-plugins\"\u003eTree-sitter plugins\u003c/h3\u003e\n\u003cp\u003eFolks who rely on \u003ca href=\"https://github.com/tree-sitter/tree-sitter\"\u003eTree-sitter\u003c/a\u003e, know that its installation with the many different grammars is not as trivial. Yet, Nix makes the management of tree-sitter grammars reproducible and easy to install.\u003c/p\u003e\n\u003cp\u003eFor example, these are some of the inputs I declare to use the \u003ccode\u003etree-sitter-scala\u003c/code\u003e fork from Eugene.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  inputs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ts-build\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eurl \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003egithub:pta2002/build-ts-grammar.nix\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    tree-sitter-scala \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      url \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003egithub:eed3si9n/tree-sitter-scala/fork-integration\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      flake \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis is then treated specially by a library function defined by the Neovim Flake.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  tree-sitter-scala3 \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e inputs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ets-build\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ebuildGrammar pkgs {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    language \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;scala\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    version \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;eed3si9n-fork\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    source \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e inputs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etree-sitter-scala;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ts \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e prev\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etree-sitter\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eoverride {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    extraGrammars \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e { \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e tree-sitter-scala3; };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  treesitterGrammars \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e ts\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ewithPlugins (p: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    p\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etree-sitter-scala3\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    p\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etree-sitter-nix\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    p\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etree-sitter-elm\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ]);\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eYou can quickly look at the available grammars with the following command.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-console\" data-lang=\"console\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix search nixpkgs tree-sitter-grammars\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e* legacyPackages.x86_64-linux.tree-sitter-grammars.tree-sitter-bash (0.20.7)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e* legacyPackages.x86_64-linux.tree-sitter-grammars.tree-sitter-beancount (0.20.7)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e* legacyPackages.x86_64-linux.tree-sitter-grammars.tree-sitter-bibtex (0.20.7)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e* legacyPackages.x86_64-linux.tree-sitter-grammars.tree-sitter-c (0.20.7)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThere are always plugins that require special treatment, but that\u0026rsquo;s not a blocker. For instance, the Smithy grammar requires the highlights file to be copied manually for some reason, but Nix makes it easy to automate this trivial task in a reproducible way.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  nvimTreesitterHook \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    rm -r parser\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    ln -s \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003etreesitterGrammars\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e parser\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    mkdir -p $out/queries/smithy\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    cp \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ets\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ebuiltGrammars\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etree-sitter-smithy\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/queries/highlights.scm $out/queries/smithy/highlights.scm\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e  \u0026#39;\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn most cases, building your custom tree-sitter grammar should be as simple as the \u003ccode\u003etree-sitter-scala\u003c/code\u003e one.\u003c/p\u003e\n\u003ch3 id=\"conclusion\"\u003eConclusion\u003c/h3\u003e\n\u003cp\u003eIf you are still hesitating over using Nix, this blog post laid out another compelling use case :)\u003c/p\u003e\n\u003cp\u003eGive it a try (drop it a 🌟 if you enjoy it!) and if you need any help, reach out via the \u003ca href=\"https://github.com/gvolpe/neovim-flake/discussions\"\u003ediscussions\u003c/a\u003e section.\u003c/p\u003e\n\u003cp\u003eMerry Xmas,\nGabriel.\u003c/p\u003e\n","date":"2022-12-25","dateFormatted":"2022.12.25","excerpt":"It is no secret that Neovim is my favorite text editor. I use it on a daily basis for multiple purposes:\nTo write Scala code (and the occasional Typescript) for …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/neovim-meets-nix-flakes/","readingTime":8,"slug":"neovim-meets-nix-flakes","subtitle":null,"summary":"\u003cp\u003eIt is no secret that \u003ca href=\"https://neovim.io/\"\u003eNeovim\u003c/a\u003e is my favorite text editor. I use it on a daily basis for multiple purposes:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eTo write Scala code (and the occasional Typescript) for \u003ccode\u003e$work\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eTo write the extensive text and code for \u003ca href=\"https://leanpub.com/u/gvolpe\"\u003emy books\u003c/a\u003e.\u003c/li\u003e\n\u003cli\u003eTo fine-tune my ever-changing \u003ca href=\"https://github.com/gvolpe/nix-config\"\u003eNixOS configuration\u003c/a\u003e.\u003c/li\u003e\n\u003cli\u003eTo write blog posts such as this one.\u003c/li\u003e\n\u003cli\u003eFor the casual Haskell and Rust code I sometimes need to write.\u003c/li\u003e\n\u003cli\u003eLiterally for any other text file I need to explore and modify.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eAll this functionality is enabled by many plugins configured and tailored to my needs. Now there exist a bunch of plugin managers to handle the installation of plugins, from the classic \u003ca href=\"https://github.com/junegunn/vim-plug\"\u003evim-plug\u003c/a\u003e to the more esoteric \u003ca href=\"https://github.com/folke/lazy.nvim\"\u003elazy.nvim\u003c/a\u003e.\u003c/p\u003e","tags":["nix","neovim","flakes","scala","treesitter"],"title":"Neovim meets Nix flakes","url":"https://gvolpe.com/blog/neovim-meets-nix-flakes/","wordCount":1545},{"comments":true,"content":"\u003cp\u003eIf you have read this post when it first came out and felt offended by its catchy title, let me apologize for it. It caused some controversy, so I decided to change the title completely to highlight more what this post was about in the first place. Hope this time folks read to the end before drawing any conclusion :)\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003e\u003ca href=\"https://docs.scala-lang.org/scala3/new-in-scala3.html\"\u003eScala 3\u003c/a\u003e has been around for a while now, but not many people are using it in production just yet. There\u0026rsquo;s a lot of skepticism in the community when starting out a new project.\u003c/p\u003e\n\u003cp\u003eIt is still a niche language, even though it has seen a lot of adoption in the past few years. Given its hybrid OOP-FP nature, there are \u0026ldquo;sub-communities\u0026rdquo; within the language, one of them being those that go FP all the way embraced by organizations such as \u003ca href=\"https://typelevel.org/\"\u003eTypelevel\u003c/a\u003e, which has its own ecosystem of libraries.\u003c/p\u003e\n\u003ch2 id=\"tooling\"\u003eTooling\u003c/h2\u003e\n\u003cp\u003eFrom what I gathered from different folks, tooling and linting seem to be the biggest push-backs regarding Scala 3 adoption. Although tooling keeps on getting better every day when using \u003ca href=\"https://scalameta.org/metals/\"\u003eMetals\u003c/a\u003e, it seems IntelliJ IDEA support is not quite there yet\u0026mdash;you can follow its progress using the \u003ca href=\"https://youtrack.jetbrains.com/issues?q=tag:%20%7BScala%203%7D\"\u003eScala 3 tag\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eAs a Metals+NeoVim user myself, I am highly satisfied with the tooling support. Though, I must admit I don\u0026rsquo;t use too many features besides completion, navigation and diagnostics.\u003c/p\u003e\n\u003cp\u003eFurthermore, new features such as significant whitespace and fewer braces only make it more difficult for tooling to get feature-parity with Scala 2.\u003c/p\u003e\n\u003cp\u003eThe official site has a \u003ca href=\"https://docs.scala-lang.org/scala3/guides/migration/tooling-tour.html\"\u003eTooling Tour\u003c/a\u003e page offering a status report of the different tools such as Sbt, Mill and Maven (the latter still unsupported).\u003c/p\u003e\n\u003ch2 id=\"linting\"\u003eLinting\u003c/h2\u003e\n\u003cp\u003eLinting has been neglected in Scala 3, that\u0026rsquo;s the sad truth. Only in June this year \u003ca href=\"https://github.com/lampepfl/dotty/issues/15503\"\u003eit has been made official\u003c/a\u003e, and it got assigned in August as a \u0026ldquo;semester project\u0026rdquo;, but it may take a while until \u003ca href=\"https://github.com/lampepfl/dotty/pull/16157\"\u003ethis work\u003c/a\u003e sees the light.\u003c/p\u003e\n\u003cp\u003eAnyway, I think linting features such as \u0026ldquo;unused variables\u0026rdquo; and \u0026ldquo;unused imports\u0026rdquo; are only a nice-to-have, so I can understand why this work was not prioritized.\u003c/p\u003e\n\u003cp\u003eHowever, there is one linting feature that has been blocking the FP community from taking this new version of the language more seriously: \u003ccode\u003e-Wvalue-discard\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eIt may seem insignificant, but this little feature can prevent massive bugs from reaching production in purely functional codebases. Here\u0026rsquo;s an example that showcases its importance.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eval\u003c/span\u003e program\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eIO\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eIO\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eref\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eList\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eempty\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eInt\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]).\u003c/span\u003eflatMap \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e ref \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eIO\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eprintln\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;performing critical work\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ref\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eset\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eList\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erange\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e11\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eSuppose the \u003ccode\u003eIO.println\u003c/code\u003e does indeed perform critical work. We would be discarding that value, perhaps accidentally, and the Scala compiler won\u0026rsquo;t help us fix this bug! This is extremely critical, and even seasoned functional programmers can forget to connect such values (e.g. via \u003ccode\u003e*\u0026gt;\u003c/code\u003e or \u003ccode\u003eflatMap\u003c/code\u003e).\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esbt\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003edemo\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ecompile\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003esuccess\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTotal\u003c/span\u003e time\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e0\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003es\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e completed \u003cspan style=\"color:#a6e22e\"\u003eNov\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e20\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e2022\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e08\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e20\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePM\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis kind of code can be even harder to manually spot in larger codebases.\u003c/p\u003e\n\u003cp\u003eScala 2 ships with \u003ccode\u003e-Wvalue-discard\u003c/code\u003e (formerly known as \u003ccode\u003e-Ywarn-value-discard\u003c/code\u003e), which would only emit a warning with the same code, but it is highly recommended to make it a fatal error via the \u003ccode\u003e-Xfatal-warnings\u003c/code\u003e flag (also present in Scala 3).\u003c/p\u003e\n\u003cp\u003eThe \u003ca href=\"https://github.com/typelevel/sbt-tpolecat\"\u003esbt-tpolecat\u003c/a\u003e plugin makes it easy for us to focus on writing code if we let it manage the configuration of such important flags, which can be further customized.\u003c/p\u003e\n\u003cp\u003eIt is also worth noticing that there is a \u003ca href=\"https://github.com/lampepfl/dotty/pull/15975\"\u003edraft PR\u003c/a\u003e started by \u003ca href=\"https://github.com/cb372\"\u003eChris Birchall\u003c/a\u003e sometime ago attempting to add support for \u003ccode\u003e-Wvalue-discard\u003c/code\u003e to the Scala 3 compiler, but it has unfortunately gone inactive.\u003c/p\u003e\n\u003cp\u003ePoint in case, you may now understand why folks writing pure FP code are being skeptical about adopting Scala 3 in production systems! So, do we wait another year and see if it\u0026rsquo;s finally ready by then?\u003c/p\u003e\n\u003ch3 id=\"give-up-all-hope\"\u003eGive up all hope?\u003c/h3\u003e\n\u003cp\u003eThankfully, not. Meet the \u003ca href=\"https://github.com/ghik/zerowaste\"\u003eZerowaste\u003c/a\u003e compiler plugin coming to the rescue! It detects unused expressions (non-Unit), and it works for all major Scala versions.\u003c/p\u003e\n\u003cp\u003eAll we need is to add the plugin to our codebase as follows:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003elibraryDependencies \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e compilerPlugin\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;com.github.ghik\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e%\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;zerowaste\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e%\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026lt;version\u0026gt;\u0026#34;\u003c/span\u003e cross \u003cspan style=\"color:#a6e22e\"\u003eCrossVersion\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efull\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eTogether with enabling \u003ccode\u003esbt-tpolecat\u003c/code\u003e, the example code no longer compiles.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esbt\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003edemo\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003einfo\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e compiling \u003cspan style=\"color:#ae81ff\"\u003e6\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eScala\u003c/span\u003e sources to \u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003ehome\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egvolpe\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003edemo\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003etarget\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003escala\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e3.2\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eclasses \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eerror\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eError\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e/home/gvolpe/demo/src/main/scala/demo/Main.scala:\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e12\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e6\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eerror\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e12\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e|\u003c/span\u003e      \u003cspan style=\"color:#66d9ef\"\u003eIO.println\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eperforming\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ecritical\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ework\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eerror\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e]\u003c/span\u003e    \u003cspan style=\"color:#66d9ef\"\u003e|\u003c/span\u003e      \u003cspan style=\"color:#66d9ef\"\u003e^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eerror\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e    \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e      discarded expression \u003cspan style=\"color:#66d9ef\"\u003ewith\u003c/span\u003e non\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eUnit\u003c/span\u003e value\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eerror\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e one error found\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eerror\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eCompile\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003e compileIncremental\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eCompilation\u003c/span\u003e failed\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eerror\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTotal\u003c/span\u003e time\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e1\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003es\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e completed \u003cspan style=\"color:#a6e22e\"\u003eNov\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e20\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e2022\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e23\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e39\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePM\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eYes! Exactly what we all FP nerds have been waiting for 🤓\u003c/p\u003e\n\u003ch2 id=\"final-thoughts\"\u003eFinal thoughts\u003c/h2\u003e\n\u003cp\u003eAlthough some features are still missing, \u003cstrong\u003eI absolutely love Scala 3\u003c/strong\u003e and promote its usage in production (we use it at work too). I even \u003ca href=\"https://leanpub.com/feda\"\u003ewrote a book\u003c/a\u003e that endorses this new version of the language.\u003c/p\u003e\n\u003cp\u003eYes, it would be great if more linting features land in the Scala compiler. But until then, we can rely on the Zerowaste compiler plugin. Still, it could benefit from more testing!\u003c/p\u003e\n\u003cp\u003eIf you have a Scala 3 project, please do give it a try and report any issues you may find. We can help each other and grow as a community together :)\u003c/p\u003e\n\u003cp\u003eFinally, huge thanks to \u003ca href=\"https://github.com/ghik\"\u003eRoman Janusz\u003c/a\u003e (author of Zerowaste) for being the unsung Scala 3 FP hero!\u003c/p\u003e\n","date":"2022-11-20","dateFormatted":"2022.11.20","excerpt":"If you have read this post when it first came out and felt offended by its catchy title, let me apologize for it. It caused some controversy, so I decided to …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/scala3-missing-compiler-plugin/","readingTime":5,"slug":"scala3-missing-compiler-plugin","subtitle":null,"summary":"\u003cp\u003eIf you have read this post when it first came out and felt offended by its catchy title, let me apologize for it. It caused some controversy, so I decided to change the title completely to highlight more what this post was about in the first place. Hope this time folks read to the end before drawing any conclusion :)\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003e\u003ca href=\"https://docs.scala-lang.org/scala3/new-in-scala3.html\"\u003eScala 3\u003c/a\u003e has been around for a while now, but not many people are using it in production just yet. There\u0026rsquo;s a lot of skepticism in the community when starting out a new project.\u003c/p\u003e","tags":["scala"],"title":"Scala 3: the missing compiler plugin","url":"https://gvolpe.com/blog/scala3-missing-compiler-plugin/","wordCount":864},{"comments":true,"content":"\u003ch3 id=\"introduction\"\u003eIntroduction\u003c/h3\u003e\n\u003cp\u003eScala 3 introduces \u003ca href=\"https://docs.scala-lang.org/scala3/reference/new-types/union-types.html\"\u003eunion types\u003c/a\u003e. Straight from the official documentation, a union type \u003ccode\u003eA | B\u003c/code\u003e has as values all values of type \u003ccode\u003eA\u003c/code\u003e and also all values of type \u003ccode\u003eB\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eSo the following code snippet compiles performing an exhaustive pattern-matching.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e foo\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ex\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eInt\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLong\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  x \u003cspan style=\"color:#66d9ef\"\u003ematch\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eInt\u003c/span\u003e  \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e println\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Int!!!\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLong\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e println\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Long!!!\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHowever, you are not here for boring examples, are you? :)\u003c/p\u003e\n\u003ch3 id=\"error-types\"\u003eError types\u003c/h3\u003e\n\u003cp\u003eUnion types are the perfect feature to model error types. In Scala 2, we could represent the presence of errors via the \u003ccode\u003eEither\u003c/code\u003e monad. E.g.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eErr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEither\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eUserNotFound\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHowever, if we want to model other errors, we can either get into \u003ca href=\"https://github.com/milessabin/shapeless/blob/70076c2/core/src/main/scala/shapeless/coproduct.scala\"\u003eShapeless\u0026rsquo; Coproducts\u003c/a\u003e (fairly common during the Free Monad hype-era!) or into nested \u003ccode\u003eEither\u003c/code\u003es (arghhh). E.g.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eErr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEither\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eEither\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eDuplicateStory\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eUserNotFound\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThough, as we usually work in some \u003ccode\u003eF[_]\u003c/code\u003e context, you can imagine things get only much more complicated and less ergonomic from here.\u003c/p\u003e\n\u003ch4 id=\"union-types-to-the-rescue\"\u003eUnion types to the rescue!\u003c/h4\u003e\n\u003cp\u003eWith union types, we can keep a single \u003ccode\u003eEither\u003c/code\u003e instead.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eErr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEither\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eDuplicateStory\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUserNotFound\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eOr we could eliminate the \u003ccode\u003eEither\u003c/code\u003e type altogether!\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eErr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eDuplicateStory\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eUserNotFound\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eUnit\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eMuch nicer! Now, how do we get this to play nicely in the \u003ccode\u003eF\u003c/code\u003e context? Bear with me a little longer.\u003c/p\u003e\n\u003ch3 id=\"error-handling\"\u003eError handling\u003c/h3\u003e\n\u003cp\u003eIn my experience, error types are only desirable at the bottom layers. Most of the error handling should occur at the mid layers (business logic) where the errors are eliminated, or perhaps a few errors should be left for the top layers to handle.\u003c/p\u003e\n\u003cp\u003eTo put these words into an example, let\u0026rsquo;s say we work with a three-layer application.\u003c/p\u003e\n\u003ch4 id=\"bottom-layer\"\u003eBottom layer\u003c/h4\u003e\n\u003cp\u003eAt the bottom level, we have an \u003ccode\u003eUserStore\u003c/code\u003e that interacts with a database.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etrait\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eUserStore\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]]\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e fetch\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eid\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUserId\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eOption\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eUser\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e save\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003euser\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUser\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eEither\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eDuplicateEmail\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eDuplicateUsername\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWhere the error types are subtypes of \u003ccode\u003eThrowable\u003c/code\u003e (this will make our lives easier).\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e scala.util.control.NoStackTrace\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eobject\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eDuplicateEmail\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eextends\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eNoStackTrace\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eDuplicateEmail\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eDuplicateEmail\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eobject\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eDuplicateUsername\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eextends\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eNoStackTrace\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eDuplicateUsername\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eDuplicateUsername\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIf you have read my book \u003ca href=\"https://leanpub.com/pfp-scala\"\u003ePractical FP in Scala\u003c/a\u003e, you know I am not a big fan of stack traces :)\u003c/p\u003e\n\u003ch4 id=\"middle-layer\"\u003eMiddle layer\u003c/h4\u003e\n\u003cp\u003eNow at the middle layer, we might have our business logic, making calls to the \u003ccode\u003eUserStore\u003c/code\u003e, and perhaps to other components that interact with the outside world.\u003c/p\u003e\n\u003cp\u003eIn the following mid layer, our aim is to handle all declared errors, so we pattern match on all cases.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e mid1\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLogger:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eMonadThrow\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    producer\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eProducer\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eString\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    userStore\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUserStore\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUser\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e F\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e user \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  userStore\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esave\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eflatMap \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eRight\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      producer\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esend\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003es\u0026#34;User \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e$user\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e persisted!\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eLeft\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eDuplicateEmail\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#a6e22e\"\u003eLogger\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e].\u003c/span\u003eerror\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003es\u0026#34;Email \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003euser\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eemail\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e already taken!\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eLeft\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eDuplicateUsername\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#a6e22e\"\u003eLogger\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e].\u003c/span\u003eerror\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003es\u0026#34;Email \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003euser\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eemail\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e already taken!\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAfter \u003ccode\u003eflatMap\u003c/code\u003eping the result of \u003ccode\u003esave\u003c/code\u003e, we can pattern-match on the possible values. The nice thing here is that the Scala compiler checks for exhaustivity, so we never miss a declared error, in case that changes in the future.\u003c/p\u003e\n\u003cp\u003eSometimes, however, it happens that we are only interested in handling a subset of the errors, and whatever is added later should be handled at the top layers.\u003c/p\u003e\n\u003cp\u003eLet\u0026rsquo;s say we only need to handle the \u003ccode\u003eDuplicateEmail\u003c/code\u003e error.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e mid2\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLogger:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eMonadThrow\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    producer\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eProducer\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eString\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    userStore\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUserStore\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUser\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e F\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eEither\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eDuplicateUsername\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e user \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  userStore\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esave\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eflatMap \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eRight\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      producer\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esend\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003es\u0026#34;User \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e$user\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e persisted!\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e).\u003c/span\u003emap\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003easRight\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eLeft\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eDuplicateEmail\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#a6e22e\"\u003eLogger\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e].\u003c/span\u003eerror\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003es\u0026#34;Email \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003euser\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eemail\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e already taken!\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e).\u003c/span\u003emap\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003easRight\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eLeft\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ee\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003easLeft\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epure\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAny other error is caught in the last case, where we simply leave it unhandled.\u003c/p\u003e\n\u003cp\u003eThis works, though, the ergonomics are not the best, as we need to manually call \u003ccode\u003easRight\u003c/code\u003e and \u003ccode\u003easLeft\u003c/code\u003e in different places. We can improve the UX with some custom extension methods.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e mid3\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLogger:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eMonadThrow\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    producer\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eProducer\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eString\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    userStore\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUserStore\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUser\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e F\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eEither\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eDuplicateUsername\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e user \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  userStore\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esave\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erethrow\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eas\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003es\u0026#34;User \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e$user\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e persisted!\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erecoverErrorWith \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eDuplicateEmail\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eLogger\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e].\u003c/span\u003eerror\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003es\u0026#34;Email \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003euser\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eemail\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e already taken!\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elift\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eFirst of all, \u003ccode\u003erethrow\u003c/code\u003e eliminates the inner \u003ccode\u003eEither\u003c/code\u003e, giving us \u003ccode\u003eF[Unit]\u003c/code\u003e. Next, we handle the error we are interested in via \u003ccode\u003erecoverErrorWith\u003c/code\u003e. Both functions are defined by \u003ccode\u003eApplicativeError\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eUntil here the resulting type remains \u003ccode\u003eF[Unit]\u003c/code\u003e. At last, the magic happens when we call \u003ccode\u003elift\u003c/code\u003e and get \u003ccode\u003eF[Either[DuplicateUsername, Unit]]\u003c/code\u003e back!\u003c/p\u003e\n\u003cp\u003eSo what is \u003ccode\u003elift\u003c/code\u003e? It is a custom extension method defined as follows.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eextension \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eMonadThrow\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eA\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003efa\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eA\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003e@nowarn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e lift\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eE\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e\u0026lt;:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eThrowable\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eEither\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eE\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eA\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    fa\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emap\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003easRight\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eE\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]).\u003c/span\u003erecover \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eE\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003easLeft \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003eUPDATE\u003c/strong\u003e: \u003ca href=\"https://github.com/vasilmkd\"\u003eVasil Vasilev\u003c/a\u003e discovered the existence of \u003ccode\u003eattemptNarrow\u003c/code\u003e from \u003ccode\u003eApplicativeError\u003c/code\u003e, after having a quick chat about the differences between \u003ccode\u003elift\u003c/code\u003e and \u003ccode\u003eattempt\u003c/code\u003e, which is exactly what this does.\u003c/p\u003e\n\u003cp\u003eThe only difference, is that \u003ccode\u003eattemptNarrow\u003c/code\u003e requires a \u003ccode\u003eClassTag\u003c/code\u003e, but that\u0026rsquo;s not a problem :)\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e lift\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eE\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e\u0026lt;:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eThrowable:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eClassTag\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eEither\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eE\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eA\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  fa\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eattemptNarrow\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWe could use \u003ccode\u003eattemptNarrow\u003c/code\u003e directly, but if you get to the end of this post, you\u0026rsquo;ll understand why I chose to keep the \u003ccode\u003elift\u003c/code\u003e extension method instead.\u003c/p\u003e\n\u003cp\u003eNotice that for this to work, we need to either declare the function\u0026rsquo;s return type or to indicate what types we expect when we call \u003ccode\u003elift\u003c/code\u003e. E.g.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eval\u003c/span\u003e f\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eIO\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eIO\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eraiseError\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eErr1\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eval\u003c/span\u003e g\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eIO\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eEither\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eErr1\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eErr2\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e f\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elift\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eval\u003c/span\u003e h \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e f\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elift\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eErr1\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eErr2\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eg \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u0026gt;\u003c/span\u003e h\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ef \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u0026gt;\u003c/span\u003e g\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erethrow \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u0026gt;\u003c/span\u003e h\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erethrow\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHere\u0026rsquo;s another way of doing the same without \u003ccode\u003erethrow\u003c/code\u003e and \u003ccode\u003erecoverErrorWith\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e mid4\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLogger:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eMonadThrow\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    producer\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eProducer\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eString\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    userStore\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUserStore\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUser\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e F\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eEither\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eDuplicateUsername\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e user \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  userStore\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esave\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eflatMap \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eRight\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      producer\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esend\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003es\u0026#34;User \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e$user\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e persisted!\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eLeft\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eDuplicateEmail\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#a6e22e\"\u003eLogger\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e].\u003c/span\u003eerror\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003es\u0026#34;Email \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003euser\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eemail\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e already taken!\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eLeft\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ee\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eraiseError\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e}.\u003c/span\u003elift\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe only problem with the technique used in both \u003ccode\u003emid3\u003c/code\u003e and \u003ccode\u003emid4\u003c/code\u003e, is that we lose the error type information after a partial error handling and lifting. For instance, if we add another error to \u003ccode\u003eUserStore[F]#save\u003c/code\u003e, the compiler won\u0026rsquo;t help us here.\u003c/p\u003e\n\u003cp\u003eNevertheless, this is easily fixed by pattern matching on all the errors, but it might require some repetition regarding \u003ccode\u003eraiseError\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e mid5\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLogger:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eMonadThrow\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    producer\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eProducer\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eString\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    userStore\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUserStore\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUser\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e F\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eEither\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eDuplicateUsername\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e user \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  userStore\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esave\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eflatMap \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eRight\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      producer\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esend\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003es\u0026#34;User \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e$user\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e persisted!\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eLeft\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eDuplicateEmail\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#a6e22e\"\u003eLogger\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e].\u003c/span\u003eerror\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003es\u0026#34;Email \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003euser\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eemail\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e already taken!\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eLeft\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eDuplicateUsername\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eraiseError\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e}.\u003c/span\u003elift\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIf we add another \u003ccode\u003eFooError\u003c/code\u003e type in the bottom layers, the compiler is going to catch it here for us, and all we need to go is to re-raise it.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eLeft\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eFooError\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eraiseError\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThat\u0026rsquo;s what I mean with the potential repetition regarding \u003ccode\u003eraiseError\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eTo conclude with this mid-layer section, let\u0026rsquo;s just say that all of these error handling techniques are valid; only they have different trade-offs.\u003c/p\u003e\n\u003ch4 id=\"top-layer\"\u003eTop layer\u003c/h4\u003e\n\u003cp\u003eAt the top layer, is where we either handle the error or we let it crash. So here\u0026rsquo;s the perfect place to use \u003ccode\u003erethrow\u003c/code\u003e or \u003ccode\u003eraiseError\u003c/code\u003es we don\u0026rsquo;t care about.\u003c/p\u003e\n\u003cp\u003eIn the following example, we do not care about any unhandled errors so we let it fail.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e top1\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    consumer\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eConsumer\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eIO\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eUser\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    mid\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUser\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eIO\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eEither\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eDuplicateUsername\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eIO\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  consumer\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ereceive\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eevalMap \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e user \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    mid\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003euser\u003cspan style=\"color:#f92672\"\u003e).\u003c/span\u003erethrow \u003cspan style=\"color:#f92672\"\u003e*\u0026gt;\u003c/span\u003e consumer\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eack\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIf this is called by a library like Http4s, this will be translated into an HTTP response with code 500, for example.\u003c/p\u003e\n\u003cp\u003eOr we could do something about it.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e top2\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    consumer\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eConsumer\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eIO\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eUser\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    mid\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUser\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eIO\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eEither\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eDuplicateUsername\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eIO\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  consumer\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ereceive\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eevalMap \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e user \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    mid\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003euser\u003cspan style=\"color:#f92672\"\u003e).\u003c/span\u003eflatMap \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eRight\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e consumer\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eack\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eLeft\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eDuplicateUsername\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        logger\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ewarn\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Duplicate username\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u0026gt;\u003c/span\u003e consumer\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eack\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e}.\u003c/span\u003ehandlerErroWith \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      logger\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eerror\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003es\u0026#34;Unhandled \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e$e\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e, let it crash?\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u0026gt;\u003c/span\u003e consumer\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enack\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAny unhandled errors will be logged and unacked (negative acknowledge).\u003c/p\u003e\n\u003ch3 id=\"furthermore\"\u003eFurthermore\u003c/h3\u003e\n\u003cp\u003eAt the beginning of the post, when the idea of using union types for error modelling was introduced, it was hinted that we could eliminate the \u003ccode\u003eEither\u003c/code\u003e altogether. How about that?\u003c/p\u003e\n\u003cp\u003eStarting from the bottom layer, we can do this instead.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etrait\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eUserStore\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]]\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e fetch\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eid\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUserId\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eOption\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eUser\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e save\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003euser\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUser\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eDuplicateEmail\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eDuplicateUsername\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHowever, by doing so, we lose the \u003ccode\u003erethrow\u003c/code\u003e ability, as we are no longer working with \u003ccode\u003eEither\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eChallenge accepted! Let\u0026rsquo;s introduce a \u003ccode\u003erethrowU\u003c/code\u003e that works on union types.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eextension \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eMonadThrow\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eE\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e\u0026lt;:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eThrowable\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eA\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003efa\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eE\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eA\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e rethrowU\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eA\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    fa\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emap\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003easEither\u003cspan style=\"color:#f92672\"\u003e).\u003c/span\u003erethrow\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eextension \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eE\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e\u0026lt;:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eThrowable\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eA\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003eut\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eE\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eA\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003e@nowarn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e asEither\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eEither\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eE\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eA\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ut \u003cspan style=\"color:#66d9ef\"\u003ematch\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eE\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eLeft\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ee\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e a\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eA\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eRight\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ea\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn the same way, we can also introduce a \u003ccode\u003eliftU\u003c/code\u003e, defined in terms of \u003ccode\u003elift\u003c/code\u003e under the same scope.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eextension \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eMonadThrow\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eA\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003efa\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eA\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e liftU\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eE\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e\u0026lt;:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eThrowable:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eClassTag\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eE\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eA\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    lift\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emap\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003easUnionType\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eextension \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eE\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eA\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003eeither\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eEither\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eE\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eA\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e asUnionType\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eE\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eA\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    either \u003cspan style=\"color:#66d9ef\"\u003ematch\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eLeft\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ee\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eE\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e  \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eRight\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ea\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eA\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e a\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis is the reason why I kept the \u003ccode\u003elift\u003c/code\u003e extension method instead of using \u003ccode\u003eattemptNarrow\u003c/code\u003e. However, I could have also named this \u003ccode\u003eattemptNarrowU\u003c/code\u003e instead of \u003ccode\u003eliftU\u003c/code\u003e, but I prefer the shorter names :)\u003c/p\u003e\n\u003cp\u003eNow we can rewrite the final \u003ccode\u003emid5\u003c/code\u003e as follows.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e mid6\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLogger:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eMonadThrow\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    producer\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eProducer\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eString\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    userStore\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUserStore\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUser\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e F\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eDuplicateUsername\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e user \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  userStore\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esave\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eflatMap \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e()\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      producer\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esend\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003es\u0026#34;User \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e$user\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e persisted!\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eDuplicateEmail\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#a6e22e\"\u003eLogger\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e].\u003c/span\u003eerror\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003es\u0026#34;Email \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003euser\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eemail\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e already taken!\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eDuplicateUsername\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eraiseError\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e}.\u003c/span\u003eliftU\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAnd the final \u003ccode\u003etop2\u003c/code\u003e as shown below.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e top3\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    consumer\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eConsumer\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eIO\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eUser\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    mid\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUser\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eIO\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eDuplicateUsername\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eIO\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  consumer\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ereceive\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eevalMap \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e user \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    mid\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003euser\u003cspan style=\"color:#f92672\"\u003e).\u003c/span\u003eflatMap \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e()\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e consumer\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eack\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eDuplicateUsername\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        logger\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ewarn\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Duplicate username\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u0026gt;\u003c/span\u003e consumer\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eack\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e}.\u003c/span\u003ehandlerErroWith \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      logger\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eerror\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003es\u0026#34;Unhandled \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e$e\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e, let it crash?\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u0026gt;\u003c/span\u003e consumer\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enack\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"conclusion\"\u003eConclusion\u003c/h3\u003e\n\u003cp\u003eI think this error modeling and handling technique is very promising. I would probably still keep the \u003ccode\u003eEither[E1 | E2, A\u003c/code\u003e] model over \u003ccode\u003eE1 | E2 | A\u003c/code\u003e, but this blog post has demonstrated that both options are valid.\u003c/p\u003e\n\u003cp\u003eThe code shown in this post hasn\u0026rsquo;t been compiled, but I use the very same technique in the project of my \u003ca href=\"https://leanpub.com/feda\"\u003eupcoming book\u003c/a\u003e, so you can have a look the \u003ca href=\"https://github.com/gvolpe/trading/tree/main/modules/forecasts/src/main/scala/trading/forecasts\"\u003esource code\u003c/a\u003e directly.\u003c/p\u003e\n\u003cp\u003eLet\u0026rsquo;s also remind ourselves that the left side of \u003ccode\u003eEither\u003c/code\u003e representing errors is merely a social agreement. We could as well do it the other way around, but that would need different \u003ccode\u003eFunctor\u003c/code\u003e / \u003ccode\u003eMonad\u003c/code\u003e instances, so it is not quite practical.\u003c/p\u003e\n\u003cp\u003eIn the same way, we could agree that only the right hand-side type of a union type represents the successful value, and any other types on the left hand-side represent the errors. We could probably write typeclass instances that prove the lawfulness of such approach.\u003c/p\u003e\n\u003cp\u003eError handling is always a hot topic in FP land, so don\u0026rsquo;t take this as the \u003cem\u003eultimate word\u003c/em\u003e, but simply as a technique that can be exploited for our benefits :)\u003c/p\u003e\n\u003cp\u003eCheers,\nGabriel.\u003c/p\u003e\n","date":"2022-02-08","dateFormatted":"2022.02.08","excerpt":"Introduction Scala 3 introduces union types. Straight from the official documentation, a union type A | B has as values all values of type A and also all values …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/error-handling-scala3/","readingTime":9,"slug":"error-handling-scala3","subtitle":null,"summary":"\u003ch3 id=\"introduction\"\u003eIntroduction\u003c/h3\u003e\n\u003cp\u003eScala 3 introduces \u003ca href=\"https://docs.scala-lang.org/scala3/reference/new-types/union-types.html\"\u003eunion types\u003c/a\u003e. Straight from the official documentation, a union type \u003ccode\u003eA | B\u003c/code\u003e has as values all values of type \u003ccode\u003eA\u003c/code\u003e and also all values of type \u003ccode\u003eB\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eSo the following code snippet compiles performing an exhaustive pattern-matching.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e foo\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ex\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eInt\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLong\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  x \u003cspan style=\"color:#66d9ef\"\u003ematch\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eInt\u003c/span\u003e  \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e println\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Int!!!\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLong\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e println\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Long!!!\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHowever, you are not here for boring examples, are you? :)\u003c/p\u003e\n\u003ch3 id=\"error-types\"\u003eError types\u003c/h3\u003e\n\u003cp\u003eUnion types are the perfect feature to model error types. In Scala 2, we could represent the presence of errors via the \u003ccode\u003eEither\u003c/code\u003e monad. E.g.\u003c/p\u003e","tags":["scala","fp","functional-programming"],"title":"Scala 3: Error handling in FP land","url":"https://gvolpe.com/blog/error-handling-scala3/","wordCount":1735},{"comments":true,"content":"\u003cp\u003eI have recently migrated my entire NixOS and Home Manager (HM) \u003ca href=\"https://github.com/gvolpe/nix-config\"\u003econfiguration\u003c/a\u003e \u0026mdash; including programs, services, dotfiles, etc \u0026mdash; over to the new kid on the block: \u003ca href=\"https://nixos.wiki/wiki/Flakes\"\u003eNix flakes\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eIt was not as difficult as I thought it would be but there were a lot of things I had to figure out on my own or by asking more experienced folks on the NixOS matrix channel.\u003c/p\u003e\n\u003cp\u003eSo let me tell you the important bits of this migration story in this short blog post ;)\u003c/p\u003e\n\u003ch3 id=\"how-it-started-nixos\"\u003eHow it started: NixOS\u003c/h3\u003e\n\u003cp\u003eI initially had my NixOS configuration under the \u003ccode\u003esystem\u003c/code\u003e directory and my Home Manager configuration under \u003ccode\u003ehome\u003c/code\u003e, as you can see in the Github repo. So I decided to do the migration step by step, starting with the former.\u003c/p\u003e\n\u003cp\u003eI created a \u003ccode\u003eflake.nix\u003c/code\u003e file under \u003ccode\u003e/etc/nixos\u003c/code\u003e with the following content.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;NixOS configuration\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  inputs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enixpkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eurl \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;nixpkgs/nixos-unstable\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  outputs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e inputs \u003cspan style=\"color:#f92672\"\u003e@\u003c/span\u003e { self\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e nixpkgs }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e system \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;x86_64-linux\u0026#34;\u003c/span\u003e; \u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      nixosConfigurations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        tongfang-amd \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e nixpkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enixosSystem {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e system;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          specialArgs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e { \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e inputs; };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          modules \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#e6db74\"\u003e./machine/tongfang-amd\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#e6db74\"\u003e./configuration.nix\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eI could then build my system using this flake! Easy, right?\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ sudo nixos-rebuild switch --flake .#tongfang-amd\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eBy default, \u003ccode\u003enixos-rebuild\u003c/code\u003e expects the configuration under \u003ccode\u003e/etc/nixos\u003c/code\u003e. However, we can specify a different directory, as shown below.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ sudo nixos-rebuild switch --flake \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;/home/gvolpe/workspace/nix-config#tongfang-amd\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt also turns out this flake can be built via \u003ccode\u003enix build\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix build .#nixosConfigurations.tongfang-amd.config.system.build.toplevel\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ sudo result/bin/switch-to-configuration switch\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis means we can switch the system configuration from any directory by using either command!\u003c/p\u003e\n\u003cp\u003eThat\u0026rsquo;s handy if you keep all your configurations in a single directory and these are tracked by a version control system (VCS) such as git.\u003c/p\u003e\n\u003ch3 id=\"next-step-home-manager\"\u003eNext step: Home Manager\u003c/h3\u003e\n\u003cp\u003eThis was not as straightforward, as my HM configuration is a bit complex, but doable nonetheless. In the same way, I started creating a \u003ccode\u003eflake.nix\u003c/code\u003e under the \u003ccode\u003ehome\u003c/code\u003e directory but I quickly realized having two different flakes for a single machine is not ideal.\u003c/p\u003e\n\u003cp\u003eHowever, since both the NixOS and HM configurations can be built from anywhere (no need to be under \u003ccode\u003e/etc/nixos\u003c/code\u003e and \u003ccode\u003e$HOME/.config/nixpkgs\u003c/code\u003e, respectively), I went with a single \u003ccode\u003eflake.nix\u003c/code\u003e where both the NixOS and HM configurations live (importing modules to make it more readable, of course).\u003c/p\u003e\n\u003cp\u003eA nice property of having a single flake, is that we can find out all the pinned versions by looking at the \u003ccode\u003eflake.lock\u003c/code\u003e file. We also get to manage everything from a single \u003ccode\u003enix flake\u003c/code\u003e command.\u003c/p\u003e\n\u003ch4 id=\"one-flake-to-rule-them-all\"\u003eOne flake to rule them all!\u003c/h4\u003e\n\u003cp\u003eSo here\u0026rsquo;s the only \u003ccode\u003eflake.nix\u003c/code\u003e that contains both NixOS and HM configurations.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Home Manager (dotfiles) and NixOS configurations\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  inputs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    nixpkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eurl \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;nixpkgs/nixos-unstable\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    nurpkgs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      url \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003egithub:nix-community/NUR\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      inputs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enixpkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efollows \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;nixpkgs\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    home-manager \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      url \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003egithub:nix-community/home-manager\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      inputs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enixpkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efollows \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;nixpkgs\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    tex2nix \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      url \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003egithub:Mic92/tex2nix/4b17bc0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      inputs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eutils\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efollows \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;nixpkgs\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  outputs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e inputs \u003cspan style=\"color:#f92672\"\u003e@\u003c/span\u003e { self\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e nixpkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e nurpkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e home-manager\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e tex2nix }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      system \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;x86_64-linux\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      homeConfigurations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e./outputs/home-conf.nix\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e system nixpkgs nurpkgs home-manager tex2nix;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      );\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      nixosConfigurations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e./outputs/nixos-conf.nix\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e (nixpkgs) lib;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e inputs system;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      );\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      devShell\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003esystem\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e./outputs/installation.nix\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e system nixpkgs;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      );\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt consists of a set of inputs and a set of outputs.\u003c/p\u003e\n\u003cp\u003eTo make things more readable, I moved the corresponding configurations to the \u003ccode\u003eoutputs\u003c/code\u003e directory. So the NixOS configuration now lives under \u003ccode\u003eoutputs/nixos-conf.nix\u003c/code\u003e, and so on.\u003c/p\u003e\n\u003ch4 id=\"you-can-skip-this-installation-shell\"\u003eYou can skip this: installation shell\u003c/h4\u003e\n\u003cp\u003eThere is also a \u003ccode\u003edevShell\u003c/code\u003e with two packages that I use for a fresh installation for custom build script, but that\u0026rsquo;s quite personal so feel free to skip this part.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ system\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e nixpkgs }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  pkgs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e nixpkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elegacyPackages\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003esystem\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkShell {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  name \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;installation-shell\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  buildInputs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ewith\u003c/span\u003e pkgs; [ wget s-tar ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWhat\u0026rsquo;s great is that I can enter this shell without even checking out the project.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix develop github:gvolpe/nix-config\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"home-manager-flake-output\"\u003eHome Manager flake output\u003c/h4\u003e\n\u003cp\u003eThe \u003ccode\u003ehomeConfigurations\u003c/code\u003e is a custom flake output, which is not recognized by Nix flakes, so when we try to display it, it shows \u0026ldquo;unknown\u0026rdquo; as a description but this will probably be supported in the future.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix flake show | rg homeConfigurations\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e├───homeConfigurations: unknown\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eSo what\u0026rsquo;s in the \u003ccode\u003eoutputs/home-conf.nix\u003c/code\u003e? A basic HM configuration might look as follows.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ system\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e nixpkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e nurpkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e home-manager\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  username \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gvolpe\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  homeDirectory \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/home/\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003eusername\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  configHome \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ehomeDirectory\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/.config\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  pkgs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e nixpkgs {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e system;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eallowUnfree \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003exdg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003econfigHome \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e configHome;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    overlays \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ nurpkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eoverlay ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  nur \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e nurpkgs {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e pkgs;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    nurpkgs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pkgs;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  main \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e home-manager\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehomeManagerConfiguration \u003cspan style=\"color:#66d9ef\"\u003erec\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e pkgs system username homeDirectory;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    stateVersion \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;21.03\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    configuration \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e./home.nix\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e nur pkgs;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e (pkgs) config lib stdenv;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWhere \u003ccode\u003e./home.nix\u003c/code\u003e is your usual Home Manager configuration. From there, it will more likely get more complicated, as you will always tweak one piece or another.\u003c/p\u003e\n\u003cp\u003eMine looks very similar, except I have a few more overlays and two home configurations for two different displays. You can look at it directly on the Github repo to avoid repetition in here.\u003c/p\u003e\n\u003cp\u003eThe most confusing part for me was the order of evaluation in Nix (which seems to have changed?). So the overlays I had defined in \u003ccode\u003ehome.nix\u003c/code\u003e were no longer being picked up and I had to define them at the top level.\u003c/p\u003e\n\u003cp\u003eAlso, I had to set the \u003ccode\u003econfig.xdg.configHome\u003c/code\u003e manually when importing \u003ccode\u003enixpkgs\u003c/code\u003e, which was before set in \u003ccode\u003ehome.nix\u003c/code\u003e via the \u003ccode\u003exdg.enable = true;\u003c/code\u003e attribute. I still haven\u0026rsquo;t figured out the right way to do this so I\u0026rsquo;m setting it myself, but if you do know, I\u0026rsquo;d appreciate if you let me know in the comments.\u003c/p\u003e\n\u003ch4 id=\"switching-configurations\"\u003eSwitching configurations\u003c/h4\u003e\n\u003cp\u003eNow to apply a new configuration I was previously running \u003ccode\u003ehome-manager switch\u003c/code\u003e. However, now I prefer to directly build the flake and run the activation script from its result.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix build .#homeConfigurations.gvolpe-hdmi.activationPackage\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ result/activate\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt is more verbose, though, so it is a good idea to have a script for this.\u003c/p\u003e\n\u003ch3 id=\"flake-outputs\"\u003eFlake outputs\u003c/h3\u003e\n\u003cp\u003eThese are all the flake outputs I currently have (you can query the repo directly).\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix flake show github:gvolpe/nix-config\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egithub:gvolpe/nix-config/962a766ab98217aba249f2614592bd5513a267a9\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e├───devShell\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   └───x86_64-linux: development environment \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;installation-shelbash\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e├───homeConfigurations: unknown\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e└───nixosConfigurations\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ├───dell-xps: NixOS configuration\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    └───tongfang-amd: NixOS configuration\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eSo far, I\u0026rsquo;m liking the flakes experience, and I only have words of gratitude for the thousands of contributors who have taken the Nix ecosystem where it is today, and it keeps on getting closer to perfection every day!\u003c/p\u003e\n\u003ch3 id=\"conclusion\"\u003eConclusion\u003c/h3\u003e\n\u003cp\u003eI can only say one thing about my Nix configuration affairs: \u003cstrong\u003eit ain\u0026rsquo;t over yet.\u003c/strong\u003e I\u0026rsquo;m still figuring things out and learning new stuff on a daily basis, so I\u0026rsquo;m sure there will be plenty of changes in the near future, specially considering that flakes are still marked as experimental.\u003c/p\u003e\n\u003cp\u003eAnyway, that\u0026rsquo;s all I have to say. Thanks for stopping by and have a look at my \u003ca href=\"https://github.com/gvolpe/nix-config\"\u003eNix configuration files\u003c/a\u003e, perhaps something in there helps you get that missing piece in yours :)\u003c/p\u003e\n\u003cp\u003eCheers,\nGabriel.\u003c/p\u003e\n","date":"2022-01-10","dateFormatted":"2022.01.10","excerpt":"I have recently migrated my entire NixOS and Home Manager (HM) configuration \u0026mdash; including programs, services, dotfiles, etc \u0026mdash; over to the new kid on …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/nix-flakes/","readingTime":6,"slug":"nix-flakes","subtitle":null,"summary":"\u003cp\u003eI have recently migrated my entire NixOS and Home Manager (HM) \u003ca href=\"https://github.com/gvolpe/nix-config\"\u003econfiguration\u003c/a\u003e \u0026mdash; including programs, services, dotfiles, etc \u0026mdash; over to the new kid on the block: \u003ca href=\"https://nixos.wiki/wiki/Flakes\"\u003eNix flakes\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eIt was not as difficult as I thought it would be but there were a lot of things I had to figure out on my own or by asking more experienced folks on the NixOS matrix channel.\u003c/p\u003e\n\u003cp\u003eSo let me tell you the important bits of this migration story in this short blog post ;)\u003c/p\u003e","tags":["nix","nixos","home-manager","flakes"],"title":"Flakes: NixOS and Home Manager migration","url":"https://gvolpe.com/blog/nix-flakes/","wordCount":1165},{"comments":true,"content":"\u003cp\u003eWhat if I told you that you can save plenty of time and CPU-power by pre-building your \u003cem\u003eentire\u003c/em\u003e \u003ca href=\"https://nixos.org/\"\u003eNixOS\u003c/a\u003e configuration on Github actions? Fresh installations could be super fast and pre-validated on a CI build!\u003c/p\u003e\n\u003cp\u003eWell, it\u0026rsquo;s possible, and it\u0026rsquo;s what I\u0026rsquo;m \u003ca href=\"https://github.com/gvolpe/nix-config\"\u003ecurrently doing\u003c/a\u003e with my NixOS and \u003ca href=\"https://github.com/nix-community/home-manager\"\u003eHome Manager\u003c/a\u003e configurations.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/ci-badges.png\" alt=\"badges\"\u003e\u003c/p\u003e\n\u003cp\u003eBesides hosting your configuration on Github, you\u0026rsquo;ll need a binary cache where the results of your build can be pushed to be re-used later on. One of the best free alternatives is \u003ca href=\"https://www.cachix.org/\"\u003eCachix\u003c/a\u003e, offering up to 10GB on their basic tier.\u003c/p\u003e\n\u003ch3 id=\"home-manager\"\u003eHome Manager\u003c/h3\u003e\n\u003cp\u003eMy Home Manager configuration builds in about 5 minutes.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/ci-home.png\" alt=\"home\"\u003e\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s the CI job definition for my home configuration.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-yaml\" data-lang=\"yaml\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003ejobs\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003ebuild\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003eruns-on\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003eubuntu-18.04\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003esteps\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      - \u003cspan style=\"color:#f92672\"\u003euses\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003eactions/checkout@v2.3.2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      - \u003cspan style=\"color:#f92672\"\u003ename\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Get pinned nixpkgs ❄️\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003eid\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003epinned_nixpkgs\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003erun\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003eecho \u0026#34;::set-output name=url::$(cat ./pinned/nixpkgs)\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      - \u003cspan style=\"color:#f92672\"\u003ename\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Install Nix ❄️\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003euses\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003ecachix/install-nix-action@v13\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003ewith\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#f92672\"\u003enix_path\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;nixpkgs=$\\{\\{ steps.pinned_nixpkgs.outputs.url \\}}\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      - \u003cspan style=\"color:#f92672\"\u003ename\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Install Cachix ❄️\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003euses\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003ecachix/cachix-action@v10\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003ewith\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#f92672\"\u003ename\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003egvolpe-nixos\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#f92672\"\u003eauthToken\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;$\\{\\{ secrets.CACHIX_AUTH_TOKEN \\}}\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#75715e\"\u003e# Needed because cachix is also installed by Home Manager\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      - \u003cspan style=\"color:#f92672\"\u003ename\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Set priority flag for Cachix 🚩\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003erun\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003enix-env --set-flag priority 0 cachix\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      - \u003cspan style=\"color:#f92672\"\u003ename\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Build Home Manager config 🏠\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003erun\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e./build.sh ci-home\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe first relevant part is the reading of the pinned nixpkgs file, which looks as follows:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ cat pinned/nixpkgs\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e───────┬──────────────────────────────────────────────────────────────\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e       │ File: pinned/nixpkgs\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e───────┼──────────────────────────────────────────────────────────────\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e   \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e   │ https://github.com/NixOS/nixpkgs/archive/8ecc61c91a5.tar.gz\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis is then read to install Nix with this specific version.\u003c/p\u003e\n\u003cp\u003eThe second relevant part is the priority flag for Cachix. This is needed in my case, because my Home Manager configuration includes Cachix as one of the programs to install, but we already have it as part of the \u003ccode\u003ecachix-action\u003c/code\u003e, so we give it the priority before proceeding with the build.\u003c/p\u003e\n\u003cp\u003eNext, the shell script does the following (in addition to the creation of some directories specific to my needs):\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix-shell -p nix-build-uncached --run nix-build-uncached\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe \u003ca href=\"https://github.com/Mic92/nix-build-uncached\"\u003enix-build-uncached\u003c/a\u003e command saves time by not re-building what\u0026rsquo;s already present in the binary cache, which cuts about 1 minute of building time, in this case.\u003c/p\u003e\n\u003cp\u003eIt mostly accepts the same arguments as \u003ccode\u003enix-build\u003c/code\u003e, so you can use it wherever you use the former. If you\u0026rsquo;re familiar with it, you know it needs a \u003ccode\u003edefault.nix\u003c/code\u003e at the root directory, if no file name is indicated. This is where it gets interesting, because we usually build our home configuration by running \u003ccode\u003ehome-manager switch\u003c/code\u003e, so there\u0026rsquo;s no such file!\u003c/p\u003e\n\u003cp\u003eFortunately for us, it is possible to get a derivation out of Home Manager. First of all, we define a \u003ccode\u003edefault.nix\u003c/code\u003e with two attributes.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ pkgs \u003cspan style=\"color:#f92672\"\u003e?\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026lt;nixpkgs\u0026gt;\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    config \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e { allowUnfree \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e; };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  home \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecallPackage \u003cspan style=\"color:#e6db74\"\u003e./home\u003c/span\u003e {};\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  system \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecallPackage \u003cspan style=\"color:#e6db74\"\u003e./system\u003c/span\u003e {};\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis will allow us to build all the attributes, or just the one we are interested in. E.g.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix-build-uncached -A home\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAt last, here\u0026rsquo;s the derivation for Home Manager, placed under \u003ccode\u003ehome/default.nix\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ pkgs }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  hm_url \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efileContents \u003cspan style=\"color:#e6db74\"\u003e../pinned/home-manager\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  home-manager \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e builtins\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efetchTarball {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    name   \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;home-manager-2021-08-21\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    url    \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e hm_url;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    sha256 \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;0s8nlgrf16bz2bhnk0xrzvivagq55cifxf2p545c7n4zj9ryfkkp\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  evalHome \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003etoString home-manager\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/modules\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  home-config \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erecurseIntoAttrs (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    evalHome {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      configuration \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e./home.nix\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      lib \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elib;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      pkgs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pkgs;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  );\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#75715e\"\u003e# Allow nix to recurse into this attribute set to look for derivations\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  recurseForDerivations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe \u003ccode\u003epinned/home-manager\u003c/code\u003e file is similar to the \u003ccode\u003epinned/nixpkgs\u003c/code\u003e file, except it points to a specific Home Manager tarball.\u003c/p\u003e\n\u003cp\u003eAll credits go to \u003ca href=\"https://github.com/zimbatm\"\u003eJonas Chevalier\u003c/a\u003e for showing me the way!\u003c/p\u003e\n\u003ch3 id=\"nixos\"\u003eNixOS\u003c/h3\u003e\n\u003cp\u003eThe system part is equally interesting but easier to build. It takes about 3 minutes.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/ci-nixos.png\" alt=\"nixos\"\u003e\u003c/p\u003e\n\u003cp\u003eThe steps are pretty similar, except there is no need to set the priority flag for Cachix.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-yaml\" data-lang=\"yaml\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e- \u003cspan style=\"color:#f92672\"\u003ename\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Build NixOS config ❄️\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003erun\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e./build.sh ci-system\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eNext we have the derivation for the system part, defined under \u003ccode\u003esystem/default.nix\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ pkgs }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  system \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erecurseIntoAttrs (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enixos [ \u003cspan style=\"color:#e6db74\"\u003e./configuration.nix\u003c/span\u003e ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  );\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  recurseForDerivations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"conclusion\"\u003eConclusion\u003c/h3\u003e\n\u003cp\u003eThat\u0026rsquo;s all it takes to build your entire operating system and user configuration on Github actions! I find it useful to always keep my configuration up-to-date as well as speeding up big upgrades and fresh installations (which are actually more rare in my case).\u003c/p\u003e\n\u003cp\u003eMy Nix configuration files can be found at \u003ca href=\"https://github.com/gvolpe/nix-config\"\u003ehttps://github.com/gvolpe/nix-config\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eWhat\u0026rsquo;s stopping you from doing the same? :)\u003c/p\u003e\n\u003cp\u003eCheers,\nGabriel.\u003c/p\u003e\n","date":"2021-08-23","dateFormatted":"2021.08.23","excerpt":"What if I told you that you can save plenty of time and CPU-power by pre-building your entire NixOS configuration on Github actions? Fresh installations could …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/nixos-binary-cache-ci/","readingTime":4,"slug":"nixos-binary-cache-ci","subtitle":null,"summary":"\u003cp\u003eWhat if I told you that you can save plenty of time and CPU-power by pre-building your \u003cem\u003eentire\u003c/em\u003e \u003ca href=\"https://nixos.org/\"\u003eNixOS\u003c/a\u003e configuration on Github actions? Fresh installations could be super fast and pre-validated on a CI build!\u003c/p\u003e\n\u003cp\u003eWell, it\u0026rsquo;s possible, and it\u0026rsquo;s what I\u0026rsquo;m \u003ca href=\"https://github.com/gvolpe/nix-config\"\u003ecurrently doing\u003c/a\u003e with my NixOS and \u003ca href=\"https://github.com/nix-community/home-manager\"\u003eHome Manager\u003c/a\u003e configurations.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/ci-badges.png\" alt=\"badges\"\u003e\u003c/p\u003e\n\u003cp\u003eBesides hosting your configuration on Github, you\u0026rsquo;ll need a binary cache where the results of your build can be pushed to be re-used later on. One of the best free alternatives is \u003ca href=\"https://www.cachix.org/\"\u003eCachix\u003c/a\u003e, offering up to 10GB on their basic tier.\u003c/p\u003e","tags":["nix","nixos","home-manager","github-actions"],"title":"NixOS: build your system on Github actions!","url":"https://gvolpe.com/blog/nixos-binary-cache-ci/","wordCount":705},{"comments":true,"content":"\u003cp\u003eAs the title says, finite-state machines and Fs2 streams are a match made in heaven! To demonstrate it, I\u0026rsquo;ll make up a compelling problem to solve and we will get to the final solution step by step. Here\u0026rsquo;s a sneak-peek of the solution.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etrait\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTicker\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]]\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e get\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e merge\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003etimerTick\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e count\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCount\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eCount\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e ticks\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eStream\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEngine\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eConcurrent:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eParallel:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTime:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTimer\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    publish\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eSummary\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e F\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ticker\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTicker\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eprivate\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eval\u003c/span\u003e fsm \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEngine\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efsm\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003eticker\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e run\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePipe\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eEvent\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enoneTerminate\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ezip\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eticker\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eticks\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eevalMapAccumulate\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eMap\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eempty\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ePlayerId\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eAgg\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)(\u003c/span\u003efsm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erun\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecollect \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eout\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOn\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e out \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eevalMap \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e m \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        F\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etimestamp\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eflatMap \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e ts \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          m\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etoList\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eparTraverse_ \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003epid\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e agg\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e publish\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eagg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esummary\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003epid\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e ts\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eInterested in seeing more? Continue reading or browse the \u003ca href=\"https://github.com/gvolpe/fsm-streams\"\u003esource code\u003c/a\u003e on your own :)\u003c/p\u003e\n\u003ch3 id=\"problem\"\u003eProblem\u003c/h3\u003e\n\u003cp\u003eWe have an adventure game called \u0026ldquo;Streaming Adventures\u0026rdquo; where a single player is the main star. A new player starts up at level zero and score zero, and it can only win once level 50 has been cleared. This is how we will model the domain in Scala.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eGame\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  playerId\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePlayerId\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e// UUID\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e  playerScore\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePlayerScore\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e// Int\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e  level\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLevel\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e// Int\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e  gems\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eMap\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eGemType\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eInt\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ePlayer\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  id\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePlayerId\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e// UUID\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e  highestScore\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePlayerScore\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e// Int\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e  gems\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eMap\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eGemType\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eInt\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  memberSince\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTimestamp\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e// Instant\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003esealed\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrait\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eGemType\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eobject\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eGemType\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eobject\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eDiamond\u003c/span\u003e  \u003cspan style=\"color:#66d9ef\"\u003eextends\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eGemType\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eobject\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEmerald\u003c/span\u003e  \u003cspan style=\"color:#66d9ef\"\u003eextends\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eGemType\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eobject\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eRuby\u003c/span\u003e     \u003cspan style=\"color:#66d9ef\"\u003eextends\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eGemType\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eobject\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eSapphire\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eextends\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eGemType\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cem\u003eNOTE\u003c/em\u003e: For conciseness, newtypes such as \u003ccode\u003ePlayerId\u003c/code\u003e and \u003ccode\u003eLevel\u003c/code\u003e will be omitted on this post but you can find them in the corresponding \u003ca href=\"https://github.com/gvolpe/fsm-streams\"\u003erepository\u003c/a\u003e.*\u003c/p\u003e\n\u003cp\u003eThe score can be raised by either collecting gems or solving different puzzles along the way, as well as leveling up. We will model it as an event-driven system with three possible events.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003esealed\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrait\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEvent\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e createdAt\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTimestamp\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eobject\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEvent\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eLevelUp\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    playerId\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePlayerId\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    newLevel\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLevel\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    createdAt\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTimestamp\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eextends\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEvent\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ePuzzleSolved\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    playerId\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePlayerId\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    puzzleName\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePuzzleName\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    duration\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eFiniteDuration\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    createdAt\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTimestamp\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eextends\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEvent\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eGemCollected\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    playerId\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePlayerId\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    gemType\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eGemType\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    createdAt\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTimestamp\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eextends\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEvent\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eLet\u0026rsquo;s pretend the game\u0026rsquo;s logic has been completed and the game is already playable. Our job is to calculate both the \u003cstrong\u003eplayer\u0026rsquo;s score\u003c/strong\u003e and to keep track of the amount of \u003cstrong\u003ecollected gems\u003c/strong\u003e (grouped by type) based on the events defined above. Here\u0026rsquo;s a table showing how many points an event is worth it.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-console\" data-lang=\"console\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e╔══════════════╦════════╗\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e║    Event     ║ Points ║\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e╠══════════════╬════════╣\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e║ GemCollected ║   10   ║\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e║ PuzzleSolved ║   50   ║\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e║ LevelUp      ║   100  ║\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e╚══════════════╩════════╝\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAnother important point to consider is that all these requirements are solely for analytics purposes for all players and concurrent games.\u003c/p\u003e\n\u003ch3 id=\"solution\"\u003eSolution\u003c/h3\u003e\n\u003cp\u003eWe are going to solve it by aggregating all the incoming events, grouped by \u003ccode\u003ePlayerId\u003c/code\u003e, within a given time window or a certain amount of processed events, and then emit a \u003ccode\u003eSummary\u003c/code\u003e that will be processed downstream. We could probably solve it by only using a time window, though, by considering the number of processed events, we can limit how much memory we use.\u003c/p\u003e\n\u003cp\u003eAs you have seen at the beginning of this post, the solution involves an aggregation datatype and a finite-state machine, so these are the ones we will analyze first.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eevalMapAccumulate\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eMap\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eempty\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ePlayerId\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eAgg\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)(\u003c/span\u003efsm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erun\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"aggregation-datatype\"\u003eAggregation datatype\u003c/h4\u003e\n\u003cp\u003eWithout further ado, here\u0026rsquo;s the definition of \u003ccode\u003eAgg\u003c/code\u003e, a simple datatype used for data aggregation.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e monocle.macros._\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eAgg\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    level\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLevel\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    points\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePoints\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    gems\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eMap\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eGemType\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eInt\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e summary\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003epid\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePlayerId\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e ts\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTimestamp\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eSummary\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eSummary\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003epid\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e level\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e points\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e gems\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e ts\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eobject\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eAgg\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e empty \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eAgg\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eLevel\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ePoints\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eMap\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eempty\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eval\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003e_Gems\u003c/span\u003e   \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eGenLens\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eAgg\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egems\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eval\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003e_Level\u003c/span\u003e  \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eGenLens\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eAgg\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elevel\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eval\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003e_Points\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eGenLens\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eAgg\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epoints\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt also comes packed with a bunch of useful lenses and convenient functions.\u003c/p\u003e\n\u003ch4 id=\"finite-state-machine\"\u003eFinite-state machine\u003c/h4\u003e\n\u003cp\u003eQuoting the \u003ca href=\"https://en.wikipedia.org/wiki/Finite-state_machine\"\u003eWikipedia\u003c/a\u003e:\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '📜';\"\u003e\n  \u003ch4\u003eWikipedia\u003c/h4\u003e\n  \u003cp\u003e\n\"A finite-state machine (FSM) or finite-state automaton (FSA, plural: automata), finite automaton, or simply a state machine, is a mathematical model of computation. It is an abstract machine that can be in exactly one of a finite number of states at any given time. The FSM can change from one state to another in response to some inputs; the change from one state to another is called a transition. An FSM is defined by a list of its states, its initial state, and the inputs that trigger each transition. Finite-state machines are of two types—deterministic finite-state machines and non-deterministic finite-state machines. A deterministic finite-state machine can be constructed equivalent to any non-deterministic one.\"\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cp\u003eHere\u0026rsquo;s how we can possibly define it in Scala, which is the implementation we will be using further down.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eFSM\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eS\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eI\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eO\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003erun\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eS\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eI\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e F\u003cspan style=\"color:#f92672\"\u003e[(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eS\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eO\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eBreaking it down:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eF[_]\u003c/code\u003e: a higher-kinded type.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eS\u003c/code\u003e: the possible states this machine can be in at a given time.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eI\u003c/code\u003e: the input type.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eO\u003c/code\u003e: the output type.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eFinally, the \u003ccode\u003erun\u003c/code\u003e function represents the \u003cstrong\u003etransition\u003c/strong\u003e from a given state and input to another state and output, in the context of \u003ccode\u003eF\u003c/code\u003e. For example, a pure state-machine can be defined using the identity monad.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e cats.Id\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eobject\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eFSM\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e identity\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eS\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eI\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eO\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003erun\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eS\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eI\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eId\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eS\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eO\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)])\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eFSM\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003erun\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eTo solve our problem, we will use a \u003ca href=\"https://en.wikipedia.org/wiki/Mealy_machine\"\u003eMealy State Machine\u003c/a\u003e, which is a specific kind of FSM whose output values are determined both by its current state and the current inputs. Finite-state machines hold many other different properties but I\u0026rsquo;ll leave this great topic for another day. Right now, we have all we need to solve the problem at hand.\u003c/p\u003e\n\u003cp\u003eLet\u0026rsquo;s see how we can represent all the valid state transitions for our use case by using our \u003ccode\u003eFSM\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eResult\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eMap\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ePlayerId\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eAgg\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e],\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eState\u003c/span\u003e  \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eMap\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ePlayerId\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eAgg\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e],\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eCount\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e fsm\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eApplicative\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ticker\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTicker\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eFSM\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eState\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eOption\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eEvent\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eResult\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eFSM\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e((\u003c/span\u003em\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e count\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eSome\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eevent\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e tick\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eval\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eplayerId\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e modifier\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      event \u003cspan style=\"color:#66d9ef\"\u003ematch\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEvent\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eLevelUp\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003epid\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e level\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          pid \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003e_Points\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emodify\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e100\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e).\u003c/span\u003eandThen\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003e_Level\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eset\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003elevel\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEvent\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ePuzzleSolved\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003epid\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          pid \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003e_Points\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emodify\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e50\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEvent\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eGemCollected\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003epid\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e gemType\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          pid \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003e_Points\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emodify\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e).\u003c/span\u003eandThen \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003e_Gems\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emodify\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eupdatedWith\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003egemType\u003cspan style=\"color:#f92672\"\u003e)(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emap\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e).\u003c/span\u003eorElse\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eSome\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e))))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eval\u003c/span\u003e agg \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e m\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egetOrElse\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eplayerId\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eAgg\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eempty\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eval\u003c/span\u003e out \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e m\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eupdated\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eplayerId\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e modifier\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eagg\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eval\u003c/span\u003e nst \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003etick \u003cspan style=\"color:#f92672\"\u003e===\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOn\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eMap\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eempty\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ePlayerId\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eAgg\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e out\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ticker\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emerge\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003etick\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e count\u003cspan style=\"color:#f92672\"\u003e).\u003c/span\u003emap \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003enewTick\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e newCount\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003enst \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e newCount\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eout \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e newTick\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e((\u003c/span\u003em\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eNone\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    F\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epure\u003cspan style=\"color:#f92672\"\u003e((\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eMap\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eempty \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003em \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOn\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eFocus on the state transitions. In the first case, we get \u003ccode\u003eSome(event)\u003c/code\u003e and then proceed to pattern-match on the \u003ccode\u003eEvent\u003c/code\u003e. We increase the points accordingly, and in specific cases, we modify some other properties. Lastly, we emit the new state and ouput. You can ignore the \u003ccode\u003eticker.merge\u003c/code\u003e part for now, only know that it\u0026rsquo;s managing the ticks either by time-window or a certain number of processed events. We will get into the \u003ccode\u003eTicker\u003c/code\u003e implementation soon.\u003c/p\u003e\n\u003cp\u003eThe last case requires a bit more of explanation. Because the input type of our FSM is \u003ccode\u003e(Option[Event], Tick)\u003c/code\u003e, we need to consider the \u003ccode\u003eNone\u003c/code\u003e case, which means our input data has come to an end in a streaming context (more on this soon).\u003c/p\u003e\n\u003cp\u003eA great property of finite-state machines is that they contain pure logic and can be tested in isolation. So let\u0026rsquo;s do that first before moving on.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etest\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;FSM specification\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  forAll\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003egenGemCollected\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e genPuzzleSolved\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e genLevelUp\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ee1\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e e2\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e e3\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eval\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003est1 \u003cspan style=\"color:#66d9ef\"\u003e@\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eres1\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e count1\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eout1\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e tick1\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e fsm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erun\u003cspan style=\"color:#f92672\"\u003e((\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eMap\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eempty \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eSome\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ee1\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOff\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ecount1\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003etick1\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOff\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eout1\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e res1\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003egems\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eres1\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003elevel\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eres1\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003epoints\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eres1\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eval\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003est2 \u003cspan style=\"color:#66d9ef\"\u003e@\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eres2\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e count2\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eout2\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e tick2\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e fsm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erun\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003est1\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eSome\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ee2\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOff\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ecount2\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003etick2\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOff\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eout2\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e res2\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003egems\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eres2\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003elevel\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eres2\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003epoints\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eres2\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e60\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eval\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003est3 \u003cspan style=\"color:#66d9ef\"\u003e@\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eres3\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e count3\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eout3\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e tick3\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e fsm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erun\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003est2\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eSome\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ee3\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOff\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ecount3\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003etick3\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOff\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eout3\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e res3\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003egems\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eres3\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003elevel\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eres3\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e e3\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enewLevel\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003evalue\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003epoints\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eres3\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e160\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// at the end of the stream, both the counter and the tick timer are reseted\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e    \u003cspan style=\"color:#66d9ef\"\u003eval\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003est4 \u003cspan style=\"color:#66d9ef\"\u003e@\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eres4\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e count4\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eout4\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e tick4\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e fsm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erun\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003est3\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eNone\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOff\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ecount4\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003etick4\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOn\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assert\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eres4\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eisEmpty\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003egems\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eout4\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003elevel\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eres4\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003epoints\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eout4\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e160\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eval\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e((\u003c/span\u003eres5\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e count5\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eout5\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e tick5\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e fsm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erun\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003est4\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eSome\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ee1\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOff\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ecount5\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003etick5\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOff\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eout5\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e res5\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003egems\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eres5\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003elevel\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eres5\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    assertEquals\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003epoints\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eres5\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eLet\u0026rsquo;s break it apart.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eWe run our FSM with an initial empty state and our first event: \u003ccode\u003eGemCollected\u003c/code\u003e. Once we get the result, we run a few assertions on it. The two most important ones are that the total number of gems equals one and the total amount of points equals ten.\u003c/li\u003e\n\u003cli\u003eWe run our FSM with the previous state, namely \u003ccode\u003est1\u003c/code\u003e, and a second event: \u003ccode\u003ePuzzleSolved\u003c/code\u003e. The procedure is mostly the same. The most important assertions are that we still keep the previous gem and that our points have been increased to sixty.\u003c/li\u003e\n\u003cli\u003eWe run our FSM with the previous state, namely \u003ccode\u003est2\u003c/code\u003e, and a third event: \u003ccode\u003eLevelUp\u003c/code\u003e. In addition to the previous assertions, we also verify that a new level has been set.\u003c/li\u003e\n\u003cli\u003eWe run our FSM with the previous state, namely \u003ccode\u003est3\u003c/code\u003e, and no event (\u003ccode\u003eNone\u003c/code\u003e). In addition to the previous assertions, we also verify that counter has been reseted and the tick is now on.\u003c/li\u003e\n\u003cli\u003eLastly, we run our FSM with the previous state, namely \u003ccode\u003est4\u003c/code\u003e, and the first event once again: \u003ccode\u003eGemCollected\u003c/code\u003e. The assertion is now identical to our first case.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eYou can find more unit tests for our FSM in the \u003ca href=\"https://github.com/gvolpe/fsm-streams\"\u003erepository\u003c/a\u003e.\u003c/p\u003e\n\u003ch4 id=\"streaming\"\u003eStreaming\u003c/h4\u003e\n\u003cp\u003eIf we give it a closer look, we will recognize the type signature of \u003ca href=\"https://github.com/typelevel/fs2/blob/v2.4.5/core/shared/src/main/scala/fs2/Stream.scala#L1705\"\u003emapAccumulate\u003c/a\u003e as a finite-state machine lifted into a streaming context.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e mapAccumulate\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eS\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eO2\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003einit\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eS\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ef\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eS\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eO\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eS\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e O2\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eStream\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eS\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eO2\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThere\u0026rsquo;s also \u003ccode\u003eevalMapAccumulate\u003c/code\u003e for dealing with effectful state transitions, which is the one we will be using in our solution. Let\u0026rsquo;s bring it up again and describe each step in detail.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEngine\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eConcurrent:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eParallel:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTime:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTimer\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    publish\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eSummary\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e F\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ticker\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTicker\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eprivate\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eval\u003c/span\u003e fsm \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEngine\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efsm\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003eticker\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e run\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePipe\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eEvent\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enoneTerminate\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ezip\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eticker\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eticks\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eevalMapAccumulate\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eMap\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eempty\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ePlayerId\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eAgg\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)(\u003c/span\u003efsm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erun\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecollect \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eout\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOn\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e out \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eevalMap \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e m \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        F\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etimestamp\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eflatMap \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e ts \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          m\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etoList\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eparTraverse_ \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003epid\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e agg\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e publish\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eagg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esummary\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003epid\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e ts\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eTo begin with, we have a \u003ccode\u003ePipe[F, Event, Unit]\u003c/code\u003e, which is merely an alias for a function \u003ccode\u003eStream[F, Event] =\u0026gt; Stream[F, Unit]\u003c/code\u003e. Firstly, we call \u003ccode\u003enoneTerminate\u003c/code\u003e, which will lift our \u003ccode\u003eEvent\u003c/code\u003e into an \u003ccode\u003eOption[Event]\u003c/code\u003e, giving us a \u003ccode\u003eStream[F, Option[Event]]\u003c/code\u003e. The reason for doing so, is that we want to accumulate and aggregate events in memory before emitting a summary but, in case the stream terminates, that last aggregation won\u0026rsquo;t be emitted. So we handle this case with the \u003ccode\u003eNone\u003c/code\u003e case in our state machine.\u003c/p\u003e\n\u003cp\u003eNext, we \u003ccode\u003ezip\u003c/code\u003e our input with the output of a \u003ccode\u003eStream[F, Tick]\u003c/code\u003e, yielding a \u003ccode\u003eStream[F, (Option[Event], Tick)]\u003c/code\u003e. This is effectively the input type of our state machine. A tick will be produced either when the given time window has passed or when the number of processed events has been reached, whichever happens first. We will later dive into the \u003ccode\u003eTicker\u003c/code\u003e implementation.\u003c/p\u003e\n\u003cp\u003eContinuing, we \u003ccode\u003eevalMapAccumulate\u003c/code\u003e with an initial state: an empty map and counter set to zero. As mentioned before, both \u003ccode\u003emapAccumulate\u003c/code\u003e and \u003ccode\u003eevalMapAccumulate\u003c/code\u003e fit the type signature of a finite-state machine, so here we call \u003ccode\u003efsm.run\u003c/code\u003e. These events will be aggregated in memory until our tick becomes \u003ccode\u003eTick.On\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eWhich takes us to \u003ccode\u003ecollect { case (_, (out, Tick.On)) =\u0026gt; out }\u003c/code\u003e. The current aggregation will be only emitted when we get a tick.\u003c/p\u003e\n\u003cp\u003eFinally, we create a timestamp - which comes from a custom \u003ccode\u003eTime\u003c/code\u003e interface - and proceed to publish a \u003ccode\u003eSummary\u003c/code\u003e for every player, by using the given \u003ccode\u003epublish\u003c/code\u003e function.\u003c/p\u003e\n\u003ch4 id=\"boolean-blindness\"\u003eBoolean blindness\u003c/h4\u003e\n\u003cp\u003e\u003ccode\u003eTick\u003c/code\u003e is a simple datatype used to avoid \u003ca href=\"https://runtimeverification.com/blog/code-smell-boolean-blindness/\"\u003eboolean blindness\u003c/a\u003e. However, it also forms a \u003ccode\u003eSemigroup\u003c/code\u003e. In the next section, you will understand how this is useful.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e cats.\u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eEq\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eSemigroup\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003esealed\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrait\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eobject\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eobject\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eOn\u003c/span\u003e  \u003cspan style=\"color:#66d9ef\"\u003eextends\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eobject\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eOff\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eextends\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eimplicit\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eval\u003c/span\u003e eq\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eEq\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEq\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efromUniversalEquals\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eimplicit\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eval\u003c/span\u003e semigroup\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eSemigroup\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003enew\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eSemigroup\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e combine\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ex\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e y\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ex\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e y\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ematch\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOn\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e    \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eOn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eOn\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e    \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eOn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOff\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eOff\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eOff\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"ticker\"\u003eTicker\u003c/h4\u003e\n\u003cp\u003eFs2 provides a function named \u003ca href=\"https://github.com/typelevel/fs2/blob/v2.5.0/core/shared/src/main/scala/fs2/Stream.scala#L3187\"\u003eevery\u003c/a\u003e, which constantly emits a tick expressed as a \u003ccode\u003eboolean\u003c/code\u003e. Whenever the specified duration is reached, it evaluates to \u003ccode\u003etrue\u003c/code\u003e. Otherwise, it evaluates to \u003ccode\u003efalse\u003c/code\u003e. However, we can\u0026rsquo;t make use of it because we also need to consider the number of events that have been processed so far. Whatever happens first, both the tick\u0026rsquo;s timer and the events counter need to be reseted. Here\u0026rsquo;s where our custom \u003ccode\u003eTicker\u003c/code\u003e comes into play.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etrait\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTicker\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]]\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e get\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e merge\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003etimerTick\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e count\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCount\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eCount\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e ticks\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eStream\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eobject\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTicker\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCount\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eInt\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e create\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eConcurrent:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTimer\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      maxNrOfEvents\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eInt\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      timeWindow\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eFiniteDuration\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eTicker\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eRef\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eof\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOff\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e).\u003c/span\u003emap \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e ref \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003enew\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTicker\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e get\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e ref\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e merge\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003etimerTick\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e count\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCount\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eCount\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          ref\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emodify \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e              \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOff\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e count \u003cspan style=\"color:#f92672\"\u003e===\u003c/span\u003e maxNrOfEvents \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOn\u003c/span\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e              \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e timerTick \u003cspan style=\"color:#f92672\"\u003e===\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOn\u003c/span\u003e          \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOff\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e              \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e                                   \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOff\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ecount \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eflatMap \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e newCount \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e              get\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emap \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e counterTick \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#66d9ef\"\u003eval\u003c/span\u003e newTick \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e counterTick \u003cspan style=\"color:#f92672\"\u003e|+|\u003c/span\u003e timerTick\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                newTick \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e newCount\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e              \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e ticks\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eStream\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#66d9ef\"\u003eval\u003c/span\u003e duration \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e timeWindow\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etoNanos\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#66d9ef\"\u003eval\u003c/span\u003e interval \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eFiniteDuration\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e((\u003c/span\u003etimeWindow\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etoSeconds \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0.05\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e).\u003c/span\u003etoLong\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eSECONDS\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e).\u003c/span\u003etoNanos\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e go\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003elastSpikeNanos\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLong\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eStream\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eStream\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eeval\u003cspan style=\"color:#f92672\"\u003e((\u003c/span\u003eF\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emonotonic\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eNANOSECONDS\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e),\u003c/span\u003e get\u003cspan style=\"color:#f92672\"\u003e).\u003c/span\u003etupled\u003cspan style=\"color:#f92672\"\u003e).\u003c/span\u003eflatMap \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e              \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003enow\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e tick\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e((\u003c/span\u003enow \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e lastSpikeNanos\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e duration \u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003etick \u003cspan style=\"color:#f92672\"\u003e===\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003enow \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e lastSpikeNanos\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e interval\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                  \u003cspan style=\"color:#a6e22e\"\u003eStream\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eemit\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOn\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003e go\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003enow\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eStream\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eemit\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOff\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003e go\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003elastSpikeNanos\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          go\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e).\u003c/span\u003etail\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn the \u003ca href=\"https://github.com/gvolpe/fsm-streams\"\u003eGithub repository\u003c/a\u003e, you will also find scaladocs on each method and unit tests for this implementation. Though, in the meantime, let\u0026rsquo;s analyze each function.\u003c/p\u003e\n\u003cp\u003eOur first function, \u003ccode\u003eget\u003c/code\u003e, is the simplest one. It retrieves the current state of the counter tick.\u003c/p\u003e\n\u003cp\u003eNext, we have \u003ccode\u003emerge\u003c/code\u003e, which takes a timer tick and a count and returns a tuple \u003ccode\u003e(Tick, Count)\u003c/code\u003e, possibly running some effects. In a nutshell, this function is the responsible for resetting both the counter and time ticks, as well as keeping track of the count. Ultimately, the tick response we get is the result of the combination of the counter tick and the timer tick by relying on the \u003ccode\u003eSemigroup[Tick]\u003c/code\u003e instance we\u0026rsquo;ve seen before.\u003c/p\u003e\n\u003cp\u003eFinally, our \u003ccode\u003eticks\u003c/code\u003e function is the analog to the \u003ccode\u003eStream.every\u003c/code\u003e function that also considers the current state of the counter ticks. It also emits a \u003ccode\u003eTick\u003c/code\u003e instead of a \u003ccode\u003eboolean\u003c/code\u003e.\u003c/p\u003e\n\u003ch3 id=\"credits\"\u003eCredits\u003c/h3\u003e\n\u003cp\u003eInitially, my first solution was based on \u003ccode\u003eevalMapAccumulate\u003c/code\u003e + \u003ccode\u003edebounce\u003c/code\u003e + \u003ccode\u003eRef\u003c/code\u003e but after asking for feedback, \u003ca href=\"https://github.com/wemrysi\"\u003eEmrys Ingersoll\u003c/a\u003e came up with a solution based on \u003ccode\u003enoneTerminate\u003c/code\u003e + \u003ccode\u003emapAccumulate\u003c/code\u003e + \u003ccode\u003ecollect\u003c/code\u003e, which is the one I used for the final solution so credits where due, thanks again Emrys!\u003c/p\u003e\n\u003ch3 id=\"summary\"\u003eSummary\u003c/h3\u003e\n\u003cp\u003eThis was a really fun problem to solve! I hope you have enjoyed reading about it. As a bonus track, here are some graphics showing how this performs under heavy load of events coming in from an Apache Pulsar topic.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/metrics1.png\" alt=\"m1\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/metrics2.png\" alt=\"m1\"\u003e\u003c/p\u003e\n\u003cp\u003eI can\u0026rsquo;t disclose more information than you can see in these JVM metrics but the GC pauses are interesting to observe, which makes sense given that we allocate quite a lot of objects in memory during the aggregation time window.\u003c/p\u003e\n\u003cp\u003eCheers,\nGabriel.\u003c/p\u003e\n","date":"2020-12-29","dateFormatted":"2020.12.29","excerpt":"As the title says, finite-state machines and Fs2 streams are a match made in heaven! To demonstrate it, I\u0026rsquo;ll make up a compelling problem to solve and we …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/fsm-fs2-a-match-made-in-heaven/","readingTime":12,"slug":"fsm-fs2-a-match-made-in-heaven","subtitle":null,"summary":"\u003cp\u003eAs the title says, finite-state machines and Fs2 streams are a match made in heaven! To demonstrate it, I\u0026rsquo;ll make up a compelling problem to solve and we will get to the final solution step by step. Here\u0026rsquo;s a sneak-peek of the solution.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etrait\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTicker\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]]\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e get\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e merge\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003etimerTick\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e count\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCount\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eCount\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e ticks\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eStream\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEngine\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eConcurrent:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eParallel:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTime:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTimer\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    publish\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eSummary\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e F\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ticker\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTicker\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eprivate\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eval\u003c/span\u003e fsm \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEngine\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efsm\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e](\u003c/span\u003eticker\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e run\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePipe\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eEvent\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enoneTerminate\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ezip\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eticker\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eticks\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eevalMapAccumulate\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eMap\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eempty\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ePlayerId\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eAgg\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)(\u003c/span\u003efsm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erun\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecollect \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eout\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTick\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eOn\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e out \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eevalMap \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e m \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        F\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etimestamp\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eflatMap \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e ts \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          m\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etoList\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eparTraverse_ \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003epid\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e agg\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u0026gt;\u003c/span\u003e publish\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eagg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esummary\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003epid\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e ts\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eInterested in seeing more? Continue reading or browse the \u003ca href=\"https://github.com/gvolpe/fsm-streams\"\u003esource code\u003c/a\u003e on your own :)\u003c/p\u003e","tags":["scala","fp","fs2","fsm","streaming"],"title":"Finite-State Machines + FS2 streams: A match made in heaven","url":"https://gvolpe.com/blog/fsm-fs2-a-match-made-in-heaven/","wordCount":2550},{"comments":true,"content":"\u003cp\u003eI\u0026rsquo;ve been a \u003ca href=\"https://www.gnome.org/\"\u003eGnome\u003c/a\u003e user for a long time and I have never cared about using a \u003ca href=\"https://en.wikipedia.org/wiki/Window_manager\"\u003eWindow Manager\u003c/a\u003e ever before but I recently switched to using \u003ca href=\"https://xmonad.org/\"\u003eXMonad\u003c/a\u003e full-time. Why? For a couple of reasons.\u003c/p\u003e\n\u003cp\u003eOne of the reasons is that I\u0026rsquo;ve been wanting to try a lightweight window manager for a while. However, the main reason is that Gnome \u003cem\u003eleaks memory\u003c/em\u003e, unfortunately, and it seems to be an issue that\u0026rsquo;s been around \u003ca href=\"https://bugs.launchpad.net/gnome-shell/+bug/1672297\"\u003eforever\u003c/a\u003e. Although it has been announced as allegedly solved, I have been experiencing a similar issue in Gnome 3.36 on \u003ca href=\"https://nixos.org/\"\u003eNixOS\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eAfter a fresh start, it consumed ~1.2% of my 32GB (about 390MB) but right after 24 hours (only one day of uptime!) I found out it was consuming ~50% (about 16GB!), which is unacceptable. I have screenshots of the memory consumption but I decided to leave them out of this post since it is not about bashing Gnome.\u003c/p\u003e\n\u003cp\u003eI know writing solid software is hard and memory leaks are one of the toughest problems to solve in Software Engineering so I only want to thank Gnome for all the good years and wish only the best. My mother still uses Gnome 3 with Ubuntu, FWIW :)\u003c/p\u003e\n\u003cp\u003eThe good news is that this incident has pushed me into giving XMonad a serious try, and oh boy, there\u0026rsquo;s no way back! It\u0026rsquo;s like that day I discovered functional programming. How could I ever write imperative code again?\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eNOTE\u003c/strong\u003e: if you\u0026rsquo;re already familiar with this and are only interested in the configuration files, you can find them \u003ca href=\"https://github.com/gvolpe/nix-config\"\u003ehere\u003c/a\u003e.\u003c/p\u003e\n\u003ch3 id=\"xmonad\"\u003eXMonad\u003c/h3\u003e\n\u003cp\u003eXMonad is a dynamically tiling \u003ca href=\"https://en.wikipedia.org/wiki/X_Window_System\"\u003eX11\u003c/a\u003e window manager that is written and configured in \u003ca href=\"https://www.haskell.org/\"\u003eHaskell\u003c/a\u003e. There are many tutorials, blog posts, videos and documentation on the Internet about it so I\u0026rsquo;m only going to focus on getting XMonad properly set up in NixOS.\u003c/p\u003e\n\u003cp\u003eThis is what it looks like at the moment on my machine (it\u0026rsquo;s always evolving).\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/xmonad.jpg\" alt=\"xmonad\"\u003e\u003c/p\u003e\n\u003cp\u003eThat\u0026rsquo;s not only XMonad but also \u003ca href=\"https://polybar.github.io/\"\u003ePolybar\u003c/a\u003e and \u003ca href=\"https://github.com/yshui/picom\"\u003ePicom\u003c/a\u003e complementing it, a status bar and a compositor, respectively.\u003c/p\u003e\n\u003cp\u003eIf you, like me, come from a popular desktop manager such as Gnome or KDE, you might be surprised finding out all the things they do! Since XMonad is merely a window manager, you won\u0026rsquo;t have out-of-box support for a status bar, notifications, screen locker, etc. This follows the Unix philosophy where we are free to choose how every piece of our software is put together.\u003c/p\u003e\n\u003cp\u003eIn the next sections, we will look into every piece of software I have configured to get the full desktop manager experience.\u003c/p\u003e\n\u003ch4 id=\"configuration\"\u003eConfiguration\u003c/h4\u003e\n\u003cp\u003eI configure all my software and user services via \u003ca href=\"https://github.com/nix-community/home-manager\"\u003eHome Manager\u003c/a\u003e, an amazing Nix tool. It\u0026rsquo;s great on NixOS but it also works in other Linux distros, if you want to give it a shot.\u003c/p\u003e\n\u003cp\u003eHowever, some programs and services need to be declared at the system level, which is also the case for XMonad. So we will start with it.\u003c/p\u003e\n\u003cp\u003eAll configuration files live under \u003ccode\u003e/etc/nixos/\u003c/code\u003e and \u003ccode\u003e~/.config/nixpkgs/\u003c/code\u003e for system-level configuration and user-level configuration, respectively. Therefore, only the relative path will be mentioned in the following sections.\u003c/p\u003e\n\u003ch4 id=\"system-level\"\u003eSystem level\u003c/h4\u003e\n\u003cp\u003eI\u0026rsquo;ve defined the XMonad system configuration under \u003ccode\u003ewm/xmonad.nix\u003c/code\u003e, and I\u0026rsquo;ve done pretty much the same with my Gnome configuration, so it would be easy to switch between both.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ config\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  services \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    gnome3\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egnome-keyring\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    upower\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    dbus \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      socketActivated \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      packages \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egnome3\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edconf ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    xserver \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      startDbusSession \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      layout \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;us\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      libinput \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        disableWhileTyping \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      displayManager\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edefaultSession \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;none+xmonad\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      windowManager\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003exmonad \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        enableContribAndExtras \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      xkbOptions \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;caps:ctrl_modifier\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  hardware\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ebluetooth\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  services\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eblueman\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  systemd\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eservices\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eupower\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThere is a lot declared in this file that is completely unrelated to it but that complements it.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003egnome3.gnome-keyring\u003c/code\u003e: needed to store credentials such as WiFi passwords, so we don\u0026rsquo;t need to enter it every time we log in.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eupower\u003c/code\u003e: get system\u0026rsquo;s power information such as CPU usage. We also need the \u003ccode\u003esystemd\u003c/code\u003e daemon.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003edbus\u003c/code\u003e: short for Desktop Bus, allows concurrent communication between multiple processes and it\u0026rsquo;s kind of ubiquitous these days.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ebluetooth\u003c/code\u003e: enable bluetooth.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eblueman\u003c/code\u003e: enable the bluetooth manager service.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eFor what it\u0026rsquo;s worth, all of these are enabled by default when using either Gnome or KDE.\u003c/p\u003e\n\u003cp\u003eThe rest of the configuration is part of \u003ccode\u003exserver\u003c/code\u003e, in which case, we need to highlight the default session of our display manager being set to \u003ccode\u003enone+xmonad\u003c/code\u003e and enabling XMonad as our window manager. The \u003ccode\u003enone\u003c/code\u003e part means we don\u0026rsquo;t want any Desktop Environment (DE), which defaults to \u003ccode\u003exfce\u003c/code\u003e in NixOS, if I\u0026rsquo;m not mistaken.\u003c/p\u003e\n\u003cp\u003eThis file is then imported at \u003ccode\u003econfiguration.nix\u003c/code\u003e, which is the main NixOS configuration file.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  imports \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#75715e\"\u003e# Include the results of the hardware scan.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e./hardware-configuration.nix\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#75715e\"\u003e# Machine-specific configuration\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e./machine/current.nix\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#75715e\"\u003e# Window manager\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e./wm/xmonad.nix\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eA regular \u003ccode\u003enixos-rebuild switch\u003c/code\u003e followed by a \u003ccode\u003ereboot\u003c/code\u003e are needed for all these changes to become active.\u003c/p\u003e\n\u003ch4 id=\"user-level\"\u003eUser level\u003c/h4\u003e\n\u003cp\u003eUser-level configuration includes all the dotfiles that usually live under \u003ccode\u003e$HOME/.config\u003c/code\u003e as well as user-level services managed via \u003ccode\u003esystemctl\u003c/code\u003e. This is the perfect fit for Home Manager (HM). For reference, this is how the files are organized.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-console\" data-lang=\"console\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e├── config.nix\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e├── home.nix\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e├── overlays\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e├── programs\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   ├── alacritty\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   ├── browsers\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   ├── fish\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   ├── git\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   ├── neovim\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   ├── rofi\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   └── xmonad\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e├── services\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   ├── dunst\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   ├── gpg-agent\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   ├── networkmanager\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   ├── picom\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   ├── polybar\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   ├── screenlocker\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│   └── udiskie\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e└── unstable.nix\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eEvery folder has a \u003ccode\u003edefault.nix\u003c/code\u003e we import in \u003ccode\u003ehome.nix\u003c/code\u003e. So, for example, the configuration for XMonad lives under \u003ccode\u003eprograms/xmonad/default.nix\u003c/code\u003e, and so on. Its definition is shown below.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  xsession \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    initExtra \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e polybarOpts;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    windowManager\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003exmonad \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      enableContribAndExtras \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      extraPackages \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e hp: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        hp\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edbus\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        hp\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emonad-logger\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        hp\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003exmonad-contrib\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      config \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e./config.hs\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWe will later see how \u003ccode\u003epolybarOpts\u003c/code\u003e is defined and whether that\u0026rsquo;s needed for you or not. This file is then imported by \u003ccode\u003ehome.nix\u003c/code\u003e, in the same way we do it at the system level.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ config\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e stdenv\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  programs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehome-manager\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  imports \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#e6db74\"\u003e./programs/xmonad/default.nix\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn general, all the programs and user-level services are imported in this way, to keep the modularity.\u003c/p\u003e\n\u003ch3 id=\"rofi\"\u003eRofi\u003c/h3\u003e\n\u003cp\u003eRight after XMonad is installed, having an application launcher (recommended) might be very convenient to have. Otherwise, the only way to run applications will be directly from a terminal, which is not the nicest experience for GUI programs. This is where tools such as \u003ca href=\"https://tools.suckless.org/dmenu/\"\u003edmenu\u003c/a\u003e and \u003ca href=\"https://github.com/davatorium/rofi\"\u003eRofi\u003c/a\u003e come into place.\u003c/p\u003e\n\u003cp\u003eThe former has been around for a long time and it\u0026rsquo;s a good default but after trying out both, I\u0026rsquo;ve settled for Rofi, which is a modern replacement for \u003ccode\u003edmenu\u003c/code\u003e. You can customize its look and feel as you please and there are a few themes to select from if you prefer something already made.\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s how it looks on my machine.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/rofi.png\" alt=\"rofi\"\u003e\u003c/p\u003e\n\u003cp\u003eI use a customized version of the \u003ca href=\"https://github.com/davatorium/rofi/blob/next/themes/arthur.rasi\"\u003eArthur theme\u003c/a\u003e. Here\u0026rsquo;s how to import it using Home Manager.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ pkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  programs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erofi \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    terminal \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003epkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ealacritty\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/alacritty\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    theme \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e./theme.rafi\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"polybar\"\u003ePolybar\u003c/h3\u003e\n\u003cp\u003eWhen I got started with XMonad, I\u0026rsquo;ve tried \u003ca href=\"https://xmobar.org/\"\u003eXMobar\u003c/a\u003e first, which is the default status bar you would see recommended for this window manager. Later on, I gave \u003ca href=\"https://github.com/taffybar/taffybar\"\u003etaffybar\u003c/a\u003e a try, but it was a bit painful to set up since I needed to add two Nix overlays to get it working as it is usually marked as broken on Nixpkgs.\u003c/p\u003e\n\u003cp\u003eBoth of them work fine and might suit your needs (I still keep the configuration files for both in my repo, if you\u0026rsquo;re interested) but I ultimately fell in love with \u003ca href=\"https://polybar.github.io/\"\u003ePolybar\u003c/a\u003e. I am actually running two instances of it: one at the top; another at the bottom. There are many existing themes, great documentation and a vibrant community behind it, which makes it even more appealing.\u003c/p\u003e\n\u003cp\u003eIt is configured via \u003ca href=\"https://en.wikipedia.org/wiki/INI_file\"\u003eINI files\u003c/a\u003e. Here\u0026rsquo;s a snippet.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-ini\" data-lang=\"ini\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003e[global/wm]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emargin-top\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emargin-bottom\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003e[bar/main]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emonitor\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e${env:MONITOR:HDMI-1}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ewidth\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e100%\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eheight\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e48\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eradius\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e6.0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003efixed-center\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt comes packed with a few modules by default such as CPU, memory and network status. However, some modules might require some software to be available in your system. I\u0026rsquo;ll leave that for you to further read and instead I\u0026rsquo;ll show you the important part:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eXMonad integration to visualize the current window title and workspaces.\u003c/li\u003e\n\u003cli\u003eDealing with modules that require custom scripts doing it the Nix way.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eHere\u0026rsquo;s my configuration, which runs as a service you can manage via \u003ccode\u003esystemctl\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ config\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  mypolybar \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epolybar\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eoverride {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    alsaSupport   \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    githubSupport \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    mpdSupport    \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    pulseSupport  \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#75715e\"\u003e# theme adapted from: https://github.com/adi1090x/polybar-themes#-polybar-5\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  bars   \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e builtins\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ereadFile \u003cspan style=\"color:#e6db74\"\u003e./bars.ini\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  colors \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e builtins\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ereadFile \u003cspan style=\"color:#e6db74\"\u003e./colors.ini\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  mods1  \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e builtins\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ereadFile \u003cspan style=\"color:#e6db74\"\u003e./modules.ini\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  mods2  \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e builtins\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ereadFile \u003cspan style=\"color:#e6db74\"\u003e./user_modules.ini\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  mprisScript     \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecallPackage \u003cspan style=\"color:#e6db74\"\u003e./scripts/mpris.nix\u003c/span\u003e {};\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  mpris \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    [module/mpris]\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    type = custom/script\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    exec = \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003emprisScript\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/mpris\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    tail = true\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    label-maxlen = 60\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    interval = 2\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    format =   \u0026lt;label\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    format-padding = 2\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e  \u0026#39;\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  xmonad \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    [module/xmonad]\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    type = custom/script\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    exec = \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003epkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003exmonad-log\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/xmonad-log\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    tail = true\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e  \u0026#39;\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  services\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epolybar \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    package \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e mypolybar;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    config \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e./config.ini\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    extraConfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e bars \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e colors \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e mods1 \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e mods2 \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e mpris \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e xmonad;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    script \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e      polybar top \u0026amp;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e      polybar bottom \u0026amp;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    \u0026#39;\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAs mentioned above, I\u0026rsquo;m running two different bars, as the \u003ccode\u003escript\u003c/code\u003e section shows.\u003c/p\u003e\n\u003cp\u003eFor the XMonad integration, we only need to add \u003ccode\u003e[module/xmonad]\u003c/code\u003e. If we were not using Nix, this could have been defined in the INI files but since we are on NixOS, we do it properly by referencing the binary we want to run from this module, which is \u003ccode\u003exmonad-log\u003c/code\u003e. We also need to do our part in our XMonad configuration, to make sure it sends out information via DBus.\u003c/p\u003e\n\u003cp\u003eThis is most of the code. You can find the complete configuration on my \u003ca href=\"https://github.com/gvolpe/nix-config/blob/master/home/programs/xmonad/config.hs\"\u003eGithub\u003c/a\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emkDbusClient\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eIO\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eD\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eClient\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emkDbusClient\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  dbus \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eD\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003econnectSession\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eD\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erequestName dbus (\u003cspan style=\"color:#66d9ef\"\u003eD\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ebusName_ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;org.xmonad.log\u0026#34;\u003c/span\u003e) opts\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  return dbus\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ewhere\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  opts \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\u003cspan style=\"color:#66d9ef\"\u003eD\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enameAllowReplacement, \u003cspan style=\"color:#66d9ef\"\u003eD\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enameReplaceExisting, \u003cspan style=\"color:#66d9ef\"\u003eD\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enameDoNotQueue]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e-- Emit a DBus signal on log updates\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edbusOutput\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eD\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eClient\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eString\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eIO\u003c/span\u003e ()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edbusOutput\u003c/span\u003e dbus str \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e opath  \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eD\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eobjectPath_ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/org/xmonad/Log\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      iname  \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eD\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003einterfaceName_ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;org.xmonad.Log\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      mname  \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eD\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ememberName_ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Update\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      signal \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eD\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esignal opath iname mname)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      body   \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\u003cspan style=\"color:#66d9ef\"\u003eD\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etoVariant \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUTF8\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edecodeString str]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e  \u003cspan style=\"color:#66d9ef\"\u003eD\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eemit dbus \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e signal { \u003cspan style=\"color:#66d9ef\"\u003eD\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esignalBody \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e body }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003epolybarHook\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eD\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eClient\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePP\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003epolybarHook\u003c/span\u003e dbus \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e wrapper c s \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e s \u003cspan style=\"color:#f92672\"\u003e/=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;NSP\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e wrap (\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;%{F\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u0026gt;\u003c/span\u003e c \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;} \u0026#34;\u003c/span\u003e) \u003cspan style=\"color:#e6db74\"\u003e\u0026#34; %{F-}\u0026#34;\u003c/span\u003e s\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                  \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e otherwise  \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e mempty\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      blue   \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;#2E9AFE\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      gray   \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;#7F7F7F\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      orange \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;#ea4300\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      purple \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;#9058c7\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      red    \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;#722222\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e  def { ppOutput          \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e dbusOutput dbus\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          , ppCurrent         \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e wrapper blue\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          , ppVisible         \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e wrapper gray\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          , ppUrgent          \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e wrapper orange\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          , ppHidden          \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e wrapper gray\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          , ppHiddenNoWindows \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e wrapper red\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          , ppTitle           \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e shorten \u003cspan style=\"color:#ae81ff\"\u003e100\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e wrapper purple\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emyPolybarLogHook\u003c/span\u003e dbus \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e myLogHook \u003cspan style=\"color:#f92672\"\u003e\u0026lt;+\u0026gt;\u003c/span\u003e dynamicLogWithPP (polybarHook dbus)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eNow for other custom scripts, you can do the same. Above we see \u003ccode\u003e[module/mpris]\u003c/code\u003e, which is a shell script that reads the current song being played via \u003ccode\u003eplayerctl\u003c/code\u003e. It is also defined the Nix way, as shown below.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ pkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e}:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  pctl \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003epkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eplayerctl\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/playerctl\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ewriteShellScriptBin \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;mpris\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    echo $(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003epctl\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e --player=spotify,%any metadata --format \u0026#39;{{ artist }} - {{ title }}\u0026#39;)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e  \u0026#39;\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAnother important setting you might need, depending on your modules, is to add a proper font and icons package to your user packages. This is what I have, for example.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  polybarPkgs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ewith\u003c/span\u003e pkgs; [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    font-awesome-ttf      \u003cspan style=\"color:#75715e\"\u003e# awesome fonts\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    material-design-icons \u003cspan style=\"color:#75715e\"\u003e# fonts with glyphs\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  home\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epackages \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e polybarPkgs;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"dunst\"\u003eDunst\u003c/h3\u003e\n\u003cp\u003e\u003ca href=\"https://dunst-project.org/\"\u003eDunst\u003c/a\u003e is a lightweight replacement for the notification daemons provided by most desktop environments. It\u0026rsquo;s very customizable, isn\u0026rsquo;t dependent on any toolkits, and therefore fits into those window manager centric setups we all love to customize to perfection.\u003c/p\u003e\n\u003cp\u003eThat\u0026rsquo;s a quote from the official site. It also runs a service, and here\u0026rsquo;s a snippet showing how it is integrated via Home Manager.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ config\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  services\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edunst \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    iconTheme \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      name \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Adwaita\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      package \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egnome3\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eadwaita-icon-theme;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      size \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;16x16\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    settings \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      global \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        monitor \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        geometry \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;600x50-50+65\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        shrink \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;yes\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        transparency \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        padding \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e16\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        horizontal_padding \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e16\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        font \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;JetBrainsMono Nerd Font 10\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        line_height \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e4\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        format \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\u0026lt;b\u0026gt;%s\u0026lt;/b\u0026gt;\\n%b\u0026#39;\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eI haven\u0026rsquo;t tried any other notification system and I haven\u0026rsquo;t been made aware of a better choice but if you do, please let me know. I always like to try new things.\u003c/p\u003e\n\u003ch3 id=\"betterlockscreen\"\u003eBetterlockscreen\u003c/h3\u003e\n\u003cp\u003eIf you want to configure a screen locker, you\u0026rsquo;ll find there are quite a few alternatives out there, with \u003ca href=\"https://i3wm.org/i3lock/\"\u003ei3lock\u003c/a\u003e being probably the most popular option. I\u0026rsquo;ve tried a few variants of it but I finally settled for \u003ca href=\"https://github.com/pavanjadhaw/betterlockscreen\"\u003ebetterlockscreen\u003c/a\u003e, which is also built on top of \u003ccode\u003ei3lock\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eIt runs as a service as well. Here\u0026rsquo;s my configuration, without much further ado.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ pkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  services\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003escreen-locker \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    inactiveInterval \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e30\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    lockCmd \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003epkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ebetterlockscreen\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/bin/betterlockscreen -l dim\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    xautolockExtraOptions \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Xautolock.killer: systemctl suspend\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"picom\"\u003ePicom\u003c/h3\u003e\n\u003cp\u003eWhat exactly is a compositor? According to \u003ca href=\"https://en.wikipedia.org/wiki/Compositing_window_manager\"\u003eWikipedia\u003c/a\u003e, a compositing window manager, or compositor, is a window manager that provides applications with an off-screen buffer for each window. The window manager composites the window buffers into an image representing the screen and writes the result into the display memory.\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/yshui/picom\"\u003epicom\u003c/a\u003e is probably the indisputable default these days, which is a fork of \u003ccode\u003ecompton\u003c/code\u003e, which is also a fork of the original \u003ccode\u003excompmgr\u003c/code\u003e, both \u0026ldquo;retired\u0026rdquo; projects.\u003c/p\u003e\n\u003cp\u003eIt shouldn\u0026rsquo;t come as a surprise that it also runs a service. Here\u0026rsquo;s my configuration.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  services\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epicom \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    activeOpacity \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;1.0\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    inactiveOpacity \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;0.8\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    backend \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;glx\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    fade \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    fadeDelta \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    opacityRule \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;100:name *= \u0026#39;i3lock\u0026#39;\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    shadow \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    shadowOpacity \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;0.75\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"system-tray\"\u003eSystem tray\u003c/h3\u003e\n\u003cp\u003eIf you use programs like Slack or Telegram, you might appreciate a system tray for them. This is something we also need to take care about. When using XMobar or Taffybar, people usually go for \u003ca href=\"http://stalonetray.sourceforge.net/\"\u003estandalonetray\u003c/a\u003e, which is fairly basic. Conversely, Polybar users don\u0026rsquo;t need to worry about it as it comes with a nice system tray by default.\u003c/p\u003e\n\u003cp\u003eThe configuration for my system tray is fairly basic. I keep it in the center of my top bar.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-ini\" data-lang=\"ini\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003etray-padding\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e3\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003etray-background\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e${color.bg}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003etray-detached\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003efalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003etray-maxsize\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e6\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003etray-offset-x\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003etray-offset-y\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003etray-padding\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003etray-scale\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e1.0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003e[bar/top]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003einherit\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003ebar/main\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003etray-position\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003ecenter\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003e[bar/bottom]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003einherit\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003ebar/main\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ebottom\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003etray-position\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003enone\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAt the beginning of the user level section, I briefly mentioned the use of \u003ccode\u003epolybarOpts\u003c/code\u003e as an option to \u003ccode\u003exsession.initExtra\u003c/code\u003e.  This is how it is defined.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epolybarOpts \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e  nitrogen --restore \u0026amp;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e  pasystray \u0026amp;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e  blueman-applet \u0026amp;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e  nm-applet --sm-disable --indicator \u0026amp;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eI basically start up some utilities that run in the system tray such as Pulse Audio (\u003ccode\u003epasystray\u003c/code\u003e), Bluetooth Manager (\u003ccode\u003eblueman-applet\u003c/code\u003e) and Network Manager (\u003ccode\u003enm-applet\u003c/code\u003e). There\u0026rsquo;s also \u003ccode\u003enitrogen\u003c/code\u003e to restore my wallpaper. Some other folks prefer to use \u003ca href=\"https://feh.finalrewind.org/\"\u003efeh\u003c/a\u003e. I would use any of them, to be fair, no big preferences.\u003c/p\u003e\n\u003cp\u003eIt might also be relevant to set up a GTK theme. This is what I have defined in my \u003ccode\u003ehome.nix\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egtk \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  iconTheme \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    name \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Adwaita-dark\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    package \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egnome3\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eadwaita-icon-theme;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  theme \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    name \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Adwaita-dark\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    package \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egnome3\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eadwaita-icon-theme;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e};\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"udiskie\"\u003eUdiskie\u003c/h3\u003e\n\u003cp\u003eSince we talked about our system tray, there\u0026rsquo;s also \u003ca href=\"https://github.com/coldfix/udiskie\"\u003eudiskie\u003c/a\u003e, which is quite popular. It shows information about removable media and it automounts them by default.\u003c/p\u003e\n\u003cp\u003eWe could have started up as part of our XSession but since Home Manager supports running it as a service, I went with it.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  services\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eudiskie \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    tray \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;always\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHome Manager also supports starting up the \u003ca href=\"https://rycee.gitlab.io/home-manager/options.html#opt-services.network-manager-applet.enable\"\u003eNetwork Manager applet\u003c/a\u003e as a service but at the moment of writing, it doesn\u0026rsquo;t support customized flags, so I run it manually in my XSession.\u003c/p\u003e\n\u003ch3 id=\"conclusion\"\u003eConclusion\u003c/h3\u003e\n\u003cp\u003eWow, we went through a lot of stuff! This is actually a big part of my current NixOS configuration. I hope you have enjoyed the details since when I started playing around with all of it, the information was pretty much scattered around the Internet and I had to figure out a lot of things by trial and error.\u003c/p\u003e\n\u003cp\u003eThese settings will probably keep on changing but I\u0026rsquo;m already in a very comfortable place with my current configuration so I don\u0026rsquo;t expect drastic changes in the near future but rather small improvements.\u003c/p\u003e\n\u003cp\u003eMy full NixOS configuration, including the programs and services managed by Home Manager, can be found on \u003ca href=\"https://github.com/gvolpe/nix-config\"\u003eGithub\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eCheers,\nGabriel.\u003c/p\u003e\n","date":"2020-11-25","dateFormatted":"2020.11.25","excerpt":"I\u0026rsquo;ve been a Gnome user for a long time and I have never cared about using a Window Manager ever before but I recently switched to using XMonad full-time. …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/xmonad-polybar-nixos/","readingTime":14,"slug":"xmonad-polybar-nixos","subtitle":null,"summary":"\u003cp\u003eI\u0026rsquo;ve been a \u003ca href=\"https://www.gnome.org/\"\u003eGnome\u003c/a\u003e user for a long time and I have never cared about using a \u003ca href=\"https://en.wikipedia.org/wiki/Window_manager\"\u003eWindow Manager\u003c/a\u003e ever before but I recently switched to using \u003ca href=\"https://xmonad.org/\"\u003eXMonad\u003c/a\u003e full-time. Why? For a couple of reasons.\u003c/p\u003e\n\u003cp\u003eOne of the reasons is that I\u0026rsquo;ve been wanting to try a lightweight window manager for a while. However, the main reason is that Gnome \u003cem\u003eleaks memory\u003c/em\u003e, unfortunately, and it seems to be an issue that\u0026rsquo;s been around \u003ca href=\"https://bugs.launchpad.net/gnome-shell/+bug/1672297\"\u003eforever\u003c/a\u003e. Although it has been announced as allegedly solved, I have been experiencing a similar issue in Gnome 3.36 on \u003ca href=\"https://nixos.org/\"\u003eNixOS\u003c/a\u003e.\u003c/p\u003e","tags":["nix","nixos","xmonad","polybar","wm","windowmanager"],"title":"XMonad + Polybar on NixOS","url":"https://gvolpe.com/blog/xmonad-polybar-nixos/","wordCount":2799},{"comments":true,"content":"\u003cp\u003e\u003ca href=\"https://nixos.org/\"\u003eNixOS\u003c/a\u003e can be configured to run any desktop environment you want and \u003ca href=\"https://www.gnome.org/gnome-3/\"\u003eGnome 3\u003c/a\u003e is not an exception. However, it comes with some caveats so keep reading if you are interested in making this duo work seamlessly.\u003c/p\u003e\n\u003cp\u003eUsers who enjoy a graphical environment normally like to tweak it with their own preferences as well. E.g. installing new \u003ca href=\"https://extensions.gnome.org/\"\u003eextensions\u003c/a\u003e, changing the background image, changing the dock, etc. These are some of the tasks that \u003ca href=\"https://wiki.gnome.org/Apps/Tweaks\"\u003eGnome Tweaks\u003c/a\u003e makes possible in Gnome 3.\u003c/p\u003e\n\u003cp\u003eExcept, if you are in NixOS, I\u0026rsquo;m guessing you\u0026rsquo;d like to have all these tweaks declared in a configuration file so the next time you rebuild your system they are preserved, am I right?\u003c/p\u003e\n\u003ch3 id=\"nixos-configuration\"\u003eNixOS configuration\u003c/h3\u003e\n\u003cp\u003eFirst of all, we want to enable Gnome 3 as our default desktop manager in our \u003ccode\u003e/etc/nixos/configuration.nix\u003c/code\u003e file.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eservices \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  xserver \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    enable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    layout \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;us\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    displayManager\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egdm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    displayManager\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egdm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ewayland \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    desktopManager\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egnome3\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  dbus\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epackages \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egnome3\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edconf ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  udev\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epackages \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egnome3\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egnome-settings-daemon ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e};\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ca href=\"https://wayland.freedesktop.org/\"\u003eWayland\u003c/a\u003e is a modern replacement for \u003ca href=\"https://www.x.org/wiki/\"\u003eX\u003c/a\u003e. I tried it out for a while and it worked pretty well but unfortunately some functionality like screen-sharing is broken, reason why I have it disabled.\u003c/p\u003e\n\u003cp\u003eAdditionally, you need \u003ccode\u003edconf\u003c/code\u003e and \u003ccode\u003egnome-settings-daemon\u003c/code\u003e running as a service to configure Gnome 3. This is all we need at the system level.\u003c/p\u003e\n\u003ch3 id=\"home-manager\"\u003eHome Manager\u003c/h3\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/rycee/home-manager\"\u003eHome Manager\u003c/a\u003e is a great tool that manages all the software you want to install at the user level and all the configuration that comes with it. For example, Vim, Fish shell, Tmux, etc.\u003c/p\u003e\n\u003cp\u003eIt can also manage Gnome extensions for us. Here\u0026rsquo;s an example:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ config\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e stdenv\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  programs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehome-manager\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  home\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epackages \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ewith\u003c/span\u003e pkgs; [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e# gnome3 apps\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    gnome3\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eeog    \u003cspan style=\"color:#75715e\"\u003e# image viewer\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    gnome3\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eevince \u003cspan style=\"color:#75715e\"\u003e# pdf reader\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e# desktop look \u0026amp; feel\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    gnome3\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egnome-tweak-tool\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e# extensions\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    gnomeExtensions\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eappindicator\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    gnomeExtensions\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edash-to-dock\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ];\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAfter running \u003ccode\u003ehome-manager switch\u003c/code\u003e, if the build succeeded, you will see these extensions installed in Gnome Tweaks. Now we can go ahead, turn them on and configure each extension as we like. Next day we install another extension, say \u003ccode\u003egnomeExtensions.battery-status\u003c/code\u003e, and again run \u003ccode\u003ehome-manager switch\u003c/code\u003e. You will find out that all the three extensions are now installed but that you have lost the changes made to the configuration of the extensions!\u003c/p\u003e\n\u003cp\u003eAs I previously mentioned, Gnome 3\u0026rsquo;s configuration is managed by \u003ccode\u003edconf\u003c/code\u003e. You can get a description of the configuration by running the following command:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ dconf dump / \u0026gt; dconf.settings\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt will have content similar to the one below.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e org/gnome/desktop/peripherals/mouse \u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enatural-scroll\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003efalse\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003espeed\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e-0.5\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e org/gnome/desktop/peripherals/touchpad \u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etap-to-click\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003efalse\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etwo-finger-scrolling-enabled\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003etrue\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003eorg/gnome/desktop/input-sources\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecurrent\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003euint32 \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esources\u003cspan style=\"color:#f92672\"\u003e=[(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;xkb\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;us\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003exkb-options\u003cspan style=\"color:#f92672\"\u003e=[\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39; terminate:ctrl_alt_bksp \u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39; lv3:ralt_switch \u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39; caps:ctrl_modifier \u0026#39;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e org/gnome/desktop/screensaver \u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epicture-uri\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39; file:///home/gvolpe/Pictures/nixos.png \u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHome Manager provides a \u003ca href=\"https://rycee.gitlab.io/home-manager/options.html#opt-dconf.settings\"\u003edconf.settings\u003c/a\u003e option we can use to configure \u003ccode\u003edconf\u003c/code\u003e. However, we need to write it in Nix instead. The configuration above will look as follows:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ lib\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  mkTuple \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egvariant\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkTuple;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  dconf\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esettings \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;org/gnome/desktop/peripherals/mouse\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;natural-scroll\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;speed\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e-0\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e.5\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;org/gnome/desktop/peripherals/touchpad\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;tap-to-click\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;two-finger-scrolling-enabled\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;org/gnome/desktop/input-sources\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;current\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;uint32 0\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;sources\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ (mkTuple [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;xkb\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;us\u0026#34;\u003c/span\u003e ]) ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;xkb-options\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;terminate:ctrl_alt_bksp\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;lv3:ralt_switch\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;caps:ctrl_modifier\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;org/gnome/desktop/screensaver\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;picture-uri\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;file:///home/gvolpe/Pictures/nixos.png\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAs we can see, it is a bit tedious to write all of this manually.\u003c/p\u003e\n\u003ch3 id=\"dconf2nix\"\u003edconf2nix\u003c/h3\u003e\n\u003cp\u003eSo I recently made available a tool called \u003ca href=\"https://github.com/gvolpe/dconf2nix\"\u003edconf2nix\u003c/a\u003e, which automates this conversion. It is written in a few lines of Haskell, thanks to the expressiveness of parser combinators.\u003c/p\u003e\n\u003cp\u003eOnce you get the dump of the \u003ccode\u003edconf\u003c/code\u003e configuration, you\u0026rsquo;re just a command away from getting it in the Nix format as expected by Home Manager.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ dconf2nix -i dconf.settings -o dconf.nix\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eTo keep the modularity, it is a good practice to keep the \u003ccode\u003edconf.nix\u003c/code\u003e as is, and import it in your \u003ccode\u003ehome.nix\u003c/code\u003e file instead.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ config\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e stdenv\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e lib\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  programs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehome-manager\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  imports \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e./dconf.nix\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAnd that\u0026rsquo;s it! Next time you make changes in the UI, make sure to also update the \u003ccode\u003edconf.nix\u003c/code\u003e file. For instance, you may want to enable some extensions by default.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;org/gnome/shell\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  command-history \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gnome-tweaks\u0026#34;\u003c/span\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  enabled-extensions \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;horizontal-workspaces@gnome-shell-extensions.gcampax.github.com\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;dash-to-dock@micxgx.gmail.com\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  favorite-apps \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;chromium-browser.desktop\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;spotify.desktop\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;org.gnome.tweaks.desktop\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e};\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eYou can have a look at my \u003ca href=\"https://github.com/gvolpe/nix-config\"\u003eNixOS configuration\u003c/a\u003e, where I have also configured some custom Gnome extensions and a bunch of other stuff.\u003c/p\u003e\n\u003cp\u003eHave fun with Nix! And if you happen to find any issue with \u003ca href=\"https://github.com/gvolpe/dconf2nix\"\u003edconf2nix\u003c/a\u003e, please report it, as it is quite new and there might be some missing edge cases.\u003c/p\u003e\n\u003cp\u003eCheers,\nGabriel.\u003c/p\u003e\n","date":"2020-07-01","dateFormatted":"2020.07.01","excerpt":"NixOS can be configured to run any desktop environment you want and Gnome 3 is not an exception. However, it comes with some caveats so keep reading if you are …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/gnome3-on-nixos/","readingTime":4,"slug":"gnome3-on-nixos","subtitle":null,"summary":"\u003cp\u003e\u003ca href=\"https://nixos.org/\"\u003eNixOS\u003c/a\u003e can be configured to run any desktop environment you want and \u003ca href=\"https://www.gnome.org/gnome-3/\"\u003eGnome 3\u003c/a\u003e is not an exception. However, it comes with some caveats so keep reading if you are interested in making this duo work seamlessly.\u003c/p\u003e\n\u003cp\u003eUsers who enjoy a graphical environment normally like to tweak it with their own preferences as well. E.g. installing new \u003ca href=\"https://extensions.gnome.org/\"\u003eextensions\u003c/a\u003e, changing the background image, changing the dock, etc. These are some of the tasks that \u003ca href=\"https://wiki.gnome.org/Apps/Tweaks\"\u003eGnome Tweaks\u003c/a\u003e makes possible in Gnome 3.\u003c/p\u003e","tags":["nix","nixos","gnome","home-manager","hm"],"title":"Gnome 3 on NixOS","url":"https://gvolpe.com/blog/gnome3-on-nixos/","wordCount":767},{"comments":true,"content":"\u003cp\u003eThe more I learn about \u003ca href=\"https://nixos.org/nix/\"\u003eNix\u003c/a\u003e — a purely functional package manager — the more I am convinced this is the way forward. Even if there\u0026rsquo;s still room for big improvements.\u003c/p\u003e\n\u003cp\u003eToday I\u0026rsquo;d like to share with y\u0026rsquo;all what I\u0026rsquo;ve been up-to lately. Though, you could imagine I\u0026rsquo;ve been \u003cem\u003eNixifying\u003c/em\u003e more than one project ;)\u003c/p\u003e\n\u003ch3 id=\"nix-shell\"\u003eNix shell\u003c/h3\u003e\n\u003cp\u003eNix shell is a great tool to create reproducible development environments. For example, we could create a shell where the \u003ccode\u003eredis\u003c/code\u003e package is available, without installing it in our system.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix-shell -p redis\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003enix-shell:~\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e$ redis-cli --version\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eredis-cli 6.0.3\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003enix-shell:~\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e$ exit\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eexit\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ redis-cli --version\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCommand \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;redis-cli\u0026#39;\u003c/span\u003e not found\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis is not \u003cem\u003ethat reproducible\u003c/em\u003e since different machines might be pointing to a different Nix channel, or to the same channel but using a different version. To guarantee reproducibility, we need to \u003ca href=\"https://nixos.wiki/wiki/FAQ/Pinning_Nixpkgs\"\u003epin our nixpkgs\u003c/a\u003e version.\u003c/p\u003e\n\u003cp\u003eNowadays, it is very common to provide a \u003ccode\u003eshell.nix\u003c/code\u003e per project, declaring the dependencies needed to build it. This comes in very handy since we can checkout a project, type \u003ccode\u003enix-shell\u003c/code\u003e and we would be ready to build it. Furthermore, one could use \u003ca href=\"https://github.com/nix-community/nix-direnv\"\u003enix-direnv\u003c/a\u003e instead so that all the needed dependencies are available as soon as we cd into the directory (highly recommended).\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;ve been following this practice in most of my projects, and this blog is not an exception. This is how its \u003ccode\u003eshell.nix\u003c/code\u003e is defined:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  nixpkgs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e fetchTarball {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    name   \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;NixOS-unstable-13-05-2020\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    url    \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;https://github.com/NixOS/nixpkgs-channels/archive/6bcb1dec8ea.tar.gz\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    sha256 \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;04x750byjr397d3mfwkl09b2cz7z71fcykhvn8ypxrck8w7kdi1h\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  pkgs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e nixpkgs {};\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ruby \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eruby_2_7;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  rubygems \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erubygems\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eoverride { ruby \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e ruby; });\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkShell {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  buildInputs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehaskellPackages\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edhall-json \u003cspan style=\"color:#75715e\"\u003e# v1.6.2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ruby \u003cspan style=\"color:#75715e\"\u003e# v2.7.1p83\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  shellHook \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    mkdir -p .nix-gems\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    export GEM_HOME=$PWD/.nix-gems\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    export GEM_PATH=$GEM_HOME\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    export PATH=$GEM_HOME/bin:$PATH\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e  \u0026#39;\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIf you have never seen one, this is what\u0026rsquo;s happening step by step:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003ccode\u003efetchTarball\u003c/code\u003e gets the specified version of the \u003ccode\u003enixpkgs\u003c/code\u003e as a tarball (reproducibility).\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003erubygems\u003c/code\u003e is overriding the \u003ccode\u003eruby\u003c/code\u003e version of \u003ccode\u003enixpkgs\u003c/code\u003e to match the one we need (\u003ccode\u003e2.7.x\u003c/code\u003e).\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003epkgs.mkShell\u003c/code\u003e should be self-explanatory; it creates a shell using the specified version of \u003ccode\u003enixpkgs\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ebuildInputs\u003c/code\u003e defines the two dependencies needed to build the project. Actually, only \u003ccode\u003eruby\u003c/code\u003e is needed to build the blog; \u003ccode\u003edhall-json\u003c/code\u003e is only used locally to generate some YAML files.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eshellHook\u003c/code\u003e allows us to run some commands to prepare the environment of our shell.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eNow anyone could clone this project on Github and build it locally using Nix. Reproducibility should be guaranteed (well, mostly(*)).\u003c/p\u003e\n\u003cp\u003e(*) In a Ruby project, the best way to guarantee reproducibility is by using \u003ca href=\"https://github.com/nix-community/bundix\"\u003ebundix\u003c/a\u003e, but I didn\u0026rsquo;t bother in using it just to build my blog. After all, I prefer to write code in statically-typed languages.\u003c/p\u003e\n\u003ch3 id=\"github-actions-powered-by-dhall\"\u003eGithub actions powered by Dhall\u003c/h3\u003e\n\u003cp\u003eThe curious reader might be thinking what are those YAML files we need to build. The answer is the YAML files needed by \u003ca href=\"https://help.github.com/en/actions\"\u003eGithub actions\u003c/a\u003e. Why not defining the YAML files directly? One may ask. The answer is I really \u003ca href=\"https://noyaml.com/\"\u003edislike YAML files\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eSo there is \u003ca href=\"https://dhall-lang.org/\"\u003eDhall\u003c/a\u003e, which among other things, can generate YAML from a sane Dhall definition via the \u003ccode\u003edhall-to-yaml\u003c/code\u003e program. This is how the CI build definition for this blog looks like:\u003c/p\u003e\n\u003cdiv class=\"highlight dhall-lang\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eGithubActions\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      https\u003cspan style=\"color:#66d9ef\"\u003e://\u003c/span\u003eraw\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egithubusercontent\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecom\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egvolpe\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egithub\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eactions\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003edhall\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003esteps\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003ecachix\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003epackage\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edhall sha256\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e4\u003c/span\u003ecd8f64770d8b015c2fd52bae5ddfb5b393eb7e0936f7f8e18f311c591b888a5\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e setup \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      [ \u003cspan style=\"color:#66d9ef\"\u003eGithubActions\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esteps\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003echeckout\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      , \u003cspan style=\"color:#66d9ef\"\u003eGithubActions\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esteps\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecachix\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003einstall\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003enix\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      , \u003cspan style=\"color:#66d9ef\"\u003eGithubActions\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esteps\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecachix\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003ecachix { cache\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ename \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gvolpe-blog\u0026#34;\u003c/span\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      , \u003cspan style=\"color:#66d9ef\"\u003eGithubActions\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esteps\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erun\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          { run \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;nix-shell run -- \u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003ebundle install \u0026amp;\u0026amp; bundle exec jekyll build\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e  \u003cspan style=\"color:#66d9ef\"\u003eGithubActions\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eWorkflow\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    , name \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Blog\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    , on \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eGithubActions\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eOn\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      , pull_request \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eSome\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eGithubActions\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ePullRequest\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e{\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      , push \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eSome\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eGithubActions\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ePush\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e{ branches \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eSome\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;master\u0026#34;\u003c/span\u003e ] }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    , jobs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e toMap\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        { build \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eGithubActions\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eJob\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          , name \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;build\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          , needs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eNone\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eList\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eText\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          , runs\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eon \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eGithubActions\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etypes\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eRunsOn\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e`ubuntu\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e18.04\u003c/span\u003e`\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          , steps \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e setup\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eBesides giving us types, Dhall gives us \u003ca href=\"http://www.haskellforall.com/2017/11/semantic-integrity-checks-are-next.html\"\u003esemantic integrity checks\u003c/a\u003e, which is a great and secure way of versioning files. You should really read the linked blogpost if you haven\u0026rsquo;t heard of it.\u003c/p\u003e\n\u003cp\u003eNotice how we use the same \u003ccode\u003eshell.nix\u003c/code\u003e we use for local development to build our software in the CI pipeline. Isn\u0026rsquo;t that awesome? No more duplicate ways of building packages! This way, we guarantee that whatever works locally, also works on our CI.\u003c/p\u003e\n\u003cp\u003eNow, what am I using to write this nice Dhall definition? The answer is \u003ca href=\"https://github.com/regadas/github-actions-dhall\"\u003eGithub Actions Dhall\u003c/a\u003e, a project by Filipe Regadas.\u003c/p\u003e\n\u003cp\u003eIn this particular example, I\u0026rsquo;m pointing at my fork, which contains the definitions of the \u003ccode\u003ecachix/install-nix\u003c/code\u003e and \u003ccode\u003ecachix/cachix\u003c/code\u003e actions. Ultimately, these changes will be available upstream, I hope.\u003c/p\u003e\n\u003cp\u003eAll we need to do to generate the required YAML file is to run the following command:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ dhall-to-yaml --file ci.dhall \u0026gt; ci.yml\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eI know this is a bit annoying at the moment, but I\u0026rsquo;d do this 1000 times over writing YAML directly. The interesting thing is that Github could support Dhall definitions natively - I actually submitted a feature request but I don\u0026rsquo;t know if they will consider it.\u003c/p\u003e\n\u003cp\u003eAll they need to do is to have \u003ccode\u003edhall-json\u003c/code\u003e installed, take in a Dhall definition and run the same \u003ccode\u003edhall-to-yaml\u003c/code\u003e command. Now they can just re-use the existing infrastructure! \u003cem\u003eIt would be fantastic if Github pulls this off\u003c/em\u003e.\u003c/p\u003e\n\u003ch3 id=\"caching-derivations\"\u003eCaching derivations\u003c/h3\u003e\n\u003cp\u003e\u003ca href=\"https://cachix.org/\"\u003eCachix\u003c/a\u003e is a binary cache as a service, and it is free for public caches and open-source projects. It helps speeding up your CI build as well as builds in other machines that share the same \u003ccode\u003eshell.nix\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eIts main documentation is centered around building Nix derivations, so this is what you will find:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ cachix create \u0026lt;name\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix-build | cachix push \u0026lt;name\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ cachix use \u0026lt;name\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHowever, if we try to build our \u003ccode\u003eshell.nix\u003c/code\u003e file, this is what happens:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix-build shell.nix\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ethese derivations will be built:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  /nix/store/7lyp4vfrrsn7rx3rwy31va2l10g4xkxq-nix-shell.drv\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebuilding \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;/nix/store/7lyp4vfrrsn7rx3rwy31va2l10g4xkxq-nix-shell.drv\u0026#39;\u003c/span\u003e...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enobuildPhase\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eThis derivation is not meant to be built, aborting\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebuilder \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;/nix/store/7lyp4vfrrsn7rx3rwy31va2l10g4xkxq-nix-shell.drv\u0026#39;\u003c/span\u003e failed with exit code \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eerror: build of \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;/nix/store/7lyp4vfrrsn7rx3rwy31va2l10g4xkxq-nix-shell.drv\u0026#39;\u003c/span\u003e failed\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eDon\u0026rsquo;t worry. Cachix supports building and pushing the derivations of a Nix shell but it is currently an under-documented feature! This is how we can do it:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix-shell\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ nix-store -qR --include-outputs \u003cspan style=\"color:#66d9ef\"\u003e$(\u003c/span\u003enix-instantiate shell.nix\u003cspan style=\"color:#66d9ef\"\u003e)\u003c/span\u003e | cachix push \u0026lt;name\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe first step might not be necessary if you already evaluated your shell or are using Nix Direnv.\u003c/p\u003e\n\u003cp\u003eOnce you learn how to use Cachix, you could take advantage of it on Github actions by using \u003ca href=\"https://github.com/cachix/cachix-action\"\u003eCachix Action\u003c/a\u003e, which is the action used in our Dhall file above.\u003c/p\u003e\n\u003cp\u003eSo far, I have been trying this configuration on \u003ca href=\"https://github.com/gvolpe/blog\"\u003ethis blog\u003c/a\u003e, on a \u003ca href=\"https://github.com/profunktor/redis4cats\"\u003eScala project\u003c/a\u003e and on a \u003ca href=\"https://github.com/gvolpe/shopping-cart-haskell\"\u003eHaskell project\u003c/a\u003e. The conclusion is that for a CI build job I could probably get away without using Cachix and it\u0026rsquo;ll still be the same. However, as soon as the Nix dependencies of your project start to grow, you may notice a difference.\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s a screenshot of the current size of all the caches I have defined:\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/cachix.png\" alt=\"cachix\"\u003e\u003c/p\u003e\n\u003ch3 id=\"microsite-publishing-automation\"\u003eMicrosite publishing automation\u003c/h3\u003e\n\u003cp\u003eThe Redis4Cats project (Scala) is particularly interesting because I use Nix only to publish the static site (microsite) that contains the documentation. This is normally done via the command \u003ccode\u003esbt publishMicrosite\u003c/code\u003e, which requires not only \u003ccode\u003esbt\u003c/code\u003e and \u003ccode\u003eopenjdk\u003c/code\u003e but also \u003ccode\u003ejekyll\u003c/code\u003e. Find below the definition of the \u003ccode\u003eshell.nix\u003c/code\u003e file:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  nixpkgs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e fetchTarball {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    name   \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;NixOS-unstable-13-05-2020\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    url    \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;https://github.com/NixOS/nixpkgs-channels/archive/6bcb1dec8ea.tar.gz\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    sha256 \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;04x750byjr397d3mfwkl09b2cz7z71fcykhvn8ypxrck8w7kdi1h\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  pkgs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e nixpkgs {};\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkShell {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    buildInputs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ewith\u003c/span\u003e pkgs; [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      haskellPackages\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edhall-json \u003cspan style=\"color:#75715e\"\u003e# 1.6.2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      jekyll \u003cspan style=\"color:#75715e\"\u003e# 4.0.1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      openjdk \u003cspan style=\"color:#75715e\"\u003e# 1.8.0_242\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      sbt \u003cspan style=\"color:#75715e\"\u003e# 1.3.10\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  }\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eBesides this Nix shell definition, we also have a Dhall definition of the Github actions job required to automate the publishing of the microsite, only when some changes have been made to the site and is pushed to master.\u003c/p\u003e\n\u003cdiv class=\"highlight dhall-lang\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eGithubActions\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      https\u003cspan style=\"color:#66d9ef\"\u003e://\u003c/span\u003eraw\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egithubusercontent\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecom\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egvolpe\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egithub\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eactions\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003edhall\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003esteps\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003ecachix\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003epackage\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edhall sha256\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e4\u003c/span\u003ecd8f64770d8b015c2fd52bae5ddfb5b393eb7e0936f7f8e18f311c591b888a5\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e setup \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      [ \u003cspan style=\"color:#66d9ef\"\u003eGithubActions\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esteps\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003echeckout\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      , \u003cspan style=\"color:#66d9ef\"\u003eGithubActions\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esteps\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecachix\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003einstall\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003enix\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      , \u003cspan style=\"color:#66d9ef\"\u003eGithubActions\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esteps\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecachix\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003ecachix { cache\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ename \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;scala-microsite\u0026#34;\u003c/span\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      , \u003cspan style=\"color:#66d9ef\"\u003eGithubActions\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esteps\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erun { run \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;nix-shell --run \u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003esbt publishSite\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e  \u003cspan style=\"color:#66d9ef\"\u003eGithubActions\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eWorkflow\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    , name \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Microsite\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    , on \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eGithubActions\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eOn\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      , push \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eSome\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eGithubActions\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ePush\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        , branches \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eSome\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;master\u0026#34;\u003c/span\u003e ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        , paths \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eSome\u003c/span\u003e [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;site/**\u0026#34;\u003c/span\u003e ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    , jobs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e toMap\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        { publish \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eGithubActions\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eJob\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          , name \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;publish-site\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          , needs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eNone\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eList\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eText\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          , runs\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eon \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eGithubActions\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etypes\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eRunsOn\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e`ubuntu\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e18.04\u003c/span\u003e`\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          , steps \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e setup\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          , env \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eSome\u003c/span\u003e (toMap { \u003cspan style=\"color:#66d9ef\"\u003eGITHUB_TOKEN\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e${\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{ secrets.GITHUB_TOKEN }}\u0026#34;\u003c/span\u003e })\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eYou can see it in action \u003ca href=\"https://github.com/profunktor/redis4cats/runs/732431811\"\u003ehere\u003c/a\u003e. It takes about 13 minutes because the \u003ccode\u003esbt publishMicrosite\u003c/code\u003e is a slow task, not because of Nix, but the point is that this is now an automated task and I can forget about it.\u003c/p\u003e\n\u003cp\u003eI like all this stuff a lot so I hope to adopt it in other projects quite soon.\u003c/p\u003e\n\u003cp\u003eCheers,\nGabriel.\u003c/p\u003e\n","date":"2020-06-02","dateFormatted":"2020.06.02","excerpt":"The more I learn about Nix — a purely functional package manager — the more I am convinced this is the way forward. Even if there\u0026rsquo;s still room for big …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/github-actions-nix-cachix-dhall/","readingTime":7,"slug":"github-actions-nix-cachix-dhall","subtitle":null,"summary":"\u003cp\u003eThe more I learn about \u003ca href=\"https://nixos.org/nix/\"\u003eNix\u003c/a\u003e — a purely functional package manager — the more I am convinced this is the way forward. Even if there\u0026rsquo;s still room for big improvements.\u003c/p\u003e\n\u003cp\u003eToday I\u0026rsquo;d like to share with y\u0026rsquo;all what I\u0026rsquo;ve been up-to lately. Though, you could imagine I\u0026rsquo;ve been \u003cem\u003eNixifying\u003c/em\u003e more than one project ;)\u003c/p\u003e\n\u003ch3 id=\"nix-shell\"\u003eNix shell\u003c/h3\u003e\n\u003cp\u003eNix shell is a great tool to create reproducible development environments. For example, we could create a shell where the \u003ccode\u003eredis\u003c/code\u003e package is available, without installing it in our system.\u003c/p\u003e","tags":["nix","nixos","cachix","ci","haskell","scala","dhall"],"title":"Github actions powered by Nix Shell \u0026 Cachix","url":"https://gvolpe.com/blog/github-actions-nix-cachix-dhall/","wordCount":1422},{"comments":true,"content":"\u003cp\u003eAs I\u0026rsquo;m preparing a talk about refinement types I will be giving this Thursday at the \u003ca href=\"https://www.meetup.com/FunctionalTricity/events/269763842/\"\u003eFunctional Tricity Meetup\u003c/a\u003e, and I\u0026rsquo;ve recently given a \u003ca href=\"https://scala.love/gabriel-volpe-why-types-matter/\"\u003esimilar talk\u003c/a\u003e using the Scala language as well, I realized there is a missing typeclass in Haskell.\u003c/p\u003e\n\u003cp\u003eIn the following sections, I will be providing examples and use cases for this typeclass to showcase why it would be great to have it in Haskell. Oh, yes\u0026hellip; I love refinement types as well!\u003c/p\u003e\n\u003cp\u003eIn Haskell, we have the \u003ca href=\"https://hackage.haskell.org/package/refined\"\u003erefined\u003c/a\u003e library and other more complex tools such as \u003ca href=\"https://hackage.haskell.org/package/liquidhaskell\"\u003eLiquid Haskell\u003c/a\u003e.\u003c/p\u003e\n\u003ch3 id=\"refinement-types\"\u003eRefinement types\u003c/h3\u003e\n\u003cp\u003eRefinement types give us the ability to define validation rules, or more commonly called \u003cem\u003epredicates\u003c/em\u003e, at the type level. This means we get compile-time validation whenever the values are known at compile-time.\u003c/p\u003e\n\u003cp\u003eSay we have the following predicates and datatype:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e Refined\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAge\u003c/span\u003e  \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eRefine\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eGreaterThan\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e17\u003c/span\u003e) \u003cspan style=\"color:#66d9ef\"\u003eInt\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eName\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eRefine\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eNonEmpty\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eText\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edata\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePerson\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePerson\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  { personAge \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAge\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  , personName \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eName\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  } \u003cspan style=\"color:#66d9ef\"\u003ederiving\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eShow\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWe can validate the creation of \u003ccode\u003ePerson\u003c/code\u003e at compile-time using Template Haskell:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eme\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePerson\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eme\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePerson\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$$\u003c/span\u003e(refineTH \u003cspan style=\"color:#ae81ff\"\u003e32\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e$$\u003c/span\u003e(refineTH \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Gabriel\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIf the age was a number under 18, or the name was an empty string, then our program wouldn\u0026rsquo;t compile. Isn\u0026rsquo;t that cool?\u003c/p\u003e\n\u003cp\u003eThough, most of the time, we need to validate incoming data from external services, meaning \u003cem\u003eruntime validation\u003c/em\u003e. Refined gives us a bunch of useful functions to achieve this, effectively replacing \u003cem\u003esmart constructors\u003c/em\u003e. The most common one is defined as follows:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003erefine\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePredicate\u003c/span\u003e p x \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e x \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eEither\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eRefineException\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eRefined\u003c/span\u003e p x)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWe can then use this function to validate our input data.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emkPerson\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eInt\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eText\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eEither\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eRefineException\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePerson\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emkPerson\u003c/span\u003e a n \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  age  \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e refine a\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  name \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e refine n\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  return \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePerson\u003c/span\u003e age name\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHowever, the program above will short-circuit on the first error, as any other Monad will do. It would be nice if we could validate all our inputs in parallel and accumulates errors, wouldn\u0026rsquo;t it?\u003c/p\u003e\n\u003cp\u003eWe can achieve this by converting our \u003ccode\u003eEither\u003c/code\u003e values given by \u003ccode\u003erefine a\u003c/code\u003e into \u003ccode\u003eValidation\u003c/code\u003e, use \u003ccode\u003eApplicative\u003c/code\u003e functions to compose the different parts, and finally converting back to \u003ccode\u003eEither\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e Data.Validation\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emkPerson\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eInt\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eText\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eEither\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eRefineException\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePerson\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emkPerson\u003c/span\u003e a n \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e toEither \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePerson\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026lt;$\u0026gt;\u003c/span\u003e fromEither (refine a)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026lt;*\u0026gt;\u003c/span\u003e fromEither (refine n)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAs we can see, it is a bit clunky, and this is a very repetitive task, which will only increase the amount of boilerplate in our codebase.\u003c/p\u003e\n\u003cp\u003eThis seems to be the \u003cem\u003estatus quo\u003c/em\u003e around validation in Haskell nowadays, and it was the same in Scala. So it\u0026rsquo;s kind of hard to realize we are missing what we don\u0026rsquo;t know: the \u003ccode\u003eParallel\u003c/code\u003e typeclass. I didn\u0026rsquo;t know it was such a game changer until I started using it everywhere.\u003c/p\u003e\n\u003cp\u003eThis is exactly what this typeclass does for us in other languages, via its helpful functions and instances. Unfortunately, it doesn\u0026rsquo;t exist in Haskell, as far as I know\u0026hellip; until now!\u003c/p\u003e\n\u003ch3 id=\"parallel-typeclass\"\u003eParallel typeclass\u003c/h3\u003e\n\u003cp\u003eLet me introduce you to the \u003ccode\u003eParallel\u003c/code\u003e typeclass, already present in \u003ca href=\"https://pursuit.purescript.org/packages/purescript-parallel/4.0.0/docs/Control.Parallel.Class#t:Parallel\"\u003ePureScript\u003c/a\u003e and \u003ca href=\"https://github.com/typelevel/cats/blob/master/core/src/main/scala/cats/Parallel.scala#L10\"\u003eScala\u003c/a\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e Control.Natural ((\u003cspan style=\"color:#f92672\"\u003e:~\u0026gt;\u003c/span\u003e))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eMonad\u003c/span\u003e m, \u003cspan style=\"color:#66d9ef\"\u003eApplicative\u003c/span\u003e f) \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eParallel\u003c/span\u003e f m \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e m \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e f, f \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e m \u003cspan style=\"color:#66d9ef\"\u003ewhere\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  parallel \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e m \u003cspan style=\"color:#66d9ef\"\u003e:~\u0026gt;\u003c/span\u003e f\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  sequential \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e f \u003cspan style=\"color:#66d9ef\"\u003e:~\u0026gt;\u003c/span\u003e m\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt defines a relationship between a \u003ccode\u003eMonad\u003c/code\u003e that can also be an \u003ccode\u003eApplicative\u003c/code\u003e with \u0026ldquo;parallely\u0026rdquo; behavior. That is, an \u003ccode\u003eApplicative\u003c/code\u003e instance that wouln\u0026rsquo;t pass the monadic laws.\u003c/p\u003e\n\u003cp\u003eThe most common relationship is the one given by \u003ccode\u003eEither\u003c/code\u003e and \u003ccode\u003eValidation\u003c/code\u003e. These two types are isomorphic, with the difference being that \u003ccode\u003eValidation\u003c/code\u003e has an \u003ccode\u003eApplicative\u003c/code\u003e instance that accumulate errors instead of short-circuiting on the first error.\u003c/p\u003e\n\u003cp\u003eSo we can represent this relationship via \u003cem\u003enatural transformation\u003c/em\u003e in a \u003ccode\u003eParallel\u003c/code\u003e instance:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003einstance\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eSemigroup\u003c/span\u003e e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eParallel\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eValidation\u003c/span\u003e e) (\u003cspan style=\"color:#66d9ef\"\u003eEither\u003c/span\u003e e) \u003cspan style=\"color:#66d9ef\"\u003ewhere\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  parallel   \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eNT\u003c/span\u003e fromEither\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  sequential \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eNT\u003c/span\u003e toEither\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn the same way, we can represent the relationship between \u003ccode\u003e[]\u003c/code\u003e and \u003ccode\u003eZipList\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003einstance\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eParallel\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eZipList\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e[]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ewhere\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  parallel   \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eNT\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eZipList\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  sequential \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eNT\u003c/span\u003e getZipList\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eNow, all this ceremony only becomes useful if we define some functions based on \u003ccode\u003eParallel\u003c/code\u003e. One of the most common ones is \u003ccode\u003eparMapN\u003c/code\u003e (or \u003ccode\u003eparMap2\u003c/code\u003e in this case, but ideally, it should be abstracted over its arity).\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eparMapN\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eApplicative\u003c/span\u003e f, \u003cspan style=\"color:#66d9ef\"\u003eMonad\u003c/span\u003e m, \u003cspan style=\"color:#66d9ef\"\u003eParallel\u003c/span\u003e f m)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e m a0\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e m a1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e (a0 \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e a1 \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e a)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e m a\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eparMapN\u003c/span\u003e ma0 ma1 f \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e unwrapNT sequential\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  (f \u003cspan style=\"color:#f92672\"\u003e\u0026lt;$\u0026gt;\u003c/span\u003e unwrapNT parallel ma0 \u003cspan style=\"color:#f92672\"\u003e\u0026lt;*\u0026gt;\u003c/span\u003e unwrapNT parallel ma1)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eBefore we get to see how we can leverage this function with refinement types and data validation, we will define a type alias for our effect type and a function \u003ccode\u003eref\u003c/code\u003e, which will convert \u003ccode\u003eRefineException\u003c/code\u003es into a \u003ccode\u003e[Text]\u003c/code\u003e, since our error type needs to be a \u003ccode\u003eSemigroup\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e Control.Arrow (\u003cspan style=\"color:#a6e22e\"\u003eleft\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e Data.Text     (\u003cspan style=\"color:#a6e22e\"\u003epack\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e Refined\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eEff\u003c/span\u003e a \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eEither\u003c/span\u003e [\u003cspan style=\"color:#66d9ef\"\u003eText\u003c/span\u003e] a\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eref\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePredicate\u003c/span\u003e p x \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e x \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eEff\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eRefined\u003c/span\u003e p x)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eref\u003c/span\u003e x \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e left (\u003cspan style=\"color:#a6e22e\"\u003e\\\u003c/span\u003ee \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e [pack \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e show e]) (refine x)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn the example below, we can appreciate how this function can be used to create a \u003ccode\u003ePerson\u003c/code\u003e instance with validated input data (it\u0026rsquo;s a breeze):\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emkPerson\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eInt\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eText\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eEff\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ePerson\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emkPerson\u003c/span\u003e a n \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e parMapN (ref a) (ref n) \u003cspan style=\"color:#66d9ef\"\u003ePerson\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eOur \u003ccode\u003emkPerson\u003c/code\u003e is now validating all our inputs in parallel via an implicit round-trip \u003ccode\u003eEither\u003c/code\u003e/\u003ccode\u003eValidation\u003c/code\u003e given by our \u003ccode\u003eParallel\u003c/code\u003e instance.\u003c/p\u003e\n\u003cp\u003eWe can also use \u003ccode\u003eparMapN\u003c/code\u003e to use a different \u003ccode\u003eApplicative\u003c/code\u003e instance for lists without manually wrapping / unwrapping \u003ccode\u003eZipList\u003c/code\u003es.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003en1\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e..\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003en2\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\u003cspan style=\"color:#ae81ff\"\u003e6\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e..\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003en3\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e [\u003cspan style=\"color:#66d9ef\"\u003eInt\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003en3\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e\u0026lt;$\u0026gt;\u003c/span\u003e n1 \u003cspan style=\"color:#f92672\"\u003e\u0026lt;*\u0026gt;\u003c/span\u003e n2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003en4\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e [\u003cspan style=\"color:#66d9ef\"\u003eInt\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003en4\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e parMapN n1 n2 (\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWithout \u003ccode\u003eParallel\u003c/code\u003e\u0026rsquo;s simplicity, it would look as follows:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003en4\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e [\u003cspan style=\"color:#66d9ef\"\u003eInt\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003en4\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e getZipList \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e\u0026lt;$\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eZipList\u003c/span\u003e n1 \u003cspan style=\"color:#f92672\"\u003e\u0026lt;*\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eZipList\u003c/span\u003e n2\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eFor convenience, here\u0026rsquo;s another function we can define in terms of \u003ccode\u003eparMapN\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eparTupled\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eApplicative\u003c/span\u003e f, \u003cspan style=\"color:#66d9ef\"\u003eMonad\u003c/span\u003e m, \u003cspan style=\"color:#66d9ef\"\u003eParallel\u003c/span\u003e f m)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e m a0\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e m a1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e m (a0, a1)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eparTupled\u003c/span\u003e ma0 ma1 \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e parMapN ma0 ma1 (,)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn Scala, there\u0026rsquo;s also an instance for \u003ccode\u003eIO\u003c/code\u003e and \u003ccode\u003eIO.Par\u003c/code\u003e, a newtype that provides a different \u003ccode\u003eApplicative\u003c/code\u003e instance, which allows us to use functions such as \u003ccode\u003eparMapN\u003c/code\u003e with \u003ccode\u003eIO\u003c/code\u003e computations to run them in parallel!\u003c/p\u003e\n\u003cp\u003eAnd this is only the beginning\u0026hellip; There are so many other useful functions we could define!\u003c/p\u003e\n\u003cp\u003eFor now, the code is presented in \u003ca href=\"https://github.com/gvolpe/types-matter\"\u003ethis Github repository\u003c/a\u003e together with some other examples. Should there be enough interest, I might polish it and ship it as a library.\u003c/p\u003e\n\u003cp\u003eLet me know your thoughts!\u003c/p\u003e\n\u003cp\u003eGabriel.\u003c/p\u003e\n","date":"2020-04-20","dateFormatted":"2020.04.20","excerpt":"As I\u0026rsquo;m preparing a talk about refinement types I will be giving this Thursday at the Functional Tricity Meetup, and I\u0026rsquo;ve recently given a similar …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/parallel-typeclass-for-haskell/","readingTime":6,"slug":"parallel-typeclass-for-haskell","subtitle":null,"summary":"\u003cp\u003eAs I\u0026rsquo;m preparing a talk about refinement types I will be giving this Thursday at the \u003ca href=\"https://www.meetup.com/FunctionalTricity/events/269763842/\"\u003eFunctional Tricity Meetup\u003c/a\u003e, and I\u0026rsquo;ve recently given a \u003ca href=\"https://scala.love/gabriel-volpe-why-types-matter/\"\u003esimilar talk\u003c/a\u003e using the Scala language as well, I realized there is a missing typeclass in Haskell.\u003c/p\u003e\n\u003cp\u003eIn the following sections, I will be providing examples and use cases for this typeclass to showcase why it would be great to have it in Haskell. Oh, yes\u0026hellip; I love refinement types as well!\u003c/p\u003e","tags":["haskell","purescript","scala"],"title":"Parallel typeclass for Haskell","url":"https://gvolpe.com/blog/parallel-typeclass-for-haskell/","wordCount":1083},{"comments":true,"content":"\u003cp\u003eIf you use \u003ca href=\"https://ubuntu.com/\"\u003eUbuntu\u003c/a\u003e or any other Linux distribution (AKA \u003cem\u003edistro\u003c/em\u003e) together with \u003ca href=\"https://nixos.org/nixpkgs/\"\u003eNixpkgs\u003c/a\u003e, you might have noticed things don\u0026rsquo;t play so well together. Nixpkgs has been mainly designed to work seamlessly in \u003ca href=\"https://nixos.org/nixos/\"\u003eNix OS\u003c/a\u003e; other distros are second class citizens.\u003c/p\u003e\n\u003cp\u003eQuoting the \u003ca href=\"https://github.com/digital-asset/ghcide\"\u003eGhcide\u003c/a\u003e repository, it is defined as:\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '📜';\"\u003e\n  \u003ch4\u003eQuote\u003c/h4\u003e\n  \u003cp\u003e\n\"A library for building Haskell IDE tooling\"\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cp\u003eIt is not defined as an IDE, because it only has a subset of the features \u003ca href=\"https://github.com/haskell/haskell-ide-engine\"\u003eHIE (Haskell IDE Engine)\u003c/a\u003e has. However, the good news is that the HIE and Ghcide teams are joining forces to create \u003ca href=\"https://neilmitchell.blogspot.com/2020/01/one-haskell-ide-to-rule-them-all.html\"\u003eOne Haskell IDE to rule them all\u003c/a\u003e!\u003c/p\u003e\n\u003cp\u003eFor what it\u0026rsquo;s worth, I\u0026rsquo;ve been using HIE for a while together with my favorite text editor, \u003ca href=\"https://neovim.io/\"\u003eNeoVim\u003c/a\u003e. It\u0026rsquo;s got a lot of cool features but it\u0026rsquo;s been quite buggy, in my experience.\u003c/p\u003e\n\u003cp\u003eGhcide only offers a few features that work flawlessly, and my experience has been great so far. Though, setting it up to work properly in NeoVim on Ubuntu when all your software has been installed using Nixpkgs hasn\u0026rsquo;t been easy at all.\u003c/p\u003e\n\u003cdiv style=\"position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;\"\u003e\n      \u003ciframe allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen\" loading=\"eager\" referrerpolicy=\"strict-origin-when-cross-origin\" src=\"https://www.youtube.com/embed/6bP_cpJkzdg?autoplay=0\u0026amp;controls=1\u0026amp;end=0\u0026amp;loop=0\u0026amp;mute=0\u0026amp;start=0\" style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;\" title=\"YouTube video\"\u003e\u003c/iframe\u003e\n    \u003c/div\u003e\n\n\u003cp\u003eThis is the reason why I am writing this blog post. Aim at those who use Nixpkgs like me, but are on a different distro other than NixOS.\u003c/p\u003e\n\u003ch2 id=\"installing-ghcide\"\u003eInstalling Ghcide\u003c/h2\u003e\n\u003cp\u003eIf you go to the main Ghcide repository, you\u0026rsquo;ll find instructions on how to install it. For those using Nix, it is officially recommended using \u003ca href=\"https://github.com/hercules-ci/ghcide-nix\"\u003eGhcide-Nix\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eIf everything went well, you will find its binary in your Nix profile.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eλ ~ which ghcide\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/home/gvolpe/.nix-profile/bin/ghcide\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eTo verify Ghcide works, go to any Haskell project or standalone files, and run \u003ccode\u003eghcide\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eλ ~ ghcide\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eghcide version: 0.1.0 \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eGHC: 8.6.5\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ePATH: /nix/store/02wcifqr53wjs1m5vn670b1avfy071az-ghcide-0.1.0-exe-ghcide/bin/ghcide\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eGhcide setup tester in /workspace/oss/types-matter.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eReport bugs at https://github.com/digital-asset/ghcide/issues\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eStep 1/6: Finding files to test in /workspace/oss/types-matter\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eFound \u003cspan style=\"color:#ae81ff\"\u003e6\u003c/span\u003e files\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eStep 2/6: Looking \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e hie.yaml files that control setup\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eFound \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e cradle\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eStep 3/6, Cradle 1/1: Loading /workspace/oss/types-matter/hie.yaml\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eStep 4/6, Cradle 1/1: Loading GHC Session\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; Warning: The package list \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;hackage.haskell.org\u0026#39;\u003c/span\u003e is \u003cspan style=\"color:#ae81ff\"\u003e15\u003c/span\u003e days old.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; Run \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;cabal update\u0026#39;\u003c/span\u003e to get the latest list of available packages.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; Resolving dependencies...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; Build profile: -w ghc-8.6.5 -O1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; In order, the following will be built \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003euse -v \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e more details\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt;  - types-matter-0.1.0.0 \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003elib\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003econfiguration changed\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; Configuring library \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e types-matter-0.1.0.0..\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; Preprocessing library \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e types-matter-0.1.0.0..\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eghcide: \u0026lt;command line\u0026gt;: cannot satisfy -package-id refined-0.4.4-ea7ce6a6d7ef3587351a3b3bdc4140a3a7bcd0f526627e28a7d5d456e39e9aa9:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    refined-0.4.4-ea7ce6a6d7ef3587351a3b3bdc4140a3a7bcd0f526627e28a7d5d456e39e9aa9 is unusable due to missing dependencies:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      QuickCheck-2.13.2-8aa3fcd8ac86c93ae5db4035083dd975e5b8f88ea826f8604a5748a04c7798bd aeson-1.4.6.0-3a4c86def05154b3acabc0a30d0acd96d235de9d3fd7ba28330e785107e8f5da exceptions-0.10.4-2e686fdff7a6bdbd62e24641169087c094e991de8ab3f6381a859306fd258e32 mtl-2.2.2 prettyprinter-1.3.0-08f8b1f2cf2a49ae7d9056e4ed9fbb3f75b812f789dbb30ab5e82b53b6cc42c8 transformers-0.5.6.2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003euse -v \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e more information\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eYes, you may get a similar output. So let\u0026rsquo;s make it clear.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eAll software installed by Nix works better within a Nix Shell.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eYou hear me. If you want this to work, make sure to you have a Nix Shell set up for your project and even run your text editor within a Nix Shell. \u003cem\u003eThis is the only sane way\u003c/em\u003e.\u003c/p\u003e\n\u003ch3 id=\"configuring-nix-shell\"\u003eConfiguring Nix Shell\u003c/h3\u003e\n\u003cp\u003eNix shell is amazing! It creates an environment where all the necessary software is installed. Once you\u0026rsquo;re done, you leave the shell and come back to your regular terminal. This way we avoid polluting our global environment, i.e. installing unnecessary global software such as Ruby, Node.js, etc.\u003c/p\u003e\n\u003cp\u003eNow, there are many ways to properly set up a Nix Shell for a Haskell project. I personally like to use \u003ca href=\"https://github.com/NixOS/cabal2nix\"\u003eCabal2Nix\u003c/a\u003e to Nixify my \u003ca href=\"https://github.com/haskell/cabal\"\u003eCabal\u003c/a\u003e projects. It basically creates a derivation with all the Haskell packages we need. Here\u0026rsquo;s an example:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ mkDerivation\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e aeson\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e async\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e base\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e bytestring\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e co-log-core\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e containers\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e dhall\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e exceptions\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e hedis\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e postgresql-simple\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e raw-strings-qq\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e refined\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e servant\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e servant-server\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e stdenv\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e template-haskell\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e text\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e uuid\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e wai\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e wai-cors\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e warp\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e wreq\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emkDerivation {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  pname \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;shopping-cart\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  version \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;0.1.0.0\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  src \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e./.\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  isLibrary \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  isExecutable \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  libraryHaskellDepends \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    aeson async base bytestring co-log-core containers dhall exceptions\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    hedis postgresql-simple raw-strings-qq refined servant\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    servant-server template-haskell text uuid wai wai-cors warp wreq\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  executableHaskellDepends \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [ base ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  description \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;The Shopping Cart developed in PFP Scala for Haskell\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  license \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e stdenv\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elicenses\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003easl20;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis derivation belongs in the \u003ccode\u003eshopping-cart.nix\u003c/code\u003e file, and it was created from a \u003ccode\u003eshopping-cart.cabal\u003c/code\u003e file as follows:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecabal2nix . \u0026gt; shopping-cart.nix\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn my current set up, I have three other Nix files that are needed in order to properly run this project within a Nix Shell: \u003ccode\u003edefault.nix\u003c/code\u003e, \u003ccode\u003erelease.nix\u003c/code\u003e, and \u003ccode\u003eshell.nix\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eThis is the content of \u003ccode\u003edefault.nix\u003c/code\u003e, which indicates what GHC version we need:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ nixpkgs \u003cspan style=\"color:#f92672\"\u003e?\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026lt;nixpkgs\u0026gt;\u003c/span\u003e {}\u003cspan style=\"color:#f92672\"\u003e,\u003c/span\u003e compiler \u003cspan style=\"color:#f92672\"\u003e?\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ghc865\u0026#34;\u003c/span\u003e }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enixpkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehaskell\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epackages\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ecompiler\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecallPackage \u003cspan style=\"color:#e6db74\"\u003e./shopping-cart.nix\u003c/span\u003e { }\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eNext is the content of \u003ccode\u003erelease.nix\u003c/code\u003e, which only refers to \u003ccode\u003edefault.nix\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  pkgs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026lt;nixpkgs\u0026gt;\u003c/span\u003e { };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  pkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehaskellPackages\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecallPackage \u003cspan style=\"color:#e6db74\"\u003e./default.nix\u003c/span\u003e { }\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eFinally, the most important one, \u003ccode\u003eshell.nix\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-nix\" data-lang=\"nix\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{ nixpkgs \u003cspan style=\"color:#f92672\"\u003e?\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026lt;nixpkgs\u0026gt;\u003c/span\u003e {} }:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e (nixpkgs) pkgs;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003einherit\u003c/span\u003e (pkgs) haskellPackages;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  project \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e./release.nix\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epkgs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estdenv\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emkDerivation {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  name \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;shell\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  buildInputs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e project\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenv\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enativeBuildInputs \u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    haskellPackages\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecabal-install\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    haskellPackages\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehlint\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  shellHook \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    export NIX_GHC=\u0026#34;$(which ghc)\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    export NIX_GHCPKG=\u0026#34;$(which ghc-pkg)\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    export NIX_GHC_DOCDIR=\u0026#34;$NIX_GHC/../../share/doc/ghc/html\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    export NIX_GHC_LIBDIR=\u0026#34;$(ghc --print-libdir)\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e  \u0026#39;\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe important part here is our \u003ccode\u003eshellHook\u003c/code\u003e, which exports a few environment variables needed to get GHC properly detected by Nixpkgs within a Nix Shell.\u003c/p\u003e\n\u003cp\u003eFurthermore, these Nix files could probably be simplified into two files or maybe a single one but, to be honest, I haven\u0026rsquo;t bothered in trying to do so. Nix is amazing when it works but, when it doesn\u0026rsquo;t, it\u0026rsquo;s a huge pain to find out what\u0026rsquo;s going on.\u003c/p\u003e\n\u003cp\u003eYou can find all these Nix files in this \u003ca href=\"https://github.com/gvolpe/shopping-cart-haskell\"\u003eGithub repository\u003c/a\u003e.\u003c/p\u003e\n\u003ch2 id=\"troubleshooting\"\u003eTroubleshooting\u003c/h2\u003e\n\u003cp\u003eThere could be a lot of different things going wrong. We have seen a first common error above, but there could be a few others. Find below a summary of common issues.\u003c/p\u003e\n\u003ch3 id=\"error-1\"\u003eError #1\u003c/h3\u003e\n\u003cp\u003eThe following error is one of the most common ones:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecannot satisfy -package-id ghc-8.6.5\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eI have reported \u003ca href=\"https://github.com/digital-asset/ghcide/issues/439\"\u003ethis issue\u003c/a\u003e. This seems to happen because the GHC version detected by Ghcide is not the same it is expected by the Cabal project. Fortunately, this is fixed by the export of the environment variables defined in our \u003ccode\u003eshellHook\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eNotice that the error message might show a different package, such as \u003ccode\u003eaeson\u003c/code\u003e, not only \u003ccode\u003eghc\u003c/code\u003e. The fix seems to be the same, though.\u003c/p\u003e\n\u003ch3 id=\"error-2\"\u003eError #2\u003c/h3\u003e\n\u003cp\u003eYou may get a lot of the following errors as well:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eStep 6/6: Type checking the files\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eFile:     /workspace/oss/shopping-cart-haskell/app/Main.hs\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eHidden:   no\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eRange:    5:17-5:28\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSource:   not found\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSeverity: DsError\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eMessage:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  Could not find module ‘Http.Server’\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  It is not a module in the current program, or in any known package.\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis is normally fixed by creating a specific \u003ccode\u003ehie.yaml\u003c/code\u003e file, indicating \u003ca href=\"https://github.com/mpickering/hie-bios\"\u003ehie-bios\u003c/a\u003e how to read our project. Here\u0026rsquo;s the one defined in the Shopping Cart project:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecradle: \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003ecabal: \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003ecomponent: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;lib:shopping-cart\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e}}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eYou will still get the same error with your \u003ccode\u003eSetup.hs\u003c/code\u003e file, defined by Cabal:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eMessage:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  Could not load module ‘Distribution.Simple’\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  It is a member of the hidden package ‘Cabal-2.4.0.1’.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  Perhaps you need to add ‘Cabal’ to the build-depends in your .cabal file.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eFiles that failed:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * /workspace/oss/shopping-cart-haskell/Setup.hs\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHowever, it doesn\u0026rsquo;t matter. Ghcide will still work in our project; this is not an issue.\u003c/p\u003e\n\u003ch3 id=\"error-3\"\u003eError #3\u003c/h3\u003e\n\u003cp\u003eThe following error is somewhat a nasty one:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eError ghcide: \u0026lt;command line\u0026gt;: can\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003et load .so/.DLL\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIf you stumble upon this one, make sure to update to the latest version. I had this error, which is reported \u003ca href=\"https://github.com/digital-asset/ghcide/issues/404\"\u003ehere\u003c/a\u003e, and got it fixed after updating Ghcide.\u003c/p\u003e\n\u003ch2 id=\"bonus-track\"\u003eBonus Track\u003c/h2\u003e\n\u003cp\u003eAs I have got used to having a linter and a formatter integrated in NeoVim when using HIE, I kind of miss these features, which are probably going to land in the future Haskell IDE as plugins.\u003c/p\u003e\n\u003cp\u003eSo I came up with a few (temporary) commands to get \u003ca href=\"https://github.com/ndmitchell/hlint\"\u003eHLint\u003c/a\u003e and \u003ca href=\"https://github.com/lspitzner/brittany/\"\u003eBrittany\u003c/a\u003e integrated in NeoVim.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-vim\" data-lang=\"vim\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ennoremap\u003c/span\u003e \u0026lt;\u003cspan style=\"color:#a6e22e\"\u003eleader\u003c/span\u003e\u0026gt;\u003cspan style=\"color:#a6e22e\"\u003eaf\u003c/span\u003e :\u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e !\u003cspan style=\"color:#a6e22e\"\u003ebrittany\u003c/span\u003e --\u003cspan style=\"color:#a6e22e\"\u003ewrite\u003c/span\u003e-\u003cspan style=\"color:#a6e22e\"\u003emode\u003c/span\u003e=\u003cspan style=\"color:#a6e22e\"\u003einplace\u003c/span\u003e %:\u003cspan style=\"color:#a6e22e\"\u003ep\u003c/span\u003e\u0026lt;\u003cspan style=\"color:#a6e22e\"\u003eCR\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ennoremap\u003c/span\u003e \u0026lt;\u003cspan style=\"color:#a6e22e\"\u003eleader\u003c/span\u003e\u0026gt;\u003cspan style=\"color:#a6e22e\"\u003eal\u003c/span\u003e :\u003cspan style=\"color:#a6e22e\"\u003eAsyncRun\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ehlint\u003c/span\u003e %:\u003cspan style=\"color:#a6e22e\"\u003ep\u003c/span\u003e\u0026lt;\u003cspan style=\"color:#a6e22e\"\u003eCR\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe first one runs \u003ccode\u003ebrittany\u003c/code\u003e on the current file in the buffer and applies its output \u003cem\u003einplace\u003c/em\u003e. The latter runs \u003ccode\u003ehlint\u003c/code\u003e\nfor the current file in the buffer and it displays its result in a quickfix window. It uses the \u003ca href=\"https://github.com/skywind3000/asyncrun.vim\"\u003easyncrun.vim\u003c/a\u003e plugin.\u003c/p\u003e\n\u003cp\u003eIf you are interested in my full configuration, you can find it \u003ca href=\"https://github.com/gvolpe/nix-config\"\u003ehere\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eThe future of IDEs in Haskell is only getting brighter :)\u003c/p\u003e\n\u003cp\u003eGabriel.\u003c/p\u003e\n","date":"2020-03-21","dateFormatted":"2020.03.21","excerpt":"If you use Ubuntu or any other Linux distribution (AKA distro) together with Nixpkgs, you might have noticed things don\u0026rsquo;t play so well together. Nixpkgs …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/setting-up-ghcide-nixpkgs-ubuntu/","readingTime":7,"slug":"setting-up-ghcide-nixpkgs-ubuntu","subtitle":null,"summary":"\u003cp\u003eIf you use \u003ca href=\"https://ubuntu.com/\"\u003eUbuntu\u003c/a\u003e or any other Linux distribution (AKA \u003cem\u003edistro\u003c/em\u003e) together with \u003ca href=\"https://nixos.org/nixpkgs/\"\u003eNixpkgs\u003c/a\u003e, you might have noticed things don\u0026rsquo;t play so well together. Nixpkgs has been mainly designed to work seamlessly in \u003ca href=\"https://nixos.org/nixos/\"\u003eNix OS\u003c/a\u003e; other distros are second class citizens.\u003c/p\u003e\n\u003cp\u003eQuoting the \u003ca href=\"https://github.com/digital-asset/ghcide\"\u003eGhcide\u003c/a\u003e repository, it is defined as:\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '📜';\"\u003e\n  \u003ch4\u003eQuote\u003c/h4\u003e\n  \u003cp\u003e\n\"A library for building Haskell IDE tooling\"\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cp\u003eIt is not defined as an IDE, because it only has a subset of the features \u003ca href=\"https://github.com/haskell/haskell-ide-engine\"\u003eHIE (Haskell IDE Engine)\u003c/a\u003e has. However, the good news is that the HIE and Ghcide teams are joining forces to create \u003ca href=\"https://neilmitchell.blogspot.com/2020/01/one-haskell-ide-to-rule-them-all.html\"\u003eOne Haskell IDE to rule them all\u003c/a\u003e!\u003c/p\u003e","tags":["haskell","nix","ide"],"title":"Setting up Ghcide in Ubuntu with Nixpkgs","url":"https://gvolpe.com/blog/setting-up-ghcide-nixpkgs-ubuntu/","wordCount":1325},{"comments":true,"content":"\u003cp\u003eThese past few weeks I\u0026rsquo;ve been interviewing with a few companies, as I\u0026rsquo;m actively on the \u003ca href=\"https://twitter.com/volpegabriel87/status/1224794505460822017\"\u003elookout for a new challenge\u003c/a\u003e, and the number of flaws I have encountered in some hiring processes has disturbed me.\u003c/p\u003e\n\u003cp\u003eInterviewing is hard; interviewing Software Engineers is a work of art.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/interview.png\" alt=\"interview\"\u003e\u003c/p\u003e\n\u003cp\u003eLet\u0026rsquo;s be honest. Software Engineers, especially in the functional programming space, are a scarce resource. So the better the interview process, the bigger the chances you have of hiring great engineers.\u003c/p\u003e\n\u003cp\u003eDuring my career, I\u0026rsquo;ve been part of the hiring process from day one, and I have learned a few lessons I would like to share with you all, given my recent frustration with some companies.\u003c/p\u003e\n\u003ch2 id=\"1-two-or-more-engineers\"\u003e1. Two or more engineers\u003c/h2\u003e\n\u003cp\u003eMost interviewing processes have a pair-programming session, which is probably the most delicate part. You will not only assess a candidate\u0026rsquo;s coding skills but also their ability to work in a team.\u003c/p\u003e\n\u003cp\u003eIt was at this stage where I\u0026rsquo;ve seen many companies fail. They commit the \u003cstrong\u003emost terrible mistake\u003c/strong\u003e: having a single engineer on the company\u0026rsquo;s side.\u003c/p\u003e\n\u003cp\u003eThis is sort of a monarchy where you, the candidate, are judged by an individual who might not be as competent as two or more persons in doing so.\u003c/p\u003e\n\u003cp\u003eI cannot enumerate the number of times I have disagreed with my colleagues while interviewing a candidate. We all have different points of view, so having at least one more brain in the room will open up the chance for debate and avoid ending up with a biased decision.\u003c/p\u003e\n\u003cp\u003eHaving two or more engineers applies to conducting any other interview, such as system design, code review, or behavioral assessment.\u003c/p\u003e\n\u003ch2 id=\"2-give-feedback\"\u003e2. Give feedback\u003c/h2\u003e\n\u003cp\u003eThe sooner you get back to your candidate with feedback, the better. Whether you decide to move forward or not.\u003c/p\u003e\n\u003cp\u003eI would argue it is even more important to give feedback when you don\u0026rsquo;t want to proceed with the candidate. Let them know about the reasons why you think they will not be considered for the current position.\u003c/p\u003e\n\u003cp\u003eIt is a professional courtesy that will make your company respectable.\u003c/p\u003e\n\u003ch2 id=\"3-ask-for-feedback\"\u003e3. Ask for feedback\u003c/h2\u003e\n\u003cp\u003eGiving feedback shows professionalism; asking for feedback shows humility.\u003c/p\u003e\n\u003cp\u003eYour interview process might be \u003cem\u003eutterly broken\u003c/em\u003e and far from perfect. So take this chance to get an outsider\u0026rsquo;s opinion to improve it.\u003c/p\u003e\n\u003cp\u003eA good convo is to ask for feedback in the same email where you are giving it.\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '📜';\"\u003e\n  \u003ch4\u003eExample\u003c/h4\u003e\n  \u003cp\u003e\nDear candidate, \u003cbr/\u003e\n\u003cbr/\u003e\nWe have decided not to move forward given the following reasons: \u003cbr/\u003e\n\u003cbr/\u003e\n- first point \u003c/br\u003e\n- second point \u003c/br\u003e\n- etc \u003c/br\u003e\n\u003cbr/\u003e\nIf you have some time, we kindly request your feedback about our hiring process. What did you think about it?\n\u003cbr/\u003e\nKind regards,\nThe interviewer.\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cp\u003eBeing humble is one underrated skill.\u003c/p\u003e\n\u003ch2 id=\"4-coding-challenge\"\u003e4. Coding challenge\u003c/h2\u003e\n\u003cp\u003eIf, as part of the process, you decide to go with a take-home coding challenge, be flexible when it comes to third-party libraries usage or different approaches.\u003c/p\u003e\n\u003cp\u003eRemember that you are asking engineers to free some of their time to get an \u003cem\u003eunpaid task\u003c/em\u003e done. So at least let them choose the tech stack.\u003c/p\u003e\n\u003cp\u003eMost engineers take this task as a chance to explore some libraries they love or would love to use. Other engineers wouldn\u0026rsquo;t even bother going through all of this, so be considerable.\u003c/p\u003e\n\u003cp\u003eWhat is more important is to evaluate their thinking-and-solving-problems ability. As long as they can defend their solution in the code review stage, the techniques or third-party libraries they might use will remain secondary. Defending a solution is something we, engineers, need to do quite often. So you will be assessing another critical engineering skill.\u003c/p\u003e\n\u003cp\u003eIn the meantime, you — \u003cem\u003ethe interviewer\u003c/em\u003e — might learn a few things as well; it is a back-and-forth learning process.\u003c/p\u003e\n\u003ch2 id=\"5-cut-the-bullshit\"\u003e5. Cut the bullshit\u003c/h2\u003e\n\u003cp\u003eLet the formalities be handled by those who enjoy living in a world of bureaucracy.\u003c/p\u003e\n\u003cp\u003eIf you know you have the chance to hire a great engineer, cut the bullshit and offer an alternative and quicker hiring process. Engineers hate beadledom.\u003c/p\u003e\n\u003cp\u003eIf you succeed, you will more likely secure a bunch of great engineers who would be happy to work in a relaxed environment, free of that they spite the most.\u003c/p\u003e\n\u003cp\u003eAdditionally, the happiness of your employees comes with other benefits; they will promote your company and recommend it to other engineers. It is a win-win strategy.\u003c/p\u003e\n\u003cp\u003eUltimately, be clear about what you are looking for, but also be honest about the current situation of the company. Nobody wants to jump on a sinking ship. Don\u0026rsquo;t oversell it.\u003c/p\u003e\n\u003ch1 id=\"summary\"\u003eSummary\u003c/h1\u003e\n\u003cp\u003eThis is my top five. However, I believe a great hiring process should always be in constant improvement. Question your techniques and results at all times, and aim for a top-notch hiring process.\u003c/p\u003e\n\u003cp\u003eHow about you, Software Engineer? What other pain points have you stumbled upon while taking interviews?\u003c/p\u003e\n\u003cp\u003eAt last, I\u0026rsquo;ll leave you with another great resource on \u003ca href=\"http://www.lihaoyi.com/post/HowtoconductagoodProgrammingInterview.html\"\u003ehow to conduct a good programming interview\u003c/a\u003e.\u003c/p\u003e\n","date":"2020-02-11","dateFormatted":"2020.02.11","excerpt":"These past few weeks I\u0026rsquo;ve been interviewing with a few companies, as I\u0026rsquo;m actively on the lookout for a new challenge, and the number of flaws I have …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/the-art-of-interviewing-engineers/","readingTime":4,"slug":"the-art-of-interviewing-engineers","subtitle":null,"summary":"\u003cp\u003eThese past few weeks I\u0026rsquo;ve been interviewing with a few companies, as I\u0026rsquo;m actively on the \u003ca href=\"https://twitter.com/volpegabriel87/status/1224794505460822017\"\u003elookout for a new challenge\u003c/a\u003e, and the number of flaws I have encountered in some hiring processes has disturbed me.\u003c/p\u003e\n\u003cp\u003eInterviewing is hard; interviewing Software Engineers is a work of art.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/interview.png\" alt=\"interview\"\u003e\u003c/p\u003e\n\u003cp\u003eLet\u0026rsquo;s be honest. Software Engineers, especially in the functional programming space, are a scarce resource. So the better the interview process, the bigger the chances you have of hiring great engineers.\u003c/p\u003e","tags":["software"],"title":"The art of interviewing engineers","url":"https://gvolpe.com/blog/the-art-of-interviewing-engineers/","wordCount":814},{"comments":true,"content":"\u003cp\u003eIn the past few months I have learnt a lot! Probably the coolest stuff has been about \u003ca href=\"https://wiki.haskell.org/Functional_dependencies\"\u003eFunctional Dependencies\u003c/a\u003e and \u003ca href=\"https://wiki.haskell.org/GHC/Type_families\"\u003eType Families\u003c/a\u003e, so this is my attempt to explain it in order to gain a better understanding and hopefully help someone else out there as well.\u003c/p\u003e\n\u003cp\u003eSo please be kind if you see any mistake, let me know and I\u0026rsquo;ll try to fix it 🙂\u003c/p\u003e\n\u003ch3 id=\"a-motivating-example\"\u003eA motivating example\u003c/h3\u003e\n\u003cp\u003eOne of the fun applications I\u0026rsquo;ve worked on is \u003ca href=\"https://github.com/gvolpe/exchange-rates\"\u003eexchange-rates\u003c/a\u003e, which uses the \u003ca href=\"https://www.fpcomplete.com/blog/2017/07/the-rio-monad\"\u003eRIO Monad\u003c/a\u003e (basically \u003ccode\u003eReaderT\u003c/code\u003e + \u003ccode\u003eIO\u003c/code\u003e).\u003c/p\u003e\n\u003cp\u003eWhen defining dependencies using such effect is very common to do it using the \u003ca href=\"https://www.fpcomplete.com/blog/2017/06/readert-design-pattern\"\u003eHas typeclass approach\u003c/a\u003e (or how I prefer to call it, the \u003cem\u003eclassy lenses Has pattern\u003c/em\u003e) instead of passing the whole context / environment.\u003c/p\u003e\n\u003cp\u003eFollowing this approach, I have defined a polymorphic \u003ccode\u003eCtx\u003c/code\u003e record that represents the application context (or dependencies). It looks as follows:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edata\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCtx\u003c/span\u003e m \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCtx\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  { ctxLogger \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLogger\u003c/span\u003e m\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  , ctxCache \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e m\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  , ctxForexClient \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eForexClient\u003c/span\u003e m\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  }\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIf we continue with the \u003ccode\u003eHas\u003c/code\u003e approach we would get something like this for our \u003ccode\u003eLogger m\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eHasLogger\u003c/span\u003e ctx \u003cspan style=\"color:#66d9ef\"\u003ewhere\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  loggerL \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLens\u0026#39;\u003c/span\u003e ctx (\u003cspan style=\"color:#66d9ef\"\u003eLogger\u003c/span\u003e m)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003einstance\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eHasLogger\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eCtx\u003c/span\u003e m) \u003cspan style=\"color:#66d9ef\"\u003ewhere\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  loggerL \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lens ctxLogger (\u003cspan style=\"color:#a6e22e\"\u003e\\\u003c/span\u003ex y \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e x { ctxLogger \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e y })\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eBut\u0026hellip; Oops, it doesn\u0026rsquo;t compile!\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCouldn\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;t match type ‘m1’ with ‘m’\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e      ‘m1’ is a rigid type variable bound by\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e        the type signature for:\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e          loggerL :: forall (m1 :: * -\u0026gt; *). Lens\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eCtx m\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eLogger m1\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        at src/Context.hs:27:3-9\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      ‘m’ is a rigid type variable bound by\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        the instance declaration\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        at src/Context.hs:26:10-47\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe reason is that the compiler has no way to know that the \u003ccode\u003em\u003c/code\u003e in \u003ccode\u003eLogger m\u003c/code\u003e (declared in the type class) is the same as the \u003ccode\u003em\u003c/code\u003e in \u003ccode\u003eCtx m\u003c/code\u003e (declared in the instance), therefore the inferred type ends up being \u003ccode\u003eLens' (Ctx m) (Logger m1)\u003c/code\u003e.\u003c/p\u003e\n\u003ch3 id=\"functional-dependencies-to-the-rescue\"\u003eFunctional Dependencies to the rescue\u003c/h3\u003e\n\u003cp\u003eWe can fix it by introducing a language extension named \u003ca href=\"https://wiki.haskell.org/Functional_dependencies\"\u003eFunctionalDependencies\u003c/a\u003e, introduced in the paper \u003ca href=\"https://web.cecs.pdx.edu/~mpj/pubs/fundeps-esop2000.pdf\"\u003eType Classes with Functional Dependencies\u003c/a\u003e by Mark P. Jones in March 2000.\u003c/p\u003e\n\u003cp\u003eWe need to change our type class definition as below:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e{-# LANGUAGE FlexibleInstances      #-}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e{-# LANGUAGE FunctionalDependencies #-}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e{-# LANGUAGE MultiParamTypeClasses  #-}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eHasLogger\u003c/span\u003e ctx m \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e ctx \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e m \u003cspan style=\"color:#66d9ef\"\u003ewhere\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  loggerL \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLens\u0026#39;\u003c/span\u003e ctx (\u003cspan style=\"color:#66d9ef\"\u003eLogger\u003c/span\u003e m)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003einstance\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eHasLogger\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eCtx\u003c/span\u003e m) m \u003cspan style=\"color:#66d9ef\"\u003ewhere\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  loggerL \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lens ctxLogger (\u003cspan style=\"color:#a6e22e\"\u003e\\\u003c/span\u003ex y \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e x { ctxLogger \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e y })\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eOur type class has now two parameters, \u003ccode\u003ectx\u003c/code\u003e and \u003ccode\u003em\u003c/code\u003e, and in addition we define a \u003cem\u003efunctional dependency\u003c/em\u003e \u003ccode\u003ectx -\u0026gt; m\u003c/code\u003e. This means that \u003ccode\u003ectx\u003c/code\u003e uniquely determines the type of \u003ccode\u003em\u003c/code\u003e, which constraints the possible instances and helps with type\ninference.\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: 'ℹ️';\"\u003e\n  \u003ch4\u003eAttention\u003c/h4\u003e\n  \u003cp\u003e\nNotice the extensions we had to enable to make this compile.\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003ch4 id=\"arithmetic-example\"\u003eArithmetic example\u003c/h4\u003e\n\u003cp\u003eHere\u0026rsquo;s another example taken from the \u003ca href=\"http://www.cse.chalmers.se/~hallgren/Papers/hallgren.pdf\"\u003eFun with Functional Dependencies\u003c/a\u003e paper by Thomas Hallgren. What\u0026rsquo;s simpler than adding two values together?\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAdd\u003c/span\u003e a b c \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e a b \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e c \u003cspan style=\"color:#66d9ef\"\u003ewhere\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  add \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e a \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e b \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e c\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003einstance\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAdd\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eZero\u003c/span\u003e b b\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003einstance\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAdd\u003c/span\u003e a b c \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAdd\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eSucc\u003c/span\u003e a) b (\u003cspan style=\"color:#66d9ef\"\u003eSucc\u003c/span\u003e c)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe functional dependency is pretty clear: given \u003ccode\u003ea\u003c/code\u003e and \u003ccode\u003eb\u003c/code\u003e we can add them together and produce \u003ccode\u003ec\u003c/code\u003e. So \u003ccode\u003ea\u003c/code\u003e and\n\u003ccode\u003eb\u003c/code\u003e uniquely determine \u003ccode\u003ec\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eNotice how we don\u0026rsquo;t even need to define \u003ccode\u003eadd\u003c/code\u003e, specifying the types is enough! If it\u0026rsquo;s still not clear, bear with me and let\u0026rsquo;s perform type substitution step by step (feel free to skip this part):\u003c/p\u003e\n\u003cp\u003eGiven this instance, all we are saying is that:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003einstance\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAdd\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eZero\u003c/span\u003e b b \u003cspan style=\"color:#66d9ef\"\u003ewhere\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cul\u003e\n\u003cli\u003e\u003ccode\u003ea\u003c/code\u003e is defined as \u003ccode\u003eZero\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eb\u003c/code\u003e is defined as \u003ccode\u003eb\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ec\u003c/code\u003e is defined as \u003ccode\u003eb\u003c/code\u003e.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eSince we now know the types of \u003ccode\u003ea\u003c/code\u003e, \u003ccode\u003eb\u003c/code\u003e and \u003ccode\u003ec\u003c/code\u003e, defining the \u003ccode\u003eadd\u003c/code\u003e function becomes redundant:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003einstance\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAdd\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eZero\u003c/span\u003e b b \u003cspan style=\"color:#66d9ef\"\u003ewhere\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  add \u003cspan style=\"color:#66d9ef\"\u003eZero\u003c/span\u003e b \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e b\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eClear now? Awesome! Let\u0026rsquo;s try this out in the REPL:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eλ\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003et add (u\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eThree\u003c/span\u003e) (u\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eZero\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eSucc\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eSucc\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eSucc\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eZero\u003c/span\u003e))\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cblockquote\u003e\n\u003cp\u003eWhere \u003ccode\u003eu = undefined\u003c/code\u003e, just a convenient type alias.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eFunctional Dependencies have proven to be very useful since it solves a real problem. But software evolves and so Type Families were created, the topic of the next section.\u003c/p\u003e\n\u003ch3 id=\"type-families\"\u003eType Families\u003c/h3\u003e\n\u003cp\u003eType Families were introduced in the paper \u003ca href=\"https://www.microsoft.com/en-us/research/wp-content/uploads/2016/07/typefun.pdf?from=http%3A%2F%2Fresearch.microsoft.com%2F~simonpj%2Fpapers%2Fassoc-types%2Ffun-with-type-funs%2Ftypefun.pdf\"\u003eFun with type\nfunctions\u003c/a\u003e by Oleg Kiselyov, Simon Peyton Jones and Chung-chieh Shan in May 2010.\u003c/p\u003e\n\u003cp\u003eThis GHC extension allows functions on types to be expressed as straightforwardly as functions on values. This means\nthat our functions are executed at compile time, during type checking.\u003c/p\u003e\n\u003cp\u003eSo here\u0026rsquo;s how we can define our \u003ccode\u003eHasLogger\u003c/code\u003e class using Type Families instead:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e{-# LANGUAGE TypeFamilies #-}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e Data.Kind (\u003cspan style=\"color:#66d9ef\"\u003eType\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eHasLogger\u003c/span\u003e ctx \u003cspan style=\"color:#66d9ef\"\u003ewhere\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLoggerF\u003c/span\u003e ctx \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eType\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eType\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  loggerL \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLens\u0026#39;\u003c/span\u003e ctx (\u003cspan style=\"color:#66d9ef\"\u003eLogger\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eLoggerF\u003c/span\u003e ctx))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003einstance\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eHasLogger\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eCtx\u003c/span\u003e m) \u003cspan style=\"color:#66d9ef\"\u003ewhere\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLoggerF\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eCtx\u003c/span\u003e m) \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e m\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  loggerL \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lens ctxLogger (\u003cspan style=\"color:#a6e22e\"\u003e\\\u003c/span\u003ex y \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e x { ctxLogger \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e y })\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eOur class has once again a single parameter \u003ccode\u003ectx\u003c/code\u003e and our functional dependency is now expressed as an \u003cem\u003eassociated type\u003c/em\u003e of the class (type family). It behaves like a function at the type level, so we can call \u003ccode\u003eLoggerF\u003c/code\u003e a type function.\u003c/p\u003e\n\u003cp\u003eNotice how we explicitly define the kind of our type function to be \u003ccode\u003eType -\u0026gt; Type\u003c/code\u003e (formerly \u003ccode\u003e* -\u0026gt; *\u003c/code\u003e). If we don\u0026rsquo;t do it the default inferred kind will just be \u003ccode\u003eType\u003c/code\u003e (formerly \u003ccode\u003e*\u003c/code\u003e).\u003c/p\u003e\n\u003ch4 id=\"arithmetic-example-1\"\u003eArithmetic example\u003c/h4\u003e\n\u003cp\u003eIn a similar way, we can define the \u003ccode\u003eAdd\u003c/code\u003e class using Type Families:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAdd\u003c/span\u003e a b \u003cspan style=\"color:#66d9ef\"\u003ewhere\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAddF\u003c/span\u003e a b \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eType\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  add \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e a \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e b \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAddF\u003c/span\u003e a b\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003einstance\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAdd\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eZero\u003c/span\u003e b \u003cspan style=\"color:#66d9ef\"\u003ewhere\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAddF\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eZero\u003c/span\u003e b \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e b\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003einstance\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAdd\u003c/span\u003e a b \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAdd\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eSucc\u003c/span\u003e a) b \u003cspan style=\"color:#66d9ef\"\u003ewhere\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eAddF\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eSucc\u003c/span\u003e a) b \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eSucc\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eAddF\u003c/span\u003e a b)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe \u003ccode\u003ec\u003c/code\u003e parameter defined before is now replaced by the \u003ccode\u003eAddF a b\u003c/code\u003e type function. Here we define the kind of the type\nfunction as a good practice but it\u0026rsquo;s not necessary.\u003c/p\u003e\n\u003cp\u003eAnd again, we can try this out in the REPL:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eλ\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003et add (u\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eThree\u003c/span\u003e) (u\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eSucc\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eZero\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eSucc\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eSucc\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eSucc\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eSucc\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eZero\u003c/span\u003e)))\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"polymorphic-mutable-ref\"\u003ePolymorphic Mutable Ref\u003c/h4\u003e\n\u003cp\u003eFurthermore, with Type Families we could define a polymorphic mutable ref class where \u003ccode\u003em\u003c/code\u003e defines \u003ccode\u003eRef\u003c/code\u003e (example taken from\nthe paper \u003cem\u003eFun with type functions\u003c/em\u003e linked above).\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eMutation\u003c/span\u003e m \u003cspan style=\"color:#66d9ef\"\u003ewhere\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eRef\u003c/span\u003e m \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eType\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eType\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  newRef   \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e a \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e m (\u003cspan style=\"color:#66d9ef\"\u003eRef\u003c/span\u003e m a)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  readRef  \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eRef\u003c/span\u003e m a \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e m a\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  writeRef \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eRef\u003c/span\u003e m a \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e a \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e m ()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003einstance\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eMutation\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eIO\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ewhere\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eRef\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eIO\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eIORef\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  newRef   \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e newIORef\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  readRef  \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e readIORef\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  writeRef \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e writeIORef\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003einstance\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eMutation\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eST\u003c/span\u003e s) \u003cspan style=\"color:#66d9ef\"\u003ewhere\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eRef\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eST\u003c/span\u003e s) \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eSTRef\u003c/span\u003e s\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  newRef   \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e newSTRef\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  readRef  \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e readSTRef\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  writeRef \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e writeSTRef\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eOnce the compiler knows what \u003ccode\u003em\u003c/code\u003e is it\u0026rsquo;s over. It\u0026rsquo;ll know what the type of the mutable ref is as well. And as a bonus, type inference will work flawlessly.\u003c/p\u003e\n\u003ch3 id=\"final-thoughts\"\u003eFinal Thoughts\u003c/h3\u003e\n\u003cp\u003eMost use cases of \u003ccode\u003eFunctionalDependencies\u003c/code\u003e can be expressed using \u003ccode\u003eTypeFamilies\u003c/code\u003e, however there are some subtle differences that come to light only in complex scenarios.\u003c/p\u003e\n\u003cp\u003eMost library authors and developers prefer to use \u003ccode\u003eTypeFamilies\u003c/code\u003e nowadays. Its main advantage over \u003ccode\u003eFunctionalDependencies\u003c/code\u003e is speed but it\u0026rsquo;s also possible to express many cases that require the extensions \u003ccode\u003eTypeSynonymInstances\u003c/code\u003e, \u003ccode\u003eFlexibleInstances\u003c/code\u003e, \u003ccode\u003eMultiParamTypeClasses\u003c/code\u003e and \u003ccode\u003eUndecidableInstances\u003c/code\u003e without them.\u003c/p\u003e\n\u003cp\u003eSo when is it more convenient to use the former? You can find a more detailed comparison in the following articles:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://wiki.haskell.org/Functional_dependencies_vs._type_families\"\u003ehttps://wiki.haskell.org/Functional_dependencies_vs._type_families\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://gitlab.haskell.org/ghc/ghc/wikis/tf-vs-fd\"\u003ehttps://gitlab.haskell.org/ghc/ghc/wikis/tf-vs-fd\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eSpecial thanks to \u003ca href=\"https://twitter.com/ChShersh\"\u003eDmitrii Kovanikov\u003c/a\u003e for reviewing the first draft!\u003c/p\u003e\n","date":"2019-08-27","dateFormatted":"2019.08.27","excerpt":"In the past few months I have learnt a lot! Probably the coolest stuff has been about Functional Dependencies and Type Families, so this is my attempt to …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/functional-dependencies-and-type-families/","readingTime":6,"slug":"functional-dependencies-and-type-families","subtitle":null,"summary":"\u003cp\u003eIn the past few months I have learnt a lot! Probably the coolest stuff has been about \u003ca href=\"https://wiki.haskell.org/Functional_dependencies\"\u003eFunctional Dependencies\u003c/a\u003e and \u003ca href=\"https://wiki.haskell.org/GHC/Type_families\"\u003eType Families\u003c/a\u003e, so this is my attempt to explain it in order to gain a better understanding and hopefully help someone else out there as well.\u003c/p\u003e\n\u003cp\u003eSo please be kind if you see any mistake, let me know and I\u0026rsquo;ll try to fix it 🙂\u003c/p\u003e\n\u003ch3 id=\"a-motivating-example\"\u003eA motivating example\u003c/h3\u003e\n\u003cp\u003eOne of the fun applications I\u0026rsquo;ve worked on is \u003ca href=\"https://github.com/gvolpe/exchange-rates\"\u003eexchange-rates\u003c/a\u003e, which uses the \u003ca href=\"https://www.fpcomplete.com/blog/2017/07/the-rio-monad\"\u003eRIO Monad\u003c/a\u003e (basically \u003ccode\u003eReaderT\u003c/code\u003e + \u003ccode\u003eIO\u003c/code\u003e).\u003c/p\u003e","tags":["haskell"],"title":"Functional Dependencies \u0026 Type Families","url":"https://gvolpe.com/blog/functional-dependencies-and-type-families/","wordCount":1227},{"comments":true,"content":"\u003cp\u003eHaving introduced Haskell at my last job I wanted to put into practice all the stuff I learned: take the good, leave the bad. So I started working on an \u003ca href=\"https://github.com/gvolpe/exchange-rates\"\u003eexchange rates API\u003c/a\u003e using a few libraries I haven\u0026rsquo;t used before, exclusively for fun and learning purposes.\u003c/p\u003e\n\u003cp\u003eIn this blog post I\u0026rsquo;ll try to share what I have identified as good practice so far and what are my personal\nrecommendations when writing a Haskell application.\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '💡';\"\u003e\n  \u003ch4\u003ePro Tip\u003c/h4\u003e\n  \u003cp\u003e\nIf you write Scala, many of the tips will also be useful for you.\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003ch3 id=\"configuration\"\u003eConfiguration\u003c/h3\u003e\n\u003cp\u003eI tried a couple of libraries where you must write your configuration file using either \u003ccode\u003eyaml\u003c/code\u003e, \u003ccode\u003ehocon\u003c/code\u003e or \u003ccode\u003ejson\u003c/code\u003e but I wasn\u0026rsquo;t satisfied until I\u0026rsquo;ve been recommended to use \u003ca href=\"https://dhall-lang.org/\"\u003edhall-lang\u003c/a\u003e. It\u0026rsquo;s a configuration language guaranteed to terminate that has also Haskell bindings, \u003ca href=\"https://hackage.haskell.org/package/dhall\"\u003enicely packaged\u003c/a\u003e for easy use. It looks as follows:\u003c/p\u003e\n\u003cdiv class=\"highlight dhall-lang\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eEnv\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTest\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e {} \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eProd\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e {} \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e makeConfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e λ(env \u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eEnv\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  { forex \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e makeForexConfig env\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  , redis \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e makeRedisConfig env\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e makeConfig ( \u003cspan style=\"color:#66d9ef\"\u003eEnv\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eTest\u003c/span\u003e {\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e} )\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eYes, it\u0026rsquo;s a language so one can define functions! Neat huh?\u003c/p\u003e\n\u003ch3 id=\"production-ready-effect-rio\"\u003eProduction-ready effect: RIO\u003c/h3\u003e\n\u003cp\u003eEver since I read Michael Snoyman\u0026rsquo;s articles \u003ca href=\"https://www.fpcomplete.com/blog/2017/06/readert-design-pattern\"\u003eThe ReaderT design pattern\u003c/a\u003e and \u003ca href=\"https://www.fpcomplete.com/blog/2017/07/the-rio-monad\"\u003eThe RIO Monad\u003c/a\u003e I\u0026rsquo;ve been convinced that this\nis the nicest and easiest approach to write applications.\u003c/p\u003e\n\u003cp\u003eTwo years later after these articles have been published the package has evolved into what they call \u003ca href=\"https://hackage.haskell.org/package/rio\"\u003eRIO: A standard\nlibrary for Haskell\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eIt doesn\u0026rsquo;t only package \u003ccode\u003eRIO\u003c/code\u003e, its monadic effect, but also its own prelude. And in addition, it re-exports a bunch of other goodies such as error handling and lenses functions.\u003c/p\u003e\n\u003cp\u003eSo right now this is my effect system of choice and it\u0026rsquo;s the one you\u0026rsquo;ll see in the examples below.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eIt\u0026rsquo;s worth mentioning other (higher-order) effects like \u003ca href=\"https://github.com/fused-effects/fused-effects\"\u003efused-effects\u003c/a\u003e and \u003ca href=\"https://github.com/polysemy-research/polysemy\"\u003epolysemy\u003c/a\u003e, and also \u003ca href=\"https://github.com/tweag/capability\"\u003ecapability\u003c/a\u003e. I think we should all keep an eye on them as they look promising and propose a different take on effects.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch3 id=\"polymorphic-record-of-functions\"\u003ePolymorphic record of functions\u003c/h3\u003e\n\u003cp\u003eThis is by far the best way I know of defining polymorphic interfaces.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edata\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e m \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  { cacheNewResult \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eExpiration\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eExchange\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e m ()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  , cachedExchange \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e m (\u003cspan style=\"color:#66d9ef\"\u003eMaybe\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eExchange\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  }\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edata\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCounter\u003c/span\u003e m \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCounter\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  { incrCount \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e m ()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  , getCount \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e m \u003cspan style=\"color:#66d9ef\"\u003eInt\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  , resetCount \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e m ()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  }\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe advantage is that we can create different interpreters using different effect types. For example, the main implementation of \u003ccode\u003eCache m\u003c/code\u003e uses \u003ccode\u003eRedis\u003c/code\u003e whereas the test one uses an in-memory map stored in an \u003ccode\u003eIORef\u003c/code\u003e.\u003c/p\u003e\n\u003ch4 id=\"why-not-typeclasses\"\u003eWhy not typeclasses?\u003c/h4\u003e\n\u003cp\u003eTypeclasses are probably what come first when we think about polymorphic interfaces. However, they need laws / properties that define whether an instance is a valid one or not. Secondly, we can only define a single instance for a specific type. This is called coherence. If we can do that, great! If we fail then we would be better off using a record of functions instead.\u003c/p\u003e\n\u003cp\u003eTo further defend this argument I should, perhaps, write another blog post only on this topic showing examples and expanding on the idea.\u003c/p\u003e\n\u003ch3 id=\"smart-constructors\"\u003eSmart constructors\u003c/h3\u003e\n\u003cp\u003eIn order to create a specific record of functions it is recommended to hide its constructor and instead export a \u003cem\u003esmart constructor\u003c/em\u003e. These are plain functions, normally effectful, responsible for the creation of a single interface. For convention, I chose to prefix them with \u003ccode\u003emk\u003c/code\u003e (for make).\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s how I defined the smart constructor for \u003ccode\u003eCounter\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emkCounter\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eIO\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eCounter\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eIO\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emkCounter\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  (newIORef \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eIO\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eIORef\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eInt\u003c/span\u003e))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u0026amp;\u0026gt;\u003c/span\u003e (\u003cspan style=\"color:#a6e22e\"\u003e\\\u003c/span\u003eref \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCounter\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          { incrCount  \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e void \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e atomicModifyIORef\u0026#39; ref (\u003cspan style=\"color:#a6e22e\"\u003e\\\u003c/span\u003eacc \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e (acc \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e, acc))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          , getCount   \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e readIORef ref\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          , resetCount \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e atomicWriteIORef ref \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        )\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eDon\u0026rsquo;t look so much at the implementation but more at its type signature. This pattern repeats over and over. We\u0026rsquo;ll see soon how other smart constructors are defined.\u003c/p\u003e\n\u003ch3 id=\"parametric-reasoning\"\u003eParametric reasoning\u003c/h3\u003e\n\u003cp\u003e\u003ccode\u003eRIO\u003c/code\u003e is a concrete effect type, basically a newtype around \u003ccode\u003eReaderT\u003c/code\u003e + \u003ccode\u003eIO\u003c/code\u003e, but it\u0026rsquo;s also fully compatible with \u003ccode\u003emtl\u003c/code\u003e\ntypeclasses. This means that we can write functions that use \u003ccode\u003eRIO\u003c/code\u003e directly and others that are 100% polymorphic. So\u0026hellip;\u003c/p\u003e\n\u003ch4 id=\"when-to-be-concrete-vs-when-to-be-polymorphic\"\u003eWhen to be concrete vs When to be polymorphic?\u003c/h4\u003e\n\u003cp\u003eHere\u0026rsquo;s my take: Be polymorphic where you have your main logic and be concrete when there\u0026rsquo;s no logic to test. Let\u0026rsquo;s look at an example to illustrate this statement better.\u003c/p\u003e\n\u003ch4 id=\"to-be-concrete\"\u003eTo be concrete\u003c/h4\u003e\n\u003cp\u003eHere are the smart constructors for \u003ccode\u003eCache m\u003c/code\u003e and \u003ccode\u003eForexClient m\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emkRedisCache\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eHasRedisConfig\u003c/span\u003e env \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eRIO\u003c/span\u003e env (\u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eIO\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emkForexClient\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eHasForexConfig\u003c/span\u003e env \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eRIO\u003c/span\u003e env (\u003cspan style=\"color:#66d9ef\"\u003eForexClient\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eIO\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eFor our main Cache (implemented using \u003ca href=\"https://hackage.haskell.org/package/hedis\"\u003ehedis\u003c/a\u003e) and the Forex Client (http client using \u003ca href=\"https://hackage.haskell.org/package/wreq\"\u003ewreq\u003c/a\u003e) I chose to be concrete, using \u003ccode\u003eRIO\u003c/code\u003e and the \u003ccode\u003eHas\u003c/code\u003e pattern.\u003c/p\u003e\n\u003cp\u003eAlthough this pattern is well described in the articles linked at the top, they don\u0026rsquo;t cover polymorphic records of functions as is the case with \u003ccode\u003eCtx m\u003c/code\u003e. So for a quick reference, here\u0026rsquo;s the \u003ccode\u003eHasCache\u003c/code\u003e typeclass and its instance for our context.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eHasCache\u003c/span\u003e ctx m \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e ctx \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e m \u003cspan style=\"color:#66d9ef\"\u003ewhere\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  cacheL \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eLens\u0026#39;\u003c/span\u003e ctx (\u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e m)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003einstance\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eMonad\u003c/span\u003e m \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eHasCache\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eCtx\u003c/span\u003e m) m \u003cspan style=\"color:#66d9ef\"\u003ewhere\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  cacheL \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lens getCache (\u003cspan style=\"color:#a6e22e\"\u003e\\\u003c/span\u003ex y \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e x { getCache \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e y })\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003eNOTE\u003c/strong\u003e: you need to enable \u003ccode\u003eFlexibleInstances\u003c/code\u003e, \u003ccode\u003eMultiParamTypeClasses\u003c/code\u003e and \u003ccode\u003eFunctionalDependencies\u003c/code\u003e. Thanks to \u003ca href=\"https://www.reddit.com/user/samb961/\"\u003e/u/samb961\u003c/a\u003e for helping me out with this!\u003c/p\u003e\n\u003cp\u003eIn both cases they have no logic I\u0026rsquo;m interested in testing so there\u0026rsquo;s no motivation to abstract over the effect type.\nWhat I want to test is how these components play their role in the main logic as we will see soon.\u003c/p\u003e\n\u003ch4 id=\"to-be-polymorphic\"\u003eTo be polymorphic\u003c/h4\u003e\n\u003cp\u003eThe main logic resides in the implementation of the \u003ccode\u003eCachedForex\u003c/code\u003e service. By following the flow diagram below you should be able to understand it.\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"../../images/api-rates-flow.png\" alt=\"flow-diagram\"\u003e\u003c/p\u003e\n\u003cp\u003eFor the atomic block \u003ccode\u003ebracket\u003c/code\u003e and \u003ccode\u003efinally\u003c/code\u003e from \u003ccode\u003eControl.Monad.Catch\u003c/code\u003e do the trick.\u003c/p\u003e\n\u003cp\u003eNow in order to create the service we need a couple of other components: \u003ccode\u003eLogger m\u003c/code\u003e, \u003ccode\u003eCache m\u003c/code\u003e, \u003ccode\u003eCounter m\u003c/code\u003e and \u003ccode\u003eForexClient m\u003c/code\u003e. We\u0026rsquo;ve seen above the definition of a few of them and for brevity I\u0026rsquo;ll skip others as they are quite similar.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emkExchangeService\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e ( \u003cspan style=\"color:#66d9ef\"\u003eMonadMask\u003c/span\u003e m\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e     , \u003cspan style=\"color:#66d9ef\"\u003eHasLogger\u003c/span\u003e ctx m\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e     , \u003cspan style=\"color:#66d9ef\"\u003eHasCache\u003c/span\u003e ctx m\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e     , \u003cspan style=\"color:#66d9ef\"\u003eHasCounter\u003c/span\u003e ctx m\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e     , \u003cspan style=\"color:#66d9ef\"\u003eHasForexClient\u003c/span\u003e ctx m\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e     , \u003cspan style=\"color:#66d9ef\"\u003eMonadReader\u003c/span\u003e ctx r\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e     )\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e r (\u003cspan style=\"color:#66d9ef\"\u003eExchangeService\u003c/span\u003e m)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eBy making it polymorphic we get to choose our effect type and the dependencies needed while at the same type we apply \u003ca href=\"https://blog.codinghorror.com/the-principle-of-least-power/\"\u003ethe principle of least power\u003c/a\u003e. This gives us \u003cem\u003eparametric reasoning\u003c/em\u003e. And it\u0026rsquo;s perfect since we would like to test our logic without having a Redis instance running nor making real http requests to the external web service.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eOne could argue that even if you make it concrete using \u003ccode\u003eRIO\u003c/code\u003e or \u003ccode\u003eIO\u003c/code\u003e, you can still pass different implementations, but we would be more limited to test using such effect and it\u0026rsquo;ll be harder to reason about our function by just looking at its type signature.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e\u003cstrong\u003eTesting our main service\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eIn this case it ended up being more practical testing using \u003ccode\u003eIO\u003c/code\u003e + \u003ccode\u003eIORef\u003c/code\u003e, given the \u003ccode\u003eMonadMask\u003c/code\u003e constraint on \u003ccode\u003em\u003c/code\u003e. But for demonstration purposes I wrote another version of the test suite using a \u003ca href=\"https://github.com/gvolpe/exchange-rates/blob/master/test/Rates/CachedForexRST.hs\"\u003estack of monad\ntransformers\u003c/a\u003e that doesn\u0026rsquo;t use \u003ccode\u003eIO\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eThe testing approach I followed is to create different interpreters for the dependencies of the main service and then run some property tests on it. So we have an in-memory cache, a dummy Forex client that always returns the same response and a logger that doesn\u0026rsquo;t log.\u003c/p\u003e\n\u003cp\u003eFinally, you need to choose a test library. I went with \u003ca href=\"https://hackage.haskell.org/package/hedgehog\"\u003ehedgehog\u003c/a\u003e. Its documentation is quite nice and it\u0026rsquo;s easy to get started with. Eg: below is the definition of the rates test.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eprop_get_rates\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCache\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eIO\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eProperty\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eprop_get_rates\u003c/span\u003e cache \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e withTests \u003cspan style=\"color:#ae81ff\"\u003e1000\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e property \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  rph     \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e forAll \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eGen\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eint (\u003cspan style=\"color:#66d9ef\"\u003eRange\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elinear \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  count   \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e forAll \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eGen\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eint (\u003cspan style=\"color:#66d9ef\"\u003eRange\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elinear \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e ctx \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCtx\u003c/span\u003e testLogger cache (mkTestCounter count) (mkTestForexClient rph)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  service \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e evalIO \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e runRIO ctx mkExchangeService\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  from    \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e forAll \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eGen\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eelement currencies\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  to      \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e forAll \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eGen\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eelement currencies\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  cached  \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e evalIO \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e cachedExchange cache from to\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  evalIO (try (getRate service from to) \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTryRate\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u0026gt;=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003e\\\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eRight\u003c/span\u003e rs \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e rs \u003cspan style=\"color:#f92672\"\u003e===\u003c/span\u003e fromMaybe (\u003cspan style=\"color:#66d9ef\"\u003eExchange\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1.0\u003c/span\u003e) cached\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eLeft\u003c/span\u003e  \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e assert (count \u003cspan style=\"color:#f92672\"\u003e\u0026gt;=\u003c/span\u003e rph)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIf we get a \u003ccode\u003eRight rs\u003c/code\u003e then we assert that the rate is equals to the one cached or the one returned by the forex\nclient. If we get a \u003ccode\u003eLeft e\u003c/code\u003e instead that means that the API limit has been reached so we assert that the counter is greater or equals than the \u0026ldquo;requests per hour\u0026rdquo; value.\u003c/p\u003e\n\u003ch4 id=\"web-server\"\u003eWeb server\u003c/h4\u003e\n\u003cp\u003eIt wouldn\u0026rsquo;t be fair if I didn\u0026rsquo;t mention the web server. It is written using \u003ca href=\"https://haskell-servant.readthedocs.io/en/stable/#\"\u003eservant\u003c/a\u003e and it\u0026rsquo;s a pleasure to use. It also allows you to generate documentation from the API definition using \u003ca href=\"https://swagger.io/\"\u003eswagger\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eThere are only two endpoints so there\u0026rsquo;s not much to cover but in a nutshell, I decided to organize the code in three\ndifferent modules as shown below.\u003c/p\u003e\n\u003ch4 id=\"endpoints\"\u003eEndpoints\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eRatesAPI\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e       \u003cspan style=\"color:#66d9ef\"\u003eApiVersion\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e:\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;rates\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e:\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eQueryParam\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;from\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e:\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eQueryParam\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;to\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e:\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eGet\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e\u0026#39;[JSON]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eExchangeResponse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003e:\u0026lt;|\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eApiVersion\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e:\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;currencies\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e:\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eGet\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e\u0026#39;[JSON]\u003c/span\u003e [\u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cem\u003eNotice how the API is defined at the type level!\u003c/em\u003e\u003c/p\u003e\n\u003ch4 id=\"response-handler\"\u003eResponse handler\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003erates\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eExchangeService\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eIO\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eMaybe\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eMaybe\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eHandler\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eExchangeResponse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003erates\u003c/span\u003e service (\u003cspan style=\"color:#66d9ef\"\u003eJust\u003c/span\u003e from) (\u003cspan style=\"color:#66d9ef\"\u003eJust\u003c/span\u003e to) \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e handle\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  (\u003cspan style=\"color:#a6e22e\"\u003e\\\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eApiLimitReached\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    throwError \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e err503 { errBody \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Api limit has been reached\u0026#34;\u003c/span\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  )\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  (liftIO \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e exchangeToResponse from to \u003cspan style=\"color:#f92672\"\u003e\u0026lt;$\u0026gt;\u003c/span\u003e getRate service from to)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003erates\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e throwError \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e err400 { errBody \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Invalid currencies\u0026#34;\u003c/span\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"data-and-json-instances\"\u003eData and json instances\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-haskell\" data-lang=\"haskell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edata\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eExchangeResponse\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eExchangeResponse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  { rate \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eFloat\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  , from \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  , to \u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eCurrency\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  } \u003cspan style=\"color:#66d9ef\"\u003ederiving\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eGeneric\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eShow\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003einstance\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eToJSON\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eExchangeResponse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"conclusion\"\u003eConclusion\u003c/h3\u003e\n\u003cp\u003eThese are some of the best practices I know of nowadays. But we all know this is a continuous learning process, it doesn\u0026rsquo;t end here. Anyway, I hope you can get something out of it!\u003c/p\u003e\n\u003ch4 id=\"summarizing\"\u003eSummarizing:\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cem\u003eParametric reasoning matters\u003c/em\u003e: ideally we should be able to tell what a function does by just looking at its type\nsignature.\u003c/li\u003e\n\u003cli\u003eBe concrete when there\u0026rsquo;s no need to complicate things.\u003c/li\u003e\n\u003cli\u003ePolymorphic records of functions are neat.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eHave any thoughts or questions? Share it down in the comments!\u003c/p\u003e\n\u003cp\u003eFind the complete source code \u003ca href=\"https://github.com/gvolpe/exchange-rates\"\u003ehere\u003c/a\u003e.\u003c/p\u003e\n","date":"2019-06-23","dateFormatted":"2019.06.23","excerpt":"Having introduced Haskell at my last job I wanted to put into practice all the stuff I learned: take the good, leave the bad. So I started working on an …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/lessons-learned-while-writing-a-haskell-app/","readingTime":9,"slug":"lessons-learned-while-writing-a-haskell-app","subtitle":null,"summary":"\u003cp\u003eHaving introduced Haskell at my last job I wanted to put into practice all the stuff I learned: take the good, leave the bad. So I started working on an \u003ca href=\"https://github.com/gvolpe/exchange-rates\"\u003eexchange rates API\u003c/a\u003e using a few libraries I haven\u0026rsquo;t used before, exclusively for fun and learning purposes.\u003c/p\u003e\n\u003cp\u003eIn this blog post I\u0026rsquo;ll try to share what I have identified as good practice so far and what are my personal\nrecommendations when writing a Haskell application.\u003c/p\u003e","tags":["haskell"],"title":"Lessons learned while writing a Haskell application","url":"https://gvolpe.com/blog/lessons-learned-while-writing-a-haskell-app/","wordCount":1707},{"comments":true,"content":"\u003cp\u003eIn a recent pull request review at work I suggested using context bound to declare effect capabilities instead of implicit values as this is what I see the most in OSS projects and it has also been my preference for a while. It makes the code look nicer even though the latter approach is equivalent. Context bound constraints get translated into implicits values at compile time.\u003c/p\u003e\n\u003ch4 id=\"context-bound\"\u003eContext bound\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e p1\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eApplicative:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eConsole\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eConsole\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e].\u003c/span\u003eputStrLn\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;a\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eConsole\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e].\u003c/span\u003eputStrLn\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;b\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eConsole\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e].\u003c/span\u003eputStrLn\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;c\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"implicit-values\"\u003eImplicit values\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e p2\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]](\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eimplicit\u003c/span\u003e ev\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eApplicative\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e],\u003c/span\u003e c\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eConsole\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e])\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  c\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eputStrLn\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;a\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    c\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eputStrLn\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;b\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    c\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eputStrLn\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;c\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eEvery time we call \u003ccode\u003eConsole[F]\u003c/code\u003e what we are doing is invoking the \u0026ldquo;summoner\u0026rdquo; method normally defined as follows:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eobject\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eConsole\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e apply\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]](\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eimplicit\u003c/span\u003e ev\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eConsole\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e])\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eConsole\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e ev\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eSo the implicit value gets resolved at compile time only once, but we\u0026rsquo;ve got to ask ourselves:\u003c/p\u003e\n\u003cstyle\u003e\n.quote::before {\n    content: var(--quote-emoji);\n}\n\u003c/style\u003e\n\n\u003cdiv class=\"quote\" style=\"--quote-emoji: '📜';\"\u003e\n  \u003ch4\u003eQuestion\u003c/h4\u003e\n  \u003cp\u003e\n\"Is there any performance penalty introduced by the summoner?\"\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cp\u003eAnd I thought I knew the answer\u0026hellip; But I wasn\u0026rsquo;t very sure so that was a call for examining the JVM bytecode and see the differences!\u003c/p\u003e\n\u003ch3 id=\"jvm-bytecode\"\u003eJVM bytecode\u003c/h3\u003e\n\u003cp\u003eHere\u0026rsquo;s the bytecode generated for both \u003ccode\u003ep1\u003c/code\u003e and \u003ccode\u003ep2\u003c/code\u003e. Let\u0026rsquo;s take a look at the differences.\u003c/p\u003e\n\u003ch4 id=\"context-bound-program\"\u003eContext bound program\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003epublic\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eF \u003cspan style=\"color:#66d9ef\"\u003eextends\u003c/span\u003e java.\u003cspan style=\"color:#a6e22e\"\u003elang\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eObject\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e F \u003cspan style=\"color:#a6e22e\"\u003ep1\u003c/span\u003e(cats.\u003cspan style=\"color:#a6e22e\"\u003eApplicative\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eF\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e, com.\u003cspan style=\"color:#a6e22e\"\u003egithub\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003egvolpe\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eConsole\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eF\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  descriptor: (Lcats\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eApplicative;Lcom\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egithub\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egvolpe\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eConsole;)Ljava\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003elang\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eObject;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  flags: ACC_PUBLIC\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  Code:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    stack\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e4, locals\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e3, args_size\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e3\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e       0: getstatic     \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e56                 \u003cspan style=\"color:#75715e\"\u003e// Field cats/implicits$.MODULE$:Lcats/implicits$;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e       3: getstatic     \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e56                 \u003cspan style=\"color:#75715e\"\u003e// Field cats/implicits$.MODULE$:Lcats/implicits$;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e       6: getstatic     \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e61                 \u003cspan style=\"color:#75715e\"\u003e// Field com/github/gvolpe/Console$.MODULE$:Lcom/github/gvolpe/Console$;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e       9: aload_2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      10: invokevirtual \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e65                 \u003cspan style=\"color:#75715e\"\u003e// Method com/github/gvolpe/Console$.apply:(Lcom/github/gvolpe/Console;)Lcom/github/gvolpe/Console;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      13: ldc           \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e67                 \u003cspan style=\"color:#75715e\"\u003e// String a\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      15: invokeinterface \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e73,  2           \u003cspan style=\"color:#75715e\"\u003e// InterfaceMethod com/github/gvolpe/Console.putStrLn:(Ljava/lang/Object;)Ljava/lang/Object;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      20: aload_1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      21: invokevirtual \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e77                 \u003cspan style=\"color:#75715e\"\u003e// Method cats/implicits$.catsSyntaxApply:(Ljava/lang/Object;Lcats/Apply;)Lcats/Apply$Ops;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      24: getstatic     \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e61                 \u003cspan style=\"color:#75715e\"\u003e// Field com/github/gvolpe/Console$.MODULE$:Lcom/github/gvolpe/Console$;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      27: aload_2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      28: invokevirtual \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e65                 \u003cspan style=\"color:#75715e\"\u003e// Method com/github/gvolpe/Console$.apply:(Lcom/github/gvolpe/Console;)Lcom/github/gvolpe/Console;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      31: ldc           \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e79                 \u003cspan style=\"color:#75715e\"\u003e// String b\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      33: invokeinterface \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e73,  2           \u003cspan style=\"color:#75715e\"\u003e// InterfaceMethod com/github/gvolpe/Console.putStrLn:(Ljava/lang/Object;)Ljava/lang/Object;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      38: invokeinterface \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e82,  2           \u003cspan style=\"color:#75715e\"\u003e// InterfaceMethod cats/Apply$Ops.$times$greater:(Ljava/lang/Object;)Ljava/lang/Object;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      43: aload_1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      44: invokevirtual \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e77                 \u003cspan style=\"color:#75715e\"\u003e// Method cats/implicits$.catsSyntaxApply:(Ljava/lang/Object;Lcats/Apply;)Lcats/Apply$Ops;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      47: getstatic     \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e61                 \u003cspan style=\"color:#75715e\"\u003e// Field com/github/gvolpe/Console$.MODULE$:Lcom/github/gvolpe/Console$;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      50: aload_2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      51: invokevirtual \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e65                 \u003cspan style=\"color:#75715e\"\u003e// Method com/github/gvolpe/Console$.apply:(Lcom/github/gvolpe/Console;)Lcom/github/gvolpe/Console;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      54: ldc           \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e84                 \u003cspan style=\"color:#75715e\"\u003e// String c\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      56: invokeinterface \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e73,  2           \u003cspan style=\"color:#75715e\"\u003e// InterfaceMethod com/github/gvolpe/Console.putStrLn:(Ljava/lang/Object;)Ljava/lang/Object;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      61: invokeinterface \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e82,  2           \u003cspan style=\"color:#75715e\"\u003e// InterfaceMethod cats/Apply$Ops.$times$greater:(Ljava/lang/Object;)Ljava/lang/Object;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      66: areturn\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    LineNumberTable:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      line 10: 0\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      line 11: 24\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      line 10: 43\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      line 12: 47\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    LocalVariableTable:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      Start  Length  Slot  Name   Signature\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          0      67     0  \u003cspan style=\"color:#66d9ef\"\u003ethis\u003c/span\u003e   Lcom\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egithub\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egvolpe\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003esummoner$;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          0      67     1 evidence$1   Lcats\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eApplicative;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          0      67     2 evidence$2   Lcom\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egithub\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egvolpe\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eConsole;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  Signature: \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e49                          \u003cspan style=\"color:#75715e\"\u003e// \u0026lt;F:Ljava/lang/Object;\u0026gt;(Lcats/Applicative\u0026lt;TF;\u0026gt;;Lcom/github/gvolpe/Console\u0026lt;TF;\u0026gt;;)TF;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  MethodParameters:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    Name                           Flags\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    evidence$1                     \u003cspan style=\"color:#66d9ef\"\u003efinal\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    evidence$2                     \u003cspan style=\"color:#66d9ef\"\u003efinal\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"implicit-values-program\"\u003eImplicit values program\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003epublic\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eF \u003cspan style=\"color:#66d9ef\"\u003eextends\u003c/span\u003e java.\u003cspan style=\"color:#a6e22e\"\u003elang\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eObject\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e F \u003cspan style=\"color:#a6e22e\"\u003ep2\u003c/span\u003e(cats.\u003cspan style=\"color:#a6e22e\"\u003eApplicative\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eF\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e, com.\u003cspan style=\"color:#a6e22e\"\u003egithub\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003egvolpe\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eConsole\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eF\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  descriptor: (Lcats\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eApplicative;Lcom\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egithub\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egvolpe\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eConsole;)Ljava\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003elang\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eObject;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  flags: ACC_PUBLIC\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  Code:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    stack\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e4, locals\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e3, args_size\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e3\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e       0: getstatic     \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e56                 \u003cspan style=\"color:#75715e\"\u003e// Field cats/implicits$.MODULE$:Lcats/implicits$;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e       3: getstatic     \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e56                 \u003cspan style=\"color:#75715e\"\u003e// Field cats/implicits$.MODULE$:Lcats/implicits$;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e       6: aload_2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e       7: ldc           \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e90                 \u003cspan style=\"color:#75715e\"\u003e// String 1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e       9: invokeinterface \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e73,  2           \u003cspan style=\"color:#75715e\"\u003e// InterfaceMethod com/github/gvolpe/Console.putStrLn:(Ljava/lang/Object;)Ljava/lang/Object;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      14: aload_1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      15: invokevirtual \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e77                 \u003cspan style=\"color:#75715e\"\u003e// Method cats/implicits$.catsSyntaxApply:(Ljava/lang/Object;Lcats/Apply;)Lcats/Apply$Ops;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      18: aload_2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      19: ldc           \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e92                 \u003cspan style=\"color:#75715e\"\u003e// String 2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      21: invokeinterface \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e73,  2           \u003cspan style=\"color:#75715e\"\u003e// InterfaceMethod com/github/gvolpe/Console.putStrLn:(Ljava/lang/Object;)Ljava/lang/Object;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      26: invokeinterface \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e82,  2           \u003cspan style=\"color:#75715e\"\u003e// InterfaceMethod cats/Apply$Ops.$times$greater:(Ljava/lang/Object;)Ljava/lang/Object;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      31: aload_1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      32: invokevirtual \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e77                 \u003cspan style=\"color:#75715e\"\u003e// Method cats/implicits$.catsSyntaxApply:(Ljava/lang/Object;Lcats/Apply;)Lcats/Apply$Ops;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      35: aload_2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      36: ldc           \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e94                 \u003cspan style=\"color:#75715e\"\u003e// String 3\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      38: invokeinterface \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e73,  2           \u003cspan style=\"color:#75715e\"\u003e// InterfaceMethod com/github/gvolpe/Console.putStrLn:(Ljava/lang/Object;)Ljava/lang/Object;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      43: invokeinterface \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e82,  2           \u003cspan style=\"color:#75715e\"\u003e// InterfaceMethod cats/Apply$Ops.$times$greater:(Ljava/lang/Object;)Ljava/lang/Object;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      48: areturn\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    LineNumberTable:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      line 15: 0\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      line 16: 18\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      line 15: 31\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      line 17: 35\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    LocalVariableTable:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      Start  Length  Slot  Name   Signature\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          0      49     0  \u003cspan style=\"color:#66d9ef\"\u003ethis\u003c/span\u003e   Lcom\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egithub\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egvolpe\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003esummoner$;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          0      49     1 evidence$3   Lcats\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eApplicative;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          0      49     2     c   Lcom\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egithub\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egvolpe\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eConsole;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  Signature: \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e49                          \u003cspan style=\"color:#75715e\"\u003e// \u0026lt;F:Ljava/lang/Object;\u0026gt;(Lcats/Applicative\u0026lt;TF;\u0026gt;;Lcom/github/gvolpe/Console\u0026lt;TF;\u0026gt;;)TF;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  MethodParameters:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    Name                           Flags\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ev                             \u003cspan style=\"color:#66d9ef\"\u003efinal\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    c                              \u003cspan style=\"color:#66d9ef\"\u003efinal\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eSo the bytecode generated for the context bound approach has a few extra calls to \u003ccode\u003egetstatic\u003c/code\u003e and \u003ccode\u003einvokevirtual\u003c/code\u003e but what does this actually mean? Find below the definition given by \u003ca href=\"https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings\"\u003eWikipedia\u003c/a\u003e:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003egetstatic\u003c/code\u003e: get a static field value of a class, where the field is identified by field reference in the constant pool index (indexbyte1 \u0026laquo; 8 + indexbyte2)\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003einvokevirtual\u003c/code\u003e: invoke virtual method on object objectref and puts the result on the stack (might be void); the method is identified by method reference index in constant pool (indexbyte1 \u0026laquo; 8 + indexbyte2)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eSo, is it slower? How can we know? There\u0026rsquo;s only one way\u0026hellip;\u003c/p\u003e\n\u003ch3 id=\"benchmark-it-all\"\u003eBenchmark it all!\u003c/h3\u003e\n\u003cp\u003eWhen not sure about some performance question / issue, benchmark your code. Benchmarking is not easy but fortunately in the JVM we have a fantastic tool: \u003ca href=\"https://openjdk.java.net/projects/code-tools/jmh/\"\u003eJava Microbenchark Harness\u003c/a\u003e or \u003ccode\u003eJMH\u003c/code\u003e for short.\u003c/p\u003e\n\u003cp\u003eHere are the simple benchmarks I wrote, calling each method thousand times via \u003ccode\u003ereplicateA\u003c/code\u003e and measuring the throughput:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e cats.Id\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e cats.implicits._\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e org.openjdk.jmh.annotations._\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ebenchmarks\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003e@Benchmark\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003e@BenchmarkMode\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eArray\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eMode\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eThroughput\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e contextBoundSummoner\u003cspan style=\"color:#f92672\"\u003e()\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e p1\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eId\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e].\u003c/span\u003ereplicateA\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1000\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e).\u003c/span\u003evoid\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003e@Benchmark\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003e@BenchmarkMode\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eArray\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eMode\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eThroughput\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e evidenceSummoner\u003cspan style=\"color:#f92672\"\u003e()\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e p2\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eId\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e].\u003c/span\u003ereplicateA\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1000\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e).\u003c/span\u003evoid\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAnd these are the results, running with 20 iterations, 5 warm-up iterations, 1 fork and 1 thread:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esbt\u0026gt; jmh:run -i \u003cspan style=\"color:#ae81ff\"\u003e20\u003c/span\u003e -wi \u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e -f1 -t1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003einfo\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e Benchmark              Mode  Cnt      Score     Error  Units\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003einfo\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e contextBoundSummoner  thrpt   \u003cspan style=\"color:#ae81ff\"\u003e20\u003c/span\u003e  15777.375 ± 593.111  ops/s\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003einfo\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e evidenceSummoner      thrpt   \u003cspan style=\"color:#ae81ff\"\u003e20\u003c/span\u003e  17302.136 ± 442.127  ops/s\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"conclusion\"\u003eConclusion\u003c/h3\u003e\n\u003cp\u003eThe difference is small enough to not be a performance concern so I would still recommend using the context bounds approach but remember to benchmark and deeply analyze your code before jumping to conclusions!\u003c/p\u003e\n\u003ch3 id=\"update\"\u003eUpdate\u003c/h3\u003e\n\u003cp\u003eAfter publishing it on \u003ca href=\"https://twitter.com/volpegabriel87/status/1105330247086399488\"\u003eTwitter\u003c/a\u003e I\u0026rsquo;ve got good feedback and some suggestions so here\u0026rsquo;s the update.\u003c/p\u003e\n\u003ch4 id=\"macro-based-summoner-imp\"\u003eMacro-based summoner: imp\u003c/h4\u003e\n\u003cp\u003e\u003ca href=\"https://twitter.com/cbirchall\"\u003eChris Birchall\u003c/a\u003e shared this interesting macro-based project named \u003ca href=\"https://github.com/non/imp\"\u003eimp\u003c/a\u003e by \u003ca href=\"https://twitter.com/d6\"\u003eErik Osheim\u003c/a\u003e and I ran the same analysis with it.\u003c/p\u003e\n\u003cp\u003eFirst of all, the summoner was changed accordingly using the \u003ccode\u003esummon\u003c/code\u003e macro.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eobject\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eConsole\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e imp.summon\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e language.experimental.macros\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e apply\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eConsole\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eConsole\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e macro summon\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eConsole\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAnd here are the results of the benchmarks, running 20 iterations like before:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esbt\u0026gt; jmh:run -i \u003cspan style=\"color:#ae81ff\"\u003e20\u003c/span\u003e -wi \u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e -f1 -t1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003einfo\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e Benchmark              Mode  Cnt      Score     Error  Units\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003einfo\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e contextBoundSummoner  thrpt   \u003cspan style=\"color:#ae81ff\"\u003e20\u003c/span\u003e  14881.788 ± 626.458  ops/s\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003einfo\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e evidenceSummoner      thrpt   \u003cspan style=\"color:#ae81ff\"\u003e20\u003c/span\u003e  15039.118 ± 411.016  ops/s\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe macro-based solution was faster as it claims to be. The scores are almost identical and that\u0026rsquo;s because the \u003cem\u003eJVM bytecode generated by both methods are exactly the same!\u003c/em\u003e And effectively running the benchmarks more times gives similar results and sometimes the winner is the classic \u003ccode\u003eevidenceSummoner\u003c/code\u003e. So we can safely claim that both methods \u003ccode\u003ep1\u003c/code\u003e and \u003ccode\u003ep2\u003c/code\u003e are exactly the same for the JVM.\u003c/p\u003e\n\u003cp\u003eFWIW someone else \u003ca href=\"https://github.com/DarkDimius/imp-bench\"\u003ehave run benchmarks\u003c/a\u003e on \u003ccode\u003eimp\u003c/code\u003e before. They\u0026rsquo;re slightly different though.\u003c/p\u003e\n\u003ch4 id=\"inline-final\"\u003e@inline final\u003c/h4\u003e\n\u003cp\u003e\u003ca href=\"https://twitter.com/pkhamutou\"\u003ePavel Khamutou\u003c/a\u003e suggested adding the \u003ccode\u003e@inline\u003c/code\u003e keyword to the summoner and I have also made it \u003ccode\u003efinal\u003c/code\u003e so here\u0026rsquo;s how it looks like:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eobject\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eConsole\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003e@inline\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efinal\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e apply\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]](\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eimplicit\u003c/span\u003e ev\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eConsole\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e])\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eConsole\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e ev\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eUnfortunately the generated JVM bytecode was the same as without trying to inline it so the benchmark results were very similar to the first results.\u003c/p\u003e\n\u003ch4 id=\"-optlinline---opt-inline-from-compiler-flags\"\u003e\u003ccode\u003e-opt:l:inline\u003c/code\u003e \u0026amp; \u003ccode\u003e-opt-inline-from:**\u003c/code\u003e compiler flags\u003c/h4\u003e\n\u003cp\u003e\u003ca href=\"https://twitter.com/kaidaxofficial\"\u003eKaidax\u003c/a\u003e suggested turning on the inliner compiler flags as described in this \u003ca href=\"https://developer.lightbend.com/blog/2018-11-01-the-scala-2.12-2.13-inliner-and-optimizer/index.html\"\u003eLightbend blog post\u003c/a\u003e. At first I didn\u0026rsquo;t see any results but after being pointed out on Reddit by \u003ca href=\"https://www.reddit.com/user/zzyzzyxx\"\u003e/u/zzyzzyxx\u003c/a\u003e that I was doing it wrong (thanks!), I tried once again and the bytecode was effectively changed.\u003c/p\u003e\n\u003cp\u003eThe calls to \u003ccode\u003einvokevirtual\u003c/code\u003e have been removed and a bunch of extra instructions have been added.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003epublic\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eF \u003cspan style=\"color:#66d9ef\"\u003eextends\u003c/span\u003e java.\u003cspan style=\"color:#a6e22e\"\u003elang\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eObject\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e F \u003cspan style=\"color:#a6e22e\"\u003ep1\u003c/span\u003e(cats.\u003cspan style=\"color:#a6e22e\"\u003eApplicative\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eF\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e, com.\u003cspan style=\"color:#a6e22e\"\u003egithub\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003egvolpe\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eConsole\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eF\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  descriptor: (Lcats\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eApplicative;Lcom\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egithub\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egvolpe\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eConsole;)Ljava\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003elang\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eObject;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  flags: ACC_PUBLIC\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  Code:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    stack\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e4, locals\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e3, args_size\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e3\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e       0: getstatic     \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e56                 \u003cspan style=\"color:#75715e\"\u003e// Field cats/implicits$.MODULE$:Lcats/implicits$;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e       3: getstatic     \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e56                 \u003cspan style=\"color:#75715e\"\u003e// Field cats/implicits$.MODULE$:Lcats/implicits$;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e       6: getstatic     \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e61                 \u003cspan style=\"color:#75715e\"\u003e// Field com/github/gvolpe/Console$.MODULE$:Lcom/github/gvolpe/Console$;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e       9: ifnonnull     14\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      12: aconst_null\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      13: athrow\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      14: aload_2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      15: ldc           \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e63                 \u003cspan style=\"color:#75715e\"\u003e// String a\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      17: invokeinterface \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e69,  2           \u003cspan style=\"color:#75715e\"\u003e// InterfaceMethod com/github/gvolpe/Console.putStrLn:(Ljava/lang/Object;)Ljava/lang/Object;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      22: aload_1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      23: invokevirtual \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e73                 \u003cspan style=\"color:#75715e\"\u003e// Method cats/implicits$.catsSyntaxApply:(Ljava/lang/Object;Lcats/Apply;)Lcats/Apply$Ops;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      26: getstatic     \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e61                 \u003cspan style=\"color:#75715e\"\u003e// Field com/github/gvolpe/Console$.MODULE$:Lcom/github/gvolpe/Console$;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      29: ifnonnull     34\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      32: aconst_null\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      33: athrow\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      34: aload_2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      35: ldc           \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e75                 \u003cspan style=\"color:#75715e\"\u003e// String b\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      37: invokeinterface \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e69,  2           \u003cspan style=\"color:#75715e\"\u003e// InterfaceMethod com/github/gvolpe/Console.putStrLn:(Ljava/lang/Object;)Ljava/lang/Object;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      42: invokeinterface \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e78,  2           \u003cspan style=\"color:#75715e\"\u003e// InterfaceMethod cats/Apply$Ops.$times$greater:(Ljava/lang/Object;)Ljava/lang/Object;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      47: aload_1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      48: invokevirtual \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e73                 \u003cspan style=\"color:#75715e\"\u003e// Method cats/implicits$.catsSyntaxApply:(Ljava/lang/Object;Lcats/Apply;)Lcats/Apply$Ops;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      51: getstatic     \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e61                 \u003cspan style=\"color:#75715e\"\u003e// Field com/github/gvolpe/Console$.MODULE$:Lcom/github/gvolpe/Console$;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      54: ifnonnull     59\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      57: aconst_null\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      58: athrow\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      59: aload_2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      60: ldc           \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e80                 \u003cspan style=\"color:#75715e\"\u003e// String c\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      62: invokeinterface \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e69,  2           \u003cspan style=\"color:#75715e\"\u003e// InterfaceMethod com/github/gvolpe/Console.putStrLn:(Ljava/lang/Object;)Ljava/lang/Object;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      67: invokeinterface \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e78,  2           \u003cspan style=\"color:#75715e\"\u003e// InterfaceMethod cats/Apply$Ops.$times$greater:(Ljava/lang/Object;)Ljava/lang/Object;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      72: areturn\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    StackMapTable: number_of_entries \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e 3\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      frame_type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e 255 \u003cspan style=\"color:#75715e\"\u003e/* full_frame */\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        offset_delta \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e 14\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        locals \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecom\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egithub\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egvolpe\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003esummoner$, \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecats\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eApplicative, \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecom\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egithub\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egvolpe\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eConsole \u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        stack \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecats\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eimplicits$, \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecats\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eimplicits$ \u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      frame_type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e 255 \u003cspan style=\"color:#75715e\"\u003e/* full_frame */\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        offset_delta \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e 19\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        locals \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecom\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egithub\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egvolpe\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003esummoner$, \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecats\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eApplicative, \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecom\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egithub\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egvolpe\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eConsole \u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        stack \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecats\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eimplicits$, \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecats\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eApply$Ops \u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      frame_type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e 88 \u003cspan style=\"color:#75715e\"\u003e/* same_locals_1_stack_item */\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        stack \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecats\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eApply$Ops \u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    LineNumberTable:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      line 10: 0\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      line 34: 14\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      line 10: 14\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      line 11: 26\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      line 34: 34\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      line 11: 34\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      line 10: 47\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      line 12: 51\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      line 34: 59\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      line 12: 59\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    LocalVariableTable:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      Start  Length  Slot  Name   Signature\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          0      73     0  \u003cspan style=\"color:#66d9ef\"\u003ethis\u003c/span\u003e   Lcom\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egithub\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egvolpe\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003esummoner$;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          0      73     1 evidence$1   Lcats\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eApplicative;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          0      73     2 evidence$2   Lcom\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egithub\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egvolpe\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eConsole;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  Signature: \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e#\u003c/span\u003e49                          \u003cspan style=\"color:#75715e\"\u003e// \u0026lt;F:Ljava/lang/Object;\u0026gt;(Lcats/Applicative\u0026lt;TF;\u0026gt;;Lcom/github/gvolpe/Console\u0026lt;TF;\u0026gt;;)TF;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  MethodParameters:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    Name                           Flags\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    evidence$1                     \u003cspan style=\"color:#66d9ef\"\u003efinal\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    evidence$2                     \u003cspan style=\"color:#66d9ef\"\u003efinal\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe benchmark results show that it has effectively been optimized:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esbt\u0026gt; jmh:run -i \u003cspan style=\"color:#ae81ff\"\u003e20\u003c/span\u003e -wi \u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e -f1 -t1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003einfo\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e Benchmark             Mode  Cnt      Score     Error  Units\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003einfo\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e contextBoundSummoner  thrpt   \u003cspan style=\"color:#ae81ff\"\u003e20\u003c/span\u003e  16330.873 ± 462.765  ops/s\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003einfo\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e evidenceSummoner      thrpt   \u003cspan style=\"color:#ae81ff\"\u003e20\u003c/span\u003e  15768.175 ± 587.291  ops/s\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"benchmarking-machine\"\u003eBenchmarking machine\u003c/h4\u003e\n\u003cp\u003eThe benchmarks have run on a Ubuntu 18.04 LTS, 16 GB RAM and Intel® Core™ i7-8550U CPU @ 1.80GHz × 8 machine on Java Oracle™ 8:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eJava\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eTM\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e SE Runtime Environment \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ebuild 1.8.0_161-b12\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eJava HotSpot\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eTM\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e 64-Bit Server VM \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ebuild 25.161-b12, mixed mode\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"source-code\"\u003eSource code\u003c/h4\u003e\n\u003cp\u003eTry it out yourself: \u003ca href=\"https://github.com/gvolpe/summoner-benchmarks\"\u003ehttps://github.com/gvolpe/summoner-benchmarks\u003c/a\u003e\u003c/p\u003e\n\u003ch3 id=\"conclusion-2\"\u003eConclusion #2\u003c/h3\u003e\n\u003cp\u003eThe conclusion remains the same. Context bound constraints are my favorite and as demonstrated have very little overhead. Using the macro-based solution is interesting but if you really care about that level of performance maybe the JVM isn\u0026rsquo;t what you\u0026rsquo;re looking for? :)\u003c/p\u003e\n\u003cp\u003e\u003cem\u003e\u003cstrong\u003eThank you all for your amazing feedback!\u003c/strong\u003e\u003c/em\u003e\u003c/p\u003e\n","date":"2019-03-12","dateFormatted":"2019.03.12","excerpt":"In a recent pull request review at work I suggested using context bound to declare effect capabilities instead of implicit values as this is what I see the most …","featured":null,"mood":null,"permalink":"https://gvolpe.com/blog/context-bound-vs-implicit-evidence/","readingTime":8,"slug":"context-bound-vs-implicit-evidence","subtitle":null,"summary":"\u003cp\u003eIn a recent pull request review at work I suggested using context bound to declare effect capabilities instead of implicit values as this is what I see the most in OSS projects and it has also been my preference for a while. It makes the code look nicer even though the latter approach is equivalent. Context bound constraints get translated into implicits values at compile time.\u003c/p\u003e\n\u003ch4 id=\"context-bound\"\u003eContext bound\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e p1\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eApplicative:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eConsole\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eConsole\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e].\u003c/span\u003eputStrLn\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;a\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eConsole\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e].\u003c/span\u003eputStrLn\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;b\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eConsole\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e].\u003c/span\u003eputStrLn\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;c\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"implicit-values\"\u003eImplicit values\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-scala\" data-lang=\"scala\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e p2\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e_\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]](\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eimplicit\u003c/span\u003e ev\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eApplicative\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e],\u003c/span\u003e c\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eConsole\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e])\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eF\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eUnit\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  c\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eputStrLn\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;a\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    c\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eputStrLn\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;b\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    c\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eputStrLn\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;c\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eEvery time we call \u003ccode\u003eConsole[F]\u003c/code\u003e what we are doing is invoking the \u0026ldquo;summoner\u0026rdquo; method normally defined as follows:\u003c/p\u003e","tags":["scala"],"title":"Context bound vs Implicit evidence: Performance","url":"https://gvolpe.com/blog/context-bound-vs-implicit-evidence/","wordCount":1538}]