<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Michael Rätzel</title>
    <description>The latest articles on Forem by Michael Rätzel (@viir).</description>
    <link>https://forem.com/viir</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F18855%2F775419e4-acda-4ad1-8e11-be16d734e9ad.jpeg</url>
      <title>Forem: Michael Rätzel</title>
      <link>https://forem.com/viir</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/viir"/>
    <language>en</language>
    <item>
      <title>Database Functions in Elm-Time - Easy Database Updates in Production</title>
      <dc:creator>Michael Rätzel</dc:creator>
      <pubDate>Tue, 09 May 2023 22:08:32 +0000</pubDate>
      <link>https://forem.com/viir/database-functions-in-elm-time-easy-database-updates-in-production-43l2</link>
      <guid>https://forem.com/viir/database-functions-in-elm-time-easy-database-updates-in-production-43l2</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published at &lt;a href="https://michaelr%C3%A4tzel.com/blog/database-functions-in-elm-time-easy-database-updates-in-production"&gt;https://michaelrätzel.com/blog/database-functions-in-elm-time-easy-database-updates-in-production&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Last week, I added this new feature in Elm-Time, helping you manage and update production databases using custom Elm functions.&lt;/p&gt;

&lt;p&gt;It's called 'Database Functions' and allows you to apply Elm functions on your database.&lt;/p&gt;

&lt;p&gt;You can see it in action in this demo video:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/9mFjdf_ABNM"&gt;https://youtu.be/9mFjdf_ABNM&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem of Updating a Database Online in Production
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://elm-time.org"&gt;Elm-Time&lt;/a&gt; is the runtime for the Elm programming language that radically simplifies the development and operation of web services and full-stack web apps.&lt;/p&gt;

&lt;p&gt;Although running a backend on Elm-Time is generally a smooth experience, I was not satisfied with the options for manually editing the Elm application state.&lt;/p&gt;

&lt;p&gt;For example, I sometimes add items to user accounts as special rewards or delete an account. This means changing the database that is running online in production. The database transactions to make these changes should be quick, as I want to avoid service interruptions. And I also want to avoid a complex preparation: The smaller the project, the more likely I prefer manually entering these changes instead of wiring it up in the application program code.&lt;/p&gt;

&lt;p&gt;What had been our options to update a database online in production?&lt;/p&gt;

&lt;p&gt;In general, the admin interface is the way to make changes without involving the app's main update function. In earlier versions, the options here were the backup/restore APIs or a migration.&lt;/p&gt;

&lt;p&gt;Using the backup and restore APIs for such an update is impractical: If we don't pause the processing of application events, we would effectively erase updates between reading the old state and setting the new state.&lt;/p&gt;

&lt;p&gt;Updates via migrations, on the other hand, guarantee isolation and consistency. &lt;a href="https://michaelr%C3%A4tzel.com/blog/design-report-migrations-in-elm-fullstack-deployments"&gt;As we discovered earlier&lt;/a&gt;, we support applying a migration function on any deployment, not only when the state type changes.&lt;/p&gt;

&lt;p&gt;So yes, we can use a migration function to update the database correctly. But this is unwieldy as it requires running a deployment. The idea for today's solution has been around for some time. And this month, I felt frustrated enough to prioritize it.&lt;/p&gt;

&lt;p&gt;So how does the new feature make this easier?&lt;/p&gt;

&lt;p&gt;It enables us to apply update functions with custom arguments using the admin interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Database Functions in Elm-Time
&lt;/h2&gt;

&lt;p&gt;To make a function available on the admin interface, we place it in an Elm module named &lt;code&gt;Backend.ExposeFunctionsToAdmin&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's look at an example that exposes three function declarations this way. Below is an excerpt from an example application at &lt;a href="https://github.com/elm-time/elm-time/blob/b0062e0c43250b5b49c82fbc4c740ccccffe9bb2/implement/example-apps/database-demo/src/Backend/ExposeFunctionsToAdmin.elm"&gt;https://github.com/elm-time/elm-time/blob/b0062e0c43250b5b49c82fbc4c740ccccffe9bb2/implement/example-apps/database-demo/src/Backend/ExposeFunctionsToAdmin.elm&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elm"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="kt"&gt;Backend&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;ExposeFunctionsToAdmin&lt;/span&gt; &lt;span class="k"&gt;exposing&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Backend&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;State&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Dict&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;UserAccount&lt;/span&gt;


&lt;span class="n"&gt;listNewUserAccounts&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;accountMaximumAgeInDays&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Backend&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;State&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;State&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;List&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;accountId&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;accountAgeInDays&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;listNewUserAccounts&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;accountMaximumAgeInDays&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="n"&gt;deleteUserAccountById&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Backend&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;State&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;State&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Backend&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;State&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;State&lt;/span&gt;
&lt;span class="n"&gt;deleteUserAccountById&lt;/span&gt; &lt;span class="n"&gt;userAccountId&lt;/span&gt; &lt;span class="n"&gt;stateBefore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;stateBefore&lt;/span&gt;
        &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;usersAccounts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stateBefore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usersAccounts&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Dict&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt; &lt;span class="n"&gt;userAccountId&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="n"&gt;userAccountAddCreditBalanceEvent&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;userAccountId&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;addedCredits&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reasonText&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Backend&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;State&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;State&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Backend&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;State&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;State&lt;/span&gt;
&lt;span class="n"&gt;userAccountAddCreditBalanceEvent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;userAccountId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;addedCredits&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reasonText&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;stateBefore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we have placed them into the Elm module with that magic name, Elm-Time makes these functions available via the admin interface. That means whether you use the command-line or graphical interface, the authorization works the same as for deployments.&lt;/p&gt;

&lt;p&gt;In the command-line interface, we can use the new commands &lt;code&gt;list-functions&lt;/code&gt; and &lt;code&gt;apply-function&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the graphical admin interface, we find the exposed functions in the new 'Database Functions' section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QcnGoVZp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ky8bow2o53wsw41f4o3o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QcnGoVZp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ky8bow2o53wsw41f4o3o.png" alt="Applying a database function in the graphical admin interface" width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Update the Database - Apply a Function
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want to follow along, you can use this command to run the same demo app:&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;elm-time  run-server  --admin-password=test  --deploy=https://github.com/elm-time/elm-time/tree/b0062e0c43250b5b49c82fbc4c740ccccffe9bb2/implement/example-apps/database-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;Let's take a closer look and apply the simplest function in our example, &lt;code&gt;deleteUserAccountById&lt;/code&gt;. This function has two parameters, the &lt;code&gt;userAccountId&lt;/code&gt; and the &lt;code&gt;stateBefore&lt;/code&gt; of the database.&lt;/p&gt;

&lt;p&gt;The second parameter bound to &lt;code&gt;stateBefore&lt;/code&gt; is special because its type equals our application state type. The runtime understands this and automatically fills in the current application state as the argument value. That leaves the first parameter, &lt;code&gt;userAccountId&lt;/code&gt;, for us to supply manually.&lt;/p&gt;

&lt;p&gt;As we can see in the type annotation, the return type also equals the app state type. That means applying this function has the potential to change the application state. Sometimes, we want to do a dry run before changing our database's main branch. For that reason, the interface lets us discard the resulting value instead of committing it to the database.&lt;/p&gt;

&lt;p&gt;To make the change go live, we enable the checkbox &lt;em&gt;'Commit resulting state to database'&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the CLI, we set the corresponding flag using the &lt;code&gt;--commit-resulting-state&lt;/code&gt; on the &lt;code&gt;apply-function&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;After using the 'Apply Function' button in the GUI or the &lt;code&gt;apply-function&lt;/code&gt; command in the CLI, we get a report about the results of the function application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In summary, we can now quickly make a manual update to an online database without the need to go through the main update function of the Elm app.&lt;/p&gt;

&lt;p&gt;Since this feature is brand-new, I assume the interfaces will evolve and look different soon. We will see. The functionality I showed here has been implemented and is available since version 2023-05-07. For me, this solution made operating backends much more relaxed.&lt;br&gt;
I am looking forward to hearing your thoughts!&lt;/p&gt;

</description>
      <category>backend</category>
      <category>elm</category>
      <category>elmtime</category>
    </item>
    <item>
      <title>Elm Silent Teacher - An Interactive Way to Learn Elm</title>
      <dc:creator>Michael Rätzel</dc:creator>
      <pubDate>Sun, 23 Apr 2023 20:37:42 +0000</pubDate>
      <link>https://forem.com/viir/elm-silent-teacher-an-interactive-way-to-learn-elm-3b67</link>
      <guid>https://forem.com/viir/elm-silent-teacher-an-interactive-way-to-learn-elm-3b67</guid>
      <description>&lt;p&gt;Originally published at &lt;a href="https://michaelr%C3%A4tzel.com/blog/elm-silent-teacher-an-interactive-way-to-learn-elm"&gt;https://michaelrätzel.com/blog/elm-silent-teacher-an-interactive-way-to-learn-elm&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Let me introduce Elm Silent Teacher, an educational game designed to help you learn the Elm programming language through interactive exercises.&lt;/p&gt;

&lt;p&gt;As I assist beginners in getting started with Elm, I often wonder how we can make learning this programming language easier. This way, I also discovered the 'Silent Teacher' shared by Martin Janiczek at &lt;a href="https://discourse.elm-lang.org/t/silent-teacher-a-game-to-teach-basics-of-programming/1490"&gt;https://discourse.elm-lang.org/t/silent-teacher-a-game-to-teach-basics-of-programming/1490&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These compact, interactive learning experiences are an excellent resource for students, so I expanded on the approach and developed it further.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core loop - learning through bite-sized exercises
&lt;/h2&gt;

&lt;p&gt;In the core interaction loop, the app presents an Elm expression and prompts us to evaluate it.&lt;br&gt;
Of course, evaluating an expression in our brain requires some knowledge of the programming language. So we enter our best guess and initially often get it wrong.&lt;br&gt;
After submitting an answer, the system checks it for correctness. If the answer is incorrect, the Silent Teacher points out the correct answer. Once we are ready for the next challenge, we can continue with a new, similar exercise on the same topic.&lt;br&gt;
And as we get more exercises right, the system shows more advanced challenges. The user gradually learns about programming language elements and functions from the core libraries through many small repetitions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ckuNLh0t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/elm-time/elm-time/1689529e95415ce9adb2915c79bc6c47cf95d72a/guide/image/2023-04-05-elm-silent-teacher-challenge-complete.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ckuNLh0t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/elm-time/elm-time/1689529e95415ce9adb2915c79bc6c47cf95d72a/guide/image/2023-04-05-elm-silent-teacher-challenge-complete.png" alt="Exercise in Elm Silent Teacher" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Elm Silent Teacher - evolved
&lt;/h2&gt;

&lt;p&gt;Compared to the earlier implementation, I adopted a different approach for encoding exercises, simplifying the authoring process. Course authors no longer need to provide a function that computes the correct answer. An interpreter now takes care of this part automatically, running the evaluation on the user's device in the web browser.&lt;/p&gt;

&lt;p&gt;Since the interpreter is readily available, users can also experiment with additional expressions. After submitting their answer, they can access a REPL-like sandbox initialized with the exercise's expression. In this exploration mode, users can modify the expression and observe how the evaluation changes accordingly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To try an example course, visit &lt;a href="https://silent-teacher.netlify.app/"&gt;https://silent-teacher.netlify.app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Elm Silent Teacher is open-source; you can find the code at &lt;a href="https://github.com/elm-time/elm-time/tree/main/implement/elm-time/ElmTime/learn-elm"&gt;https://github.com/elm-time/elm-time/tree/main/implement/elm-time/ElmTime/learn-elm&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;You can modify the exercises and customize the learning path in the &lt;a href="https://github.com/elm-time/elm-time/blob/main/implement/elm-time/ElmTime/learn-elm/src/Frontend/ElmSilentTeacher/Exercise.elm"&gt;&lt;code&gt;Exercise&lt;/code&gt; module&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you took the trip, let me know how it went. I'd love to hear your thoughts.&lt;/p&gt;

</description>
      <category>elm</category>
      <category>learning</category>
      <category>functional</category>
      <category>programming</category>
    </item>
    <item>
      <title>Introducing Elm Editor - a web-based IDE for Elm programs</title>
      <dc:creator>Michael Rätzel</dc:creator>
      <pubDate>Sat, 28 May 2022 17:14:46 +0000</pubDate>
      <link>https://forem.com/viir/introducing-elm-editor-a-web-based-ide-for-elm-programs-3089</link>
      <guid>https://forem.com/viir/introducing-elm-editor-a-web-based-ide-for-elm-programs-3089</guid>
      <description>&lt;p&gt;(Original publication at &lt;a href="https://michaelr%C3%A4tzel.com/blog/introducing-elm-editor-a-web-based-ide-for-elm-programs"&gt;https://michaelrätzel.com/blog/introducing-elm-editor-a-web-based-ide-for-elm-programs&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;About one year ago, I started working on &lt;a href="https://github.com/elm-fullstack/elm-fullstack/tree/main/implement/example-apps/elm-editor"&gt;Elm Editor&lt;/a&gt;, a web app for developing Elm programs.&lt;/p&gt;

&lt;p&gt;As an integrated development environment, it assists us in reading, writing, and testing Elm programs and in collaborating with other developers.&lt;/p&gt;

&lt;p&gt;This project minimizes the friction for newcomers to get started with programming. Since the front-end is entirely web-based, it requires practically no setup. Any modern web browser is enough to use it.&lt;/p&gt;

&lt;p&gt;To see Elm Editor in action, check out the public instance at &lt;a href="https://elm-editor.com"&gt;https://elm-editor.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This video is a short demonstration of a code editing cycle in Elm Editor:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/x6RpeuLtiXY"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Features in 2021
&lt;/h2&gt;

&lt;p&gt;Some of the features come for free with the Monaco Editor. Others required more effort and glue code in javascript to communicate with and synchronize with the code editor. (Learned quite a bit about javascript in this project)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Viewing and editing all Elm module files as well as other text files in your project.&lt;/li&gt;
&lt;li&gt;Checking programs for problems such as Elm compiler errors.&lt;/li&gt;
&lt;li&gt;Saving and sharing the current state of a project, including all files.&lt;/li&gt;
&lt;li&gt;Importing complete projects from public git repositories.&lt;/li&gt;
&lt;li&gt;For front-end web apps, viewing and testing the app in an iframe.&lt;/li&gt;
&lt;li&gt;Visual markers in the code to quickly find locations of problems.&lt;/li&gt;
&lt;li&gt;Showing error descriptions on mouse hover.&lt;/li&gt;
&lt;li&gt;Completion suggestions to discover available declarations and explore useful codes.&lt;/li&gt;
&lt;li&gt;Showing documentation and details when hovering the mouse cursor over a part of the code.&lt;/li&gt;
&lt;li&gt;Command palette to discover new functionality and keyboard shortcuts.&lt;/li&gt;
&lt;li&gt;Text search with options for case sensitivity, regular expressions, and replacing matches.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Besides these features also found in traditional big desktop-based IDEs, there are more innovative features like the &lt;a href="https://github.com/elm-fullstack/elm-fullstack/tree/main/implement/example-apps/elm-editor#project-state-models"&gt;differential project state model&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Organization and Implementation
&lt;/h2&gt;

&lt;p&gt;Elm Editor is an open-source project organized for easy customization and deployment of custom instances. The source code lives at &lt;a href="https://github.com/elm-fullstack/elm-fullstack/tree/main/implement/example-apps/elm-editor"&gt;https://github.com/elm-fullstack/elm-fullstack/tree/main/implement/example-apps/elm-editor&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most of the action happens in the front-end. The primary role of the back-end is integrating tools like the Elm and elm-format executable files and interfacing with Git hosting services like GitHub and GitLab.&lt;/p&gt;

&lt;p&gt;The front-end is mainly written in Elm and integrates the &lt;a href="https://microsoft.github.io/monaco-editor/"&gt;Monaco Editor&lt;/a&gt; from the VS Code project. The Elm app implements ports with the javascript-based Monaco Editor. The Elm side also implements language services that power editor features that require understanding the syntax and semantics of the Elm programming language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outlook
&lt;/h2&gt;

&lt;p&gt;Development continues to make reading and developing Elm programs even more efficient. Integration of a test runner seems one of the more obvious things to add soon. Adding a REPL seems another great addition. A REPL is a more interactive alternative for testing and exploring the behavior of the code in the current workspace.&lt;/p&gt;

&lt;p&gt;Then there are more minor improvements like a preview for Markdown files and viewers for images and audio files (for those working on video games).&lt;/p&gt;

&lt;p&gt;There are tons of ideas for new features; I could probably go on for hours. As with my other open-source projects, prioritization depends in large part on your feedback. Besides the Elm discourse, a good place for discussion and feedback is on GitHub at &lt;a href="https://github.com/elm-fullstack/elm-fullstack/discussions"&gt;https://github.com/elm-fullstack/elm-fullstack/discussions&lt;/a&gt; or in the issues section.&lt;/p&gt;

</description>
      <category>elm</category>
      <category>editor</category>
      <category>ide</category>
      <category>functional</category>
    </item>
    <item>
      <title>Design Report - Migrations in Elm Fullstack Deployments</title>
      <dc:creator>Michael Rätzel</dc:creator>
      <pubDate>Mon, 11 Oct 2021 11:54:27 +0000</pubDate>
      <link>https://forem.com/viir/design-report-migrations-in-elm-fullstack-deployments-79f</link>
      <guid>https://forem.com/viir/design-report-migrations-in-elm-fullstack-deployments-79f</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post was originally published at &lt;a href="https://michaelr%C3%A4tzel.com/blog/design-report-migrations-in-elm-fullstack-deployments"&gt;https://michaelrätzel.com/blog/design-report-migrations-in-elm-fullstack-deployments&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://github.com/elm-fullstack/elm-fullstack"&gt;Elm Fullstack&lt;/a&gt; is a tool for developing web services and full-stack web applications. It leverages the Elm programming language and its ecosystem of libraries. As it evolves, Elm Fullstack automates more and more activities in software development that humans had performed in the past.&lt;/p&gt;

&lt;p&gt;Some of the tasks it automates are related to maintaining an application's program state. This 'state' contains all information a web service should remember. It is the 'database' of the application. Maintaining this program state is one of the features of Elm Fullstack that shields app developers from the complexity of lower-level concepts in software development.&lt;/p&gt;

&lt;p&gt;One common machine-level concept is the distinction between volatile and persistent memory. Many frameworks for software development reflect this distinction, delegating responsibility to handle this complexity to the app developer. The first feature I implemented in Elm Fullstack resolves this distinction so that we don't need to consider it when programming apps.&lt;br&gt;
After persistence, I took on Migrations - another aspect of state management. This post overviews exploration of this area and the design I discovered for migrations in deployments.&lt;/p&gt;
&lt;h2&gt;
  
  
  Migrations
&lt;/h2&gt;

&lt;p&gt;What are migrations, and why do we need them?&lt;/p&gt;

&lt;p&gt;Successful software applications evolve. This evolution manifests in changes to our code and our data. After working on and testing functionality in a development environment, we eventually make our changes available to users and customers. To do this, we update a production system with the new program code. This process is what we call a 'deployment'.&lt;/p&gt;

&lt;p&gt;In many cases, we can deploy an Elm app simply by pushing the code to a production system. But sometimes, a change affects the state of our backend or how we represent it in the code. Changing the type of the persistent state presents a new challenge. Since we maintain the backend state across deployments, there is a type mismatch between our app's old and new versions. To resolve the type mismatch, we need to transform that data, map it from the old to the new type.&lt;br&gt;
Elm Fullstack manages these changes through a mechanism called migration.&lt;/p&gt;

&lt;p&gt;The example of a type mismatch illustrates why we need migrations. If considering only type-mismatches, I might have picked an API that expects migrations only when changing the state type. &lt;br&gt;
But, looking closer at app development, I found more uses for migrations.&lt;/p&gt;

&lt;p&gt;The meaning of the state value not only depends on type declarations but also on functions. Functions in the program code define how our service responds to clients and how these responses depend on the current state of the service. When we deploy a new program version, these dependencies of responses on the state can change, effectively changing the meaning of the service state, even if its type declaration stays the same.&lt;br&gt;
Sometimes when testing during development, I change the app state or edit the database directly. The migration functionality would help here, packaging such an edit in an atomic transaction.&lt;br&gt;
To support these scenarios, I went with a design that, by default, requires us to declare a migration function for every deployment explicitly.&lt;/p&gt;
&lt;h2&gt;
  
  
  Application Programming Interface
&lt;/h2&gt;

&lt;p&gt;How do migrations work from the app developers' perspective?&lt;/p&gt;

&lt;p&gt;Migrations happen as part of deployments, so we do not trigger them explicitly. By default, every deployment implies a migration. But sometimes, we prefer to throw away the current backend state instead of coding a migration. For these cases, the API offers an option to take the app state from an &lt;code&gt;init&lt;/code&gt; function instead of looking for a migration. In the command-line interface, we see this option surface on the &lt;code&gt;deploy&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;When deploying a new version of our app, we describe how to map values from the current to the new state type. Elm Fullstack then does the type-checking across these versions and rejects the deployment in case of a mismatch. If we provided a matching migration function with our deployment, Elm Fullstack performs the migration in an atomic transaction.&lt;/p&gt;

&lt;p&gt;Ok, now we know the admin interface will yell at us if we don't supply a migration with the next deployment. But how do we declare the migration?&lt;/p&gt;

&lt;p&gt;The Elm programming language already provides the elements to code a migration. A function with one argument is enough to describe the transformation. When applying the migration, the type of that argument needs to match the backend state type found in the currently running version. We need to maintain the declaration of that old type somewhere in the new program code so that standard Elm tooling continues to work. Compared to type-checking in Elm, the rules for custom types are more relaxed for migrations. As long as all tags are the same, we can substitute one custom type with another. In other words, type-checking in migrations does not consider the module names of custom types. This way, we can conveniently place all custom types we keep around for migrations under a common prefix.&lt;/p&gt;

&lt;p&gt;We code the migration as an Elm function that takes the old state as input and returns the new state with the new backend state type. We place it into the deployed code in an Elm module with a particular name to declare the migration.&lt;/p&gt;

&lt;p&gt;For optimal consistency and familiarity, the type of the migrate function is the same as in other update functions used on the backend, minus the event-specific first argument. We might see apps that should issue commands on migration, so the return type supports that, analogous to the update functions for application events. The difference to update functions for application events is that the state type on the input side can differ from the output side.&lt;/p&gt;

&lt;p&gt;To summarize, here is the Elm syntax with the type annotation of a migration function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elm"&gt;&lt;code&gt;&lt;span class="n"&gt;migrate&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;OldBackendTypes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;State&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;Backend&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;State&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;ElmFullstack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;BackendCmds&lt;/span&gt; &lt;span class="kt"&gt;Backend&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;State&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;What is the state of migrations now?&lt;/p&gt;

&lt;p&gt;Migrations are implemented and available with the latest releases.&lt;br&gt;
To declare the migration function, we place it in the module &lt;code&gt;Backend.MigrateState&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elm"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="kt"&gt;Backend&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;MigrateState&lt;/span&gt; &lt;span class="k"&gt;exposing&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;migrate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Backend&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Main&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;ElmFullstack&lt;/span&gt;


&lt;span class="n"&gt;migrate&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Backend&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;State&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;Backend&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;State&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;ElmFullstack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;BackendCmds&lt;/span&gt; &lt;span class="kt"&gt;Backend&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;State&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;migrate&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we see the trivial case where we don't need any transformation.&lt;/p&gt;

&lt;p&gt;While introducing type-checked migrations was significant progress, we can still improve the developer experience around migrations.&lt;/p&gt;

&lt;p&gt;An apparent future step is to add automation for coding the migration function. Extracting the backend type declarations from a previous version into a dedicated Elm module is the least we can do here.&lt;/p&gt;

</description>
      <category>elm</category>
      <category>engineering</category>
      <category>backend</category>
      <category>fullstack</category>
    </item>
    <item>
      <title>BMP Image File Decoding In Elm</title>
      <dc:creator>Michael Rätzel</dc:creator>
      <pubDate>Sun, 22 Sep 2019 13:37:03 +0000</pubDate>
      <link>https://forem.com/viir/bmp-image-file-decoding-in-elm-2859</link>
      <guid>https://forem.com/viir/bmp-image-file-decoding-in-elm-2859</guid>
      <description>&lt;p&gt;Recently I implemented a BMP image decoder in Elm. This Elm function takes an image as byte-array (&lt;a href="https://github.com/elm/bytes"&gt;&lt;code&gt;Bytes.Bytes&lt;/code&gt;&lt;/a&gt;) and returns the values of the pixels in this image.&lt;/p&gt;

&lt;p&gt;For a current project, I was looking for a way to decode 2-dimensional raster images in Elm to get values of individual pixels out of an image. I googled and searched the Elm forum for existing solutions but did not come up with anything. So I built a BMP image file decoder from scratch.&lt;/p&gt;

&lt;p&gt;So what is the scenario exactly? The user loads an RGB image file containing a screenshot into our program. The program then needs to read the intensities of the individual pixels' red, green, and blue components for further processing. What's more, the user also should be able to load the same image file into popular raster graphics editors like MS Paint. Following this constraint, I could not use the simplest possible image file format. Instead, I needed to support one of the popular file formats like PNG, BMP, or JPEG.&lt;/p&gt;

&lt;p&gt;Looking a bit closer at those image file formats, it seemed that BMP would be the simplest one to implement a decoder for. I found the Wikipedia &lt;a href="https://en.wikipedia.org/wiki/BMP_file_format"&gt;article on the BMP file format&lt;/a&gt; explains it well. In that article, we can also see that BMP supports diverse kinds of pixel formats. But for our application, we only need to support the 24-bit pixel (24bpp) format.&lt;/p&gt;

&lt;p&gt;Even with this relatively limited set of supported formats, I expected plenty of opportunities to introduce bugs while writing a decoder implementation. So it was clear from the beginning that I would need to run some tests on the decoder to trust it works correctly. I began with coding tests for some image files. These tests are written in Elm, so we can run them with the elm-test tool to see if the decoding function works correctly. The tests model the flat file on one side and the pixels values on the other side. The reference image files are encoded as base64 strings to get them into the Elm source code. The testing module is published at &lt;a href="https://github.com/Viir/bots/blob/82ef37f3089a3e40bd496f507b691a5c25011b33/implement/devtools/read-from-image/tests/DecodeBMPImageTest.elm"&gt;https://github.com/Viir/bots/blob/82ef37f3089a3e40bd496f507b691a5c25011b33/implement/devtools/read-from-image/tests/DecodeBMPImageTest.elm&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To figure out the decoding, I found the Wikipedia article easy enough to follow. One thing which took me by surprise was the behavior of &lt;code&gt;Bytes.Decode.decode&lt;/code&gt; in the Elm libraries. This function can return different results for the same inputs since it &lt;a href="https://github.com/elm/bytes/issues/9"&gt;suppresses stack overflows&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The final image decoding function to get the RGB pixel values out of an image file is published at &lt;a href="https://github.com/Viir/bots/blob/82ef37f3089a3e40bd496f507b691a5c25011b33/implement/devtools/read-from-image/src/DecodeBMPImage.elm"&gt;https://github.com/Viir/bots/blob/82ef37f3089a3e40bd496f507b691a5c25011b33/implement/devtools/read-from-image/src/DecodeBMPImage.elm&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(This post was originally published at &lt;a href="https://michaelr%C3%A4tzel.com/blog/bmp-image-file-decoding-in-elm"&gt;https://michaelrätzel.com/blog/bmp-image-file-decoding-in-elm&lt;/a&gt;)&lt;/p&gt;

</description>
      <category>engineering</category>
      <category>elm</category>
      <category>imageprocessing</category>
      <category>decoding</category>
    </item>
    <item>
      <title>Persistence in Kalmit - April 2019 Edition</title>
      <dc:creator>Michael Rätzel</dc:creator>
      <pubDate>Sun, 28 Apr 2019 14:42:36 +0000</pubDate>
      <link>https://forem.com/viir/persistence-in-kalmit-april-2019-edition-3bgm</link>
      <guid>https://forem.com/viir/persistence-in-kalmit-april-2019-edition-3bgm</guid>
      <description>&lt;p&gt;This post was originally published at &lt;a href="https://michaelr%C3%A4tzel.com/blog/persistence-in-kalmit---april-2019-edition"&gt;https://michaelrätzel.com/blog/persistence-in-kalmit---april-2019-edition&lt;/a&gt;&lt;br&gt;
It reflects the state of &lt;a href="https://github.com/Viir/Kalmit"&gt;Kalmit&lt;/a&gt; in April 2019. As the project is young, I would not be surprised to find substantial changes later.&lt;/p&gt;

&lt;p&gt;This guide assumes you already know how state changes work in &lt;a href="https://elm-lang.org"&gt;Elm&lt;/a&gt; applications. To learn about the role of the &lt;code&gt;update&lt;/code&gt; function as used here, have a look at &lt;a href="https://medium.com/@l.mugnaini/the-elm-architecture-tea-animation-3efc555e8faf"&gt;The Elm Architecture (TEA) animation&lt;/a&gt; by lucamug. Note that his illustration contains 'view' and 'DOM' parts which are specific to client-side applications and not used here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Persistence for development and operation
&lt;/h2&gt;

&lt;p&gt;Immediate automatic persistence is one of the main features of the Kalmit web host. Every &lt;a href="http://toreto.re/tea/"&gt;&lt;code&gt;update&lt;/code&gt;&lt;/a&gt; of the application state is immediately persisted.&lt;/p&gt;

&lt;p&gt;What does 'immediate automatic persistence' mean exactly? Let's break this down and define these terms.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why &lt;em&gt;persistence&lt;/em&gt;? After the server application has confirmed or reported a transaction to an external party, it should remember the resulting state. Kalmit records every change of the application state, to support consistent continuation even after sudden shutdown of the hosting machine. Since server applications run for months and years, such an interruption is bound to happen sooner or later. When the system starts up again, the application state is restored automatically.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Immediately&lt;/em&gt; means before other effects from an update become observable. The commands emitted by the update function are only applied after the state from the same update is saved. For example, when a client receives an HTTP response from your server, you know the new state of the server is already persisted.&lt;/li&gt;
&lt;li&gt;I use the attribute &lt;em&gt;automatic&lt;/em&gt; to clarify that the implementation of this persistence requires no attention by the app developer, as the Kalmit web host takes care of it. Since the app state itself is persistent, considering a database in the application code is not necessary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To learn about configuration and operation, see the guide on &lt;a href="https://github.com/Viir/Kalmit/blob/7563bdba082345df1beda6f67587bf8b171b598c/guide/how-to-configure-and-deploy-your-kalmit-web-app.md"&gt;How to Configure and Deploy Your Kalmit Web App&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Under the hood - how it works
&lt;/h2&gt;

&lt;p&gt;After clarifying what guarantees the runtime offers to support development and operation, the remainder of this guide illustrates what happens under the hood to make this possible and performant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recovering After Unexpected Shutdown
&lt;/h3&gt;

&lt;p&gt;During operation of the process, the system records the sequence of events (sometimes called 'messages' in Elm lingo) entering the process. Events are appended to a composition log, contained in composition records. Each composition record also contains the SHA256 hash of the previous composition record. These references from one record to another form a chain enabling complete traceability. A reader can determine the original order of the processed events using the hash references, even if the records have been shuffled around in the meantime.&lt;/p&gt;

&lt;p&gt;On startup, the Kalmit web host uses the composition log to restore the process state. It reads the sequence of events and replays the updates to compose the process state again.&lt;/p&gt;

&lt;p&gt;For an automated test covering the restoration of the process state, see the &lt;a href="https://github.com/Viir/Kalmit/blob/7563bdba082345df1beda6f67587bf8b171b598c/implementation/PersistentProcess/PersistentProcess.Test/TestPersistentProcess.cs#L112-L154"&gt;method &lt;code&gt;Restore_process_state_over_compositions&lt;/code&gt; in the implementation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optimizing for Faster Restore
&lt;/h3&gt;

&lt;p&gt;For real-world applications, it is not practical to depend on replaying the whole history every time we want to restore a process state. Even more so since restore is not only used for disaster recovery but also for experimentation and performance profiling.&lt;br&gt;
Besides the time it takes for replaying the composition, a growing composition chain takes up more and more storage space.&lt;/p&gt;

&lt;p&gt;To solve both the restore time and storage space problems, Kalmit also stores reductions of the composition chain at regular intervals. The reduced value equals the process state derived from the reduced composition chain. When performing a restore, the composition chain is not anymore read back to the beginning of history, but only up to the last written and available reduction record. Since the earlier part of the composition log is not needed anymore for restoration, it can be truncated, freeing up storage space.&lt;/p&gt;

&lt;p&gt;The automated test &lt;a href="https://github.com/Viir/Kalmit/blob/7563bdba082345df1beda6f67587bf8b171b598c/implementation/PersistentProcess/PersistentProcess.Test/TestPersistentProcess.cs#L156-L218"&gt;&lt;code&gt;Restore_process_state_from_combination_of_reduction_and_compositions&lt;/code&gt; in the implementation&lt;/a&gt; demonstrates the recovery of process states from truncated composition logs, with the help of reductions.&lt;/p&gt;

</description>
      <category>elm</category>
      <category>engineering</category>
      <category>backend</category>
      <category>elmfullstack</category>
    </item>
  </channel>
</rss>
