<?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: agj</title>
    <description>The latest articles on Forem by agj (@agj).</description>
    <link>https://forem.com/agj</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%2F1133727%2Fdd376282-9722-4c7c-8906-ad616a4db86a.png</url>
      <title>Forem: agj</title>
      <link>https://forem.com/agj</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/agj"/>
    <language>en</language>
    <item>
      <title>elm-simple-icons: Another Elm package</title>
      <dc:creator>agj</dc:creator>
      <pubDate>Tue, 14 Apr 2026 21:40:16 +0000</pubDate>
      <link>https://forem.com/agj/elm-simple-icons-another-elm-package-23pk</link>
      <guid>https://forem.com/agj/elm-simple-icons-another-elm-package-23pk</guid>
      <description>&lt;p&gt;I released a new Elm package: &lt;a href="https://package.elm-lang.org/packages/agj/elm-simple-icons/latest/" rel="noopener noreferrer"&gt;agj/&lt;strong&gt;elm-simple-icons&lt;/strong&gt;&lt;/a&gt;, an Elm conversion of a project called &lt;a href="https://simpleicons.org/" rel="noopener noreferrer"&gt;Simple Icons&lt;/a&gt;. It's a collection of icon versions of many logos for brands and projects. This is a practical tool to have lying around to, say, put a little Wikipedia icon next to a link pointing to a Wikipedia article, or otherwise visually annotate content related to external services. I sent a PR to the Simple Icons project to add my package to their list of “third party extensions,” and it's already listed &lt;a href="https://simpleicons.org/?modal=extensions" rel="noopener noreferrer"&gt;on their website&lt;/a&gt; next to many other similar packages.&lt;/p&gt;

&lt;p&gt;Frankly, the project lies in a bit of a legal gray area, since these logos are their respective owners' copyright and whatnot. The Simple Icons project tries to deflect this problem by offering a &lt;a href="https://github.com/simple-icons/simple-icons/blob/develop/DISCLAIMER.md" rel="noopener noreferrer"&gt;legal disclaimer&lt;/a&gt;. Some brands are not included precisely because their owners have opted out. So it has a bit of that “piracy vibe” to it, although this being just branding material it's nothing very serious. This is only use of visual material that these brands have chosen to be their public face. Now, on my side I've also linked to their disclaimer, and I made sure to put license and branding information in the documentation for each icon in the package, for those cases in which it's available (admittedly not many).&lt;/p&gt;

&lt;p&gt;My package, being a repackaging of the Simple Icons SVGs, is automatically generated from that source data. I briefly considered using &lt;a href="https://github.com/mdgriffith/elm-codegen" rel="noopener noreferrer"&gt;elm-codegen&lt;/a&gt; for that task, but I concluded that while it would be a great solution for generating the Elm code itself, it would be less ideal for the data munging part of it—I'd probably need to pass data in and out of Elm more than I would like. Also, the setup complexity cost was higher.&lt;/p&gt;

&lt;p&gt;So I used Nushell for the code generation. It was actually really easy; &lt;a href="https://github.com/agj/elm-simple-icons/blob/be5f40082f9a13398593d103b9ab0f23cbccfb3c/scripts/build.nu" rel="noopener noreferrer"&gt;the script&lt;/a&gt; is under 300 lines. It just reads the SVG data and converts it into Elm syntax, which is made simple thanks to Nushell's support for parsing XML data. Another big part of the script is generating the documentation, which is also simple since the source package includes a big JSON metadata file; I just need to surface that information.&lt;/p&gt;

&lt;p&gt;For the user-facing API I researched Elm icon libraries, and ended up loosely basing mine on the &lt;a href="https://package.elm-lang.org/packages/phosphor-icons/phosphor-elm/latest/" rel="noopener noreferrer"&gt;Phosphor icons package&lt;/a&gt; solution. My package is not a traditional icons library, as each icon has its own brand color associated to it, and they also have a &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; element with the brand name, which shows up as a mouse hover “tooltip” on browsers. So those two things, plus the icon size, were what I allow to configure via a &lt;a href="https://sporto.github.io/elm-patterns/basic/builder-pattern.html" rel="noopener noreferrer"&gt;“builder pattern”&lt;/a&gt; interface of &lt;code&gt;with*&lt;/code&gt; functions, ending in a &lt;code&gt;toHtml&lt;/code&gt; function that takes an optional list of HTML attributes. So far, so good.&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="kt"&gt;SimpleIcons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;elm&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;SimpleIcons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withColor&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#FF00FF"&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;SimpleIcons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withSize&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;50px"&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;SimpleIcons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withNoTitle&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;SimpleIcons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toHtml&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="kt"&gt;Html&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Attributes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;icon"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's still a work in progress, but I'm also coding up a Nushell script to make it easier to keep my package up-to-date with the source package. It uses &lt;a href="https://github.com/raineorshine/npm-check-updates" rel="noopener noreferrer"&gt;npm-check-updates&lt;/a&gt; to update the version in &lt;code&gt;package.json&lt;/code&gt;, regenerates stuff, then tries to update version numbers all around. I'm trying to get it to also update the changelog with added and removed icon names. Might I even put this all in a Github Action, to fully automate the process? Not likely, since I don't trust the automation enough to let it publish a package on my behalf. Maybe to generate a PR, but eh, most likely not worth the trouble.&lt;/p&gt;

&lt;p&gt;I also put good effort into making sure that the code and the documentation are solid. For one, I added tests for each configuration function that run on each icon. I didn't go full property-based testing &lt;a href="https://blog.agj.cl/2025/08/on-my-elm-knobs-elm-package" rel="noopener noreferrer"&gt;like I did on elm-knobs&lt;/a&gt;, since I'm only basically just checking that each function did modify the SVG in the place it's supposed to.&lt;/p&gt;

&lt;p&gt;I also used a great elm-review rule: &lt;a href="https://package.elm-lang.org/packages/lue-bird/elm-review-documentation-code-snippet/latest/" rel="noopener noreferrer"&gt;lue-bird/elm-review-documentation-code-snippet&lt;/a&gt;. What I use it for is a basic sanity check of all code snippets in the docs. If you take a look at &lt;a href="https://package.elm-lang.org/packages/agj/elm-simple-icons/1.1.0/SimpleIcons" rel="noopener noreferrer"&gt;my package docs&lt;/a&gt;, you'll see that all example code ends with a comment like: &lt;code&gt;--: SimpleIcons.Icon&lt;/code&gt;. This instructs the elm-review rule to check that the preceding bit of code resolves to the stated type. And while checking the type is cool, I'm already happy enough that it will attempt to compile the code at all, as that would be the first likely problem to occur if the API changed and the examples were to get outdated.&lt;/p&gt;

&lt;p&gt;One little trick I learned from Dillon Kearn's &lt;a href="https://github.com/dillonkearns/idiomatic-elm-package-guide" rel="noopener noreferrer"&gt;Idiomatic Elm Package Guide&lt;/a&gt;, and which I put into practice in this package, is that I can use &lt;code&gt;elm make --docs=file.json&lt;/code&gt; to confirm that the package documentation satisfies the Elm package repository requirements early. What it does is attempt to generate a JSON file that contains all the package documentation, but it will fail if there's missing docs for exported members, if something's incorrectly formatted, etc. So that line is one of the commands that my &lt;code&gt;check&lt;/code&gt; task runs, in addition to attempting compilation, running tests, and running elm-review.&lt;/p&gt;

&lt;p&gt;This was the second Elm package I release. I think I'm getting the hang of it. Hopefully it'll be helpful to someone other than me, eventually!&lt;/p&gt;

</description>
      <category>elm</category>
      <category>functional</category>
      <category>library</category>
      <category>frontend</category>
    </item>
    <item>
      <title>dot-into, pipes in TypeScript</title>
      <dc:creator>agj</dc:creator>
      <pubDate>Sun, 05 Apr 2026 15:54:46 +0000</pubDate>
      <link>https://forem.com/agj/dot-into-pipes-in-typescript-1cn</link>
      <guid>https://forem.com/agj/dot-into-pipes-in-typescript-1cn</guid>
      <description>&lt;p&gt;So I have a little library for JavaScript and TypeScript called &lt;a href="https://github.com/agj/dot-into" rel="noopener noreferrer"&gt;dot-into&lt;/a&gt;, which is something I actually every so often use in personal projects I've written using said languages. &lt;a href="https://blog.agj.cl/tag/?t=dot-into" rel="noopener noreferrer"&gt;I've posted about it before&lt;/a&gt; back when I launched it, and I've updated it a few times. Here's the short of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This:&lt;/span&gt;
&lt;span class="nf"&gt;third&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;second&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="nx"&gt;moreData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Becomes this:&lt;/span&gt;
&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;into&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;second&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;into&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;third&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;moreData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I just released &lt;a href="https://github.com/agj/dot-into/tree/v3.0.0" rel="noopener noreferrer"&gt;version 3.0.0&lt;/a&gt;, which breaks ES5 compatibility but fixes some outstanding type inference problems that have existed since I introduced TypeScript support. Basically, it should now work with functions that use generic types. However, it's still broken for functions with multiple type signatures, sadly. But it's an easy fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This breaks inference:&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;into&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;multiSignatureFn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// This doesn't:&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;into&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;multiSignatureFn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I guess I wanted to post about it again because the biggest problem that it had, in my eyes, was that any &lt;code&gt;null&lt;/code&gt; or &lt;code&gt;undefined&lt;/code&gt; value in the middle of a pipe would cause a runtime exception. This is because the library extends the &lt;code&gt;Object&lt;/code&gt; prototype, which those two values don't inherit from, and so the execution throws due to a non-existing object member. But more recently (in this decade or so since it was originally released) there's been two developments that mitigate this issue and make the library more useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There's new JavaScript syntax, the &lt;code&gt;?.&lt;/code&gt; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining" rel="noopener noreferrer"&gt;optional chaining operator&lt;/a&gt;, which precisely short-circuits the chain of invocations and returns &lt;code&gt;undefined&lt;/code&gt; instead of throwing.&lt;/li&gt;
&lt;li&gt;The rise of TypeScript means that (&lt;a href="https://www.typescriptlang.org/tsconfig/#strict" rel="noopener noreferrer"&gt;if set up correctly&lt;/a&gt;) we can opt into getting compilation errors when trying to access a member of a possibly nullish value.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I honestly do not love working with JS or even TS, but when I do, I like having something akin to a &lt;code&gt;|&amp;gt;&lt;/code&gt; pipe operator available, common in functional programming such as the ML family of languages and Elixir. I'd much rather use a library of nice, functional utility functions that can operate on various forms of plain data (such as the TS-native &lt;a href="https://remedajs.com/" rel="noopener noreferrer"&gt;Remeda&lt;/a&gt;) than dealing with objects with state and members and so on.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>functional</category>
      <category>library</category>
    </item>
    <item>
      <title>Traduje “la guía” de Elm al español</title>
      <dc:creator>agj</dc:creator>
      <pubDate>Sun, 15 Feb 2026 00:52:57 +0000</pubDate>
      <link>https://forem.com/agj/traduje-la-guia-de-elm-al-espanol-1mkj</link>
      <guid>https://forem.com/agj/traduje-la-guia-de-elm-al-espanol-1mkj</guid>
      <description>&lt;p&gt;Escribí una &lt;a href="https://agj.github.io/elm-guide-es/" rel="noopener noreferrer"&gt;&lt;strong&gt;traducción al español de “Introducción a Elm”&lt;/strong&gt;&lt;/a&gt;. Es un libro digital escrito por el mismo autor del lenguaje de programación Elm, Evan Czaplicki, como una forma accesible de aprender a programar usando el lenguaje. No sólo demuestra y explica el lenguaje mismo, sino que también presenta perspectivas y técnicas que yo considero muy valiosas, en un formato extremadamente accesible. Es una excelente manera de aprender programación funcional. Elm es un lenguaje y una comunidad de donde yo he aprendido muchísimo, y creo que el libro condensa una gran parte de ese valor. Con algo de suerte, esta traducción contribuirá un poquito a popularizar Elm y estas ideas dentro de los círculos hispanohablantes de programación.&lt;/p&gt;

&lt;p&gt;Viendo el historial de Git, este proyecto de traducción lo empecé en agosto de 2024, o sea más o menos hace un año y medio. Obviamente no estuve constantemente trabajando en ello, sino que ocasionalmente me metía a trabajar en unas cuantas páginas, luego me ocupaba en otras cosas.&lt;/p&gt;

&lt;p&gt;Una razón por la que quise traducir este libro es porque tenía ganas de practicar hacer una traducción técnica. Durante la última década y tanto he ocasionalmente hecho traducciones en forma remunerada, y he ido aprendiendo por mi cuenta sobre la disciplina. No me considero realmente un profesional, aunque sí es algo que me gusta hacer ocasionalmente, y no me considero malo haciéndolo. Definitivamente me falta teoría, eso sí.&lt;/p&gt;

&lt;p&gt;Al traducir intenté ser muy preciso con lo técnico y consistente con los términos usados, pero también fui bastante libre en reformular la prosa de Evan para conservar ese tono cálido y transparente, que se traduce a un estilo bastante distinto en español. Por ejemplo, decidí usar consistentemente la primera persona plural (“hacemos”), versus una mezcla de segunda singular y primera plural (“you do”/“we do”) que usa el original. Así encuentro que suena menos acusatorio o demandante en español, un poquito más suave.&lt;/p&gt;

&lt;p&gt;Hice el esfuerzo de usar lenguaje neutro al género, algo difícil de lograr totalmente en español, donde históricamente el género masculino se toma como “neutro”. El inglés no tiene este mismo problema, ya que en muy pocos casos se necesita ser explícito con el género, pero el texto original del libro sí tiene un enfoque inclusivo que definitivamente quise conservar. Encuentro particularmente importante evitar profundizar este sesgo en una industria tan masculinizada como la del software.&lt;/p&gt;

&lt;p&gt;En el repositorio mismo anoté estas y algunas otras &lt;a href="https://github.com/agj/elm-guide-es/blob/95b53f26ce5e20ccb923fd14a4a995b50e718e8f/CONTRIBUTING.md#traduciendo" rel="noopener noreferrer"&gt;directrices que definí para orientar la traducción&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Me tomé la libertad de actualizar vínculos y referencias a recursos perdidos o desactualizados, también de reemplazar vínculos a contenido externo en inglés por una versión en español cuando hacía sentido. En muchos casos no existía esa alternativa, por lo que sería ideal eventualmente, por ejemplo, subtitular los videos usando &lt;a href="https://amara.org/" rel="noopener noreferrer"&gt;Amara&lt;/a&gt;, pero es una cantidad no trivial de trabajo extra. También estoy considerando traducir los artículos escritos por el mismo Evan, que vincula desde el libro, y dejarlos como apéndices. También faltan los diagramas y bastante código de ejemplo (los comentarios en particular) que aún están tal cual en inglés, lamentablemente.&lt;/p&gt;

&lt;p&gt;Pero además de estos puntos, todavía permanece una otra gran limitación, y es que el libro hace uso de un REPL incrustado en varias páginas. El texto te invita a probar expresiones simples de código Elm y ver cómo se resuelven. Actualmente ese REPL no funciona en la traducción, porque depende de un servicio externo que Evan alberga en su servidor. Lo bueno es que ya hablé con él, y le propuse un PR que añade el dominio usado por esta traducción a la lista blanca que tiene el servicio del REPL. Evan me dijo que lo va a actualizar tan pronto se desocupe un poco, pero se nota que está bastante limitado de tiempo, así que seguramente pasará un rato antes de que lo haga.&lt;/p&gt;

&lt;p&gt;Hablando de servidores y eso, la traducción la alojé en Github Pages porque es gratis, y así me ahorro un costo monetario que puede no ser grande, pero podría convertirse eventualmente en una razón para dejarla morir, si es que por alguna razón pierdo interés personal en mantenerla. Obviamente no tengo ninguna intención similar, pero tampoco sé en qué voy a estar dentro de unos cuantos años. Es algo que ya he visto ocurrir con sitios web relacionados con Elm, mantenidos presuntamente por gente que se alejó de la comunidad. Pero tal vez cambie de opinión si encuentro una buena solución.&lt;/p&gt;

</description>
      <category>spanish</category>
      <category>elm</category>
      <category>functional</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Cielos, y un jam con Gleam</title>
      <dc:creator>agj</dc:creator>
      <pubDate>Wed, 21 Jan 2026 02:39:25 +0000</pubDate>
      <link>https://forem.com/agj/cielos-y-un-jam-con-gleam-4cpk</link>
      <guid>https://forem.com/agj/cielos-y-un-jam-con-gleam-4cpk</guid>
      <description>&lt;p&gt;Participé en &lt;a href="https://gamejam.gleam.community/" rel="noopener noreferrer"&gt;un &lt;em&gt;game jam&lt;/em&gt;&lt;/a&gt;, o sea un evento grupal donde cada quien hace un videojuego durante un periodo de tiempo específico. Es el primer jam de juegos en torno al lenguaje de programación &lt;a href="https://gleam.run/" rel="noopener noreferrer"&gt;Gleam&lt;/a&gt;, un lenguaje joven que recién el 2024 &lt;a href="https://gleam.run/news/gleam-version-1/" rel="noopener noreferrer"&gt;llegó a su versión 1.0&lt;/a&gt; y empezó a ganar tracción.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://agj.github.io/cielos/" rel="noopener noreferrer"&gt;Aquí puedes jugar &lt;strong&gt;Cielos&lt;/strong&gt;&lt;/a&gt;, y &lt;a href="https://github.com/agj/cielos" rel="noopener noreferrer"&gt;aquí está el &lt;strong&gt;código fuente&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe5xa59jvg7xbigxp9bp3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe5xa59jvg7xbigxp9bp3.png" title="Pantalla del juego." alt="Portada de un juego, con su título e instrucciones de juego. Atrás se ven estrellas amarillas flotando en perspectiva frente a un horizonte infinito de colores pasteles." width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Cielos
&lt;/h1&gt;

&lt;p&gt;El juego que hice (a medias) se llama “Cielos”. El tema para el evento fue “Lucy in the sky with diamonds”. Lucy es el nombre de la mascota de Gleam, una estrellita rosada. Cada quién interpretó el tema a su manera, algunos haciendo mención directa a dicha mascota y a otros elementos de la cultura del lenguaje, otros al nombre Lucy, otros metieron diamantes por ahí.&lt;/p&gt;

&lt;p&gt;Yo partí con la idea ambigua de hacer algo &lt;a href="https://youtu.be/2J9SxTp0UbE" rel="noopener noreferrer"&gt;tipo Jeff Minter&lt;/a&gt;, apropiado al “LSD” de la temática. Al final me quedé con la parte del “cielo”, y decidí hacer algo en un 3D no poligonal tipo Super Scaler o Modo 7. Me interesó finalmente el aspecto técnico de desarrollar este motorcito de visualización, y es en lo que más se me fue el tiempo durante ese espacio de nueve días. La estética de la tecnología de esos juegos de los arcades de Sega o de Super Nintendo la encuentro súper evocadora, y ocasionalmente me dan ganas de experimentar por ahí, como en &lt;a href="https://blog.agj.cl/tag/?t=cave-trip" rel="noopener noreferrer"&gt;Cave Trip&lt;/a&gt; y (más o menos) en &lt;a href="https://blog.agj.cl/tag/?t=viewpoints" rel="noopener noreferrer"&gt;Viewpoints&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;No me dio tiempo para permitir movilidad en las tres dimensiones (estaba pensando en algo un poco tipo el &lt;em&gt;all-range mode&lt;/em&gt; de Star Fox), pero traté que el movimiento se sienta placentero de controlar. Tuve que usar una estrategia totalmente diferente para el teclado y las pantallas táctiles. Para tacto usé rotación tipo “scroll”, que hoy no me convence del todo, se hace un poco engorroso de controlar.&lt;/p&gt;

&lt;p&gt;Dadas las limitaciones de la librería gráfica que usé (&lt;a href="https://hexdocs.pm/paint" rel="noopener noreferrer"&gt;paint&lt;/a&gt;), creé una tipografía muy simple usando líneas rectas en una grilla de 12×12, con los caracteres definidos directamente &lt;a href="https://github.com/agj/cielos/blob/be969c622af09b50b0877e84f739dd3da32ca553/src/text.gleam#L57" rel="noopener noreferrer"&gt;en el código&lt;/a&gt;. Por supuesto, sólo dibujé los caracteres que necesitaba para el juego.&lt;/p&gt;

&lt;p&gt;Fui iterando y mucho se me quedó pendiente, pero en definitiva, hice algo que podría ser la base para un futuro juego más ambicioso. Quién sabe.&lt;/p&gt;

&lt;h1&gt;
  
  
  Y qué tal ese Gleam
&lt;/h1&gt;

&lt;p&gt;Gleam es un lenguaje que empecé a aprender recién ahora en noviembre, pero la verdad es que el proceso fue súper fluído. Me fui directo al &lt;a href="https://exercism.org/tracks/gleam/concepts" rel="noopener noreferrer"&gt;carril de aprendizaje en Exercism&lt;/a&gt; (&lt;a href="https://exercism.org/profiles/agj/solutions?track_slug=gleam" rel="noopener noreferrer"&gt;aquí mis resultados&lt;/a&gt;) y avancé sin ningún problema. Hoy día, después de este jam y de un par de semanas de experimentar usando la librería &lt;a href="https://hexdocs.pm/lustre" rel="noopener noreferrer"&gt;Lustre&lt;/a&gt; para desarrollar frontend, me siento bien cómodo programando con él. Gleam puede compilar a dos objetivos: &lt;a href="https://en.wikipedia.org/wiki/BEAM_%28Erlang_virtual_machine%29" rel="noopener noreferrer"&gt;BEAM&lt;/a&gt; (que lo pone en compañía con Erlang y Elixir) y JavaScript. Yo solamente conozco su lado JavaScript por ahora, hasta que me toque programar algún backend o algo por el estilo.&lt;/p&gt;

&lt;p&gt;Es un lenguaje que parece heredar de OCaml, Elixir, Elm y Rust. Yo tengo bastante experiencia con Elm, un poquito con OCaml, y sólo he hecho algunos ejercicios con Rust y Elixir. Pero aunque no tuviera esa experiencia, Gleam sigue siendo un lenguaje súper simple, diseñado para ser fácil de aprender.&lt;/p&gt;

&lt;p&gt;Voy a enumerar algunas cosas que me gustan de Gleam.&lt;/p&gt;

&lt;p&gt;Primero lo más obvio, tiene un sistema de tipos muy simple pero muy sólido, con &lt;a href="https://es.wikipedia.org/wiki/Tipo_de_dato_algebraico" rel="noopener noreferrer"&gt;tipos de datos algebraicos (ADTs)&lt;/a&gt; e inferencia de tipado a la &lt;a href="https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system" rel="noopener noreferrer"&gt;Hindley-Milner&lt;/a&gt;, heredados de la familia de lenguajes &lt;a href="https://es.wikipedia.org/wiki/ML_%28lenguaje_de_programaci%C3%B3n%29" rel="noopener noreferrer"&gt;ML&lt;/a&gt;. El sistema de tipos se parece bastante al de Elm, pero más simple aún, donde los tipos de registro son tipos de suma con una sola variante.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type MiRegistro {
  MiRegistro(un_campo: Int, otro_campo: String)
  // Aquí pueden ir otras variantes, y esto sería un tipo de suma.
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gleam tiene tan pocos conceptos que aprender que ni siquiera tiene “if/then/else”. Pero sí tiene un muy poderoso “pattern matching”, o búsqueda de patrones, que se puede usar con el mismo efecto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;case mi_condicion {
  True -&amp;gt; cuando_si()
  False -&amp;gt; cuando_no()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Está muy pulida la experiencia de desarrollo. El CLI &lt;code&gt;gleam&lt;/code&gt; es una herramienta todo en uno: inicializa tu proyecto, compila, formatea y tiene servidor de lenguaje para tu editor. El formato además sigue la filosofía de go fmt y de elm-format, o sea que nos evitamos tener que configurarlo, porque permite un sólo formato transversal y estándar.&lt;/p&gt;

&lt;p&gt;Como último punto positivo no puedo dejar de mencionar su comunidad. Lamentablemente la mayoría de comunidades de software se concentra en lo técnico, en desmedro de lo humano. En el caso de Gleam, la comunidad es mucho más explícitamente inclusiva de lo que he visto hasta ahora, lo cual también &lt;a href="https://es.wikipedia.org/wiki/Paradoja_de_la_tolerancia" rel="noopener noreferrer"&gt;implica ser excluyente a las voces intolerantes&lt;/a&gt;. Esta es una cita directa de su web:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Black lives matter. Trans rights are human rights. No nazi bullsh*t.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Y bueno, Gleam definitivamente tiene mucho que me gusta, pero también tiene aspectos que me desilusionan. Esos aspectos que carece son justamente las cosas que me gustan de Elm, y que busco y espero en nuevos lenguajes funcionales, en lo posible.&lt;/p&gt;

&lt;p&gt;Gleam no es un lenguaje puro. Cualquier función puede causar efectos, como hacer una llamada remota o yo qué sé. Me encantaría que tuviera gestión de efectos, y la librería Lustre al menos ofrece esa capacidad, pero no hay verificación a nivel del lenguaje. Además, la manera en que funciona su interfaz foránea con Erlang o JavaScript, que es arbitraria y sin chequeo de tipos, implica que el tipado no es sólido.&lt;/p&gt;

&lt;p&gt;Estos puntos en contra son una real lástima, pero tomo lo bueno, y adopté Gleam como un lenguaje que toma mucho de lo que me gusta de Elm y lo hace un poco más versátil, pudiendo usarse en el backend sin problemas, por ejemplo. Su desarrollo es más sano y vigoroso, también, lo cual lo hace más fácil de promover con los pares y en el contexto laboral. Seguiré programando Gleam, seguro.&lt;/p&gt;

</description>
      <category>spanish</category>
      <category>gleam</category>
      <category>gamedev</category>
      <category>functional</category>
    </item>
    <item>
      <title>Experimentando con Elm en mi trabajo</title>
      <dc:creator>agj</dc:creator>
      <pubDate>Sun, 31 Aug 2025 02:56:20 +0000</pubDate>
      <link>https://forem.com/agj/experimentando-con-elm-en-mi-trabajo-2c78</link>
      <guid>https://forem.com/agj/experimentando-con-elm-en-mi-trabajo-2c78</guid>
      <description>&lt;p&gt;Llevo trabajando cuatro años como desarrollador full-stack en una empresa cuyo producto es un &lt;a href="https://es.wikipedia.org/wiki/Software_como_servicio" rel="noopener noreferrer"&gt;“SaaS”&lt;/a&gt;, o sea, básicamente una aplicación web. La empresa ha sufrido varios cambios internos de estrategia, y eso ha impactado al lado técnico también. En una de esas ocasiones se vio posible el reconsiderar varios aspectos técnicos que llevábamos en el front-end, y quise probar suerte proponiendo mi tecnología preferida para este ámbito: Elm.&lt;/p&gt;

&lt;p&gt;Para bien o para mal, los planes fueron otros, y hoy día Elm no se usa en la empresa en ninguna capacidad. Igual, quiero compartir un poco mi experiencia construyendo esta propuesta de integrar Elm en un sistema preexistente. Lo que sigue está escrito bajo la suposición de que tienes cierto conocimiento del lenguaje y algo de experiencia trabajando el front-end.&lt;/p&gt;

&lt;p&gt;El proyecto partió después de una conversación con el “ingeniero principal” de la empresa, quien me propuso que haga la prueba migrando algún trozo pequeño de la aplicación como prueba de concepto. Quedamos en que tomaría una página interna: el explorador de usuarios. Esta página la usan el equipo de soporte y el área comercial para revisar y corregir problemas en los datos de nuestros usuarios, aunque aparentemente no se usa muy frecuentemente. La página merecía ser mejorada porque funcionaba muy lento, y siendo una página de uso exclusivo interno, el riesgo de intervenirla era bajo.&lt;/p&gt;

&lt;p&gt;Como parte de este experimento, también estuve algunos días haciendo “pair programming” con dos colegas, para que conozcan Elm y puedan hacerse una opinión que compartirían con el resto del equipo, aunque a ellos los integré una vez que ya estaba todo mayormente armado. (Un error de cálculo fue que estos dos colegas se fueron de la empresa poco tiempo después, obviamente por razones sin relación con este proyecto). El resto lo hice durante semanas de “cooldown” entre proyectos, y también algunos fines de semana.&lt;/p&gt;

&lt;h1&gt;
  
  
  Integración con Webpack
&lt;/h1&gt;

&lt;p&gt;Ese entorno “legacy” sobre el que trabajaría es un monolito hecho en PHP, con Vue en algunos lados, y el JS empaquetado usando &lt;a href="https://webpack.js.org/" rel="noopener noreferrer"&gt;Webpack&lt;/a&gt;. Por eso, la verdad fue bastante fácil integrar Elm usando un plugin de nombre &lt;a href="https://www.npmjs.com/package/elm-webpack-loader" rel="noopener noreferrer"&gt;elm-webpack-loader&lt;/a&gt;. Sólo tuve que crear un punto de entrada &lt;code&gt;main.js&lt;/code&gt;, donde instancio la aplicación Elm con sus flags y puertos, y agregarlo en el archivo de configuración de Webpack como &lt;code&gt;entry&lt;/code&gt;. Lo demás consiste en poner en la página la etiqueta &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; que carga el &lt;code&gt;.js&lt;/code&gt; generado, y otra etiqueta con un ID específico que luego le pasaremos a Elm para que la use.&lt;/p&gt;

&lt;p&gt;Como se ve abajo es como estructuré los archivos, finalmente. Nótese que permite tener múltiples miniaplicaciones Elm, cada una definida en un directorio &lt;code&gt;elm/Entrypoint/*&lt;/code&gt;, que incluye su propio punto de entrada y rutas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;elm/
  Api/            Comandos de llamada a la API.
  Data/           Tipos de datos.
  Entrypoint/
    */            Cada punto de entrada tiene un submódulo.
      Route/
        *.elm     Rutas, cada una un “mini TEA”.
      Main.elm    Un punto de entrada en Elm.
      Route.elm   Tipo que define la ruta actual.
      main.js     Punto de entrada para Webpack. Instancia `Main`.
  Util/           Utilidades varias.
  View/           “Componentes”.
  Layout.elm      Constantes de la vista, como colores y tamaños.
  Ports.elm       Puertos, lado Elm.
  Run.elm         Envoltorio sobre `Browser.element`.
  ports.js        Puertos, lado JS.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El archivo &lt;code&gt;elm.json&lt;/code&gt; queda en la raíz del proyecto, con el directorio &lt;code&gt;elm/&lt;/code&gt; configurado en &lt;code&gt;source-directories&lt;/code&gt;. Cada &lt;code&gt;elm/Entrypoint/*/main.js&lt;/code&gt; es vinculado en la configuración de Webpack. Este archivo se encarga de inicializar su &lt;code&gt;Main.elm&lt;/code&gt; respectivo, pasándole flags, y configura los puertos usando una función exportada desde &lt;code&gt;elm/ports.js&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Puertos para enrutar
&lt;/h1&gt;

&lt;p&gt;Mencioné la reconstrucción de una página (la de usuarios), pero este módulo en realidad comprende dos páginas: el listado de usuarios, y la página de visualización y edición de un usuario individual. Por eso hacía falta tener alguna solución para el manejo de rutas, o sea que funcione como una “single-page application”, si bien para sólo dos rutas.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://agj.github.io/elm-guide-es/webapps/navigation.html" rel="noopener noreferrer"&gt;Normalmente&lt;/a&gt;, si uno hace una aplicación Elm independiente, uno usa &lt;code&gt;Browser.application&lt;/code&gt; (&lt;a href="https://elm.dmy.fr/packages/elm/browser/latest/Browser?source=#application" rel="noopener noreferrer"&gt;doc.&lt;/a&gt;), que permite definir las funciones &lt;code&gt;onUrlRequest&lt;/code&gt; y &lt;code&gt;onUrlChange&lt;/code&gt; para reaccionar ante clicks en links y cambios en la ruta. Lamentablemente, en este contexto no es posible, ya que &lt;code&gt;Browser.application&lt;/code&gt; se apodera de la página completa. En este caso tenemos una aplicación Elm incrustada dentro de una aplicación más grande, con menús alrededor que funcionan de forma independiente, así que fue necesario solucionarlo de otra forma.&lt;/p&gt;

&lt;p&gt;Lo que hice fue usar &lt;code&gt;Browser.element&lt;/code&gt; (&lt;a href="https://elm.dmy.fr/packages/elm/browser/latest/Browser?source=#element" rel="noopener noreferrer"&gt;doc.&lt;/a&gt;), que sólo toma poder de un elemento dentro de la página, y para manejar los cambios de ruta usé puertos. Desde el lado de JS escuché el evento &lt;code&gt;"popstate"&lt;/code&gt; (&lt;a href="https://developer.mozilla.org/es/docs/Web/API/Window/popstate_event" rel="noopener noreferrer"&gt;MDN&lt;/a&gt;) emitido desde &lt;code&gt;window&lt;/code&gt; para registrar cambios en la ruta y gatillar el puerto correspondiente, y escuché un puerto de Elm para cambiar la ruta usando &lt;code&gt;window.history.pushState&lt;/code&gt; (&lt;a href="https://developer.mozilla.org/es/docs/Web/API/History/pushState" rel="noopener noreferrer"&gt;MDN&lt;/a&gt;). Esto sirve para cambiar parámetros de búsqueda en la URL tipo &lt;code&gt;?param=value&lt;/code&gt;, también.&lt;/p&gt;

&lt;p&gt;Usé sólo dos puertos, uno de entrada y otro de salida, ambos cuales transmiten un tipo así:&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;type&lt;/span&gt; &lt;span class="k"&gt;alias&lt;/span&gt; &lt;span class="kt"&gt;JsMsg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;kind&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;value&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Value&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Este es un patrón usualmente recomendado en la comunidad Elm, aunque hay distintas maneras de implementarlo. También usamos decodificadores JSON para manejar los valores recibidos en forma estricta.&lt;/p&gt;

&lt;h1&gt;
  
  
  Manejo centralizado de rutas
&lt;/h1&gt;

&lt;p&gt;Para simplificar la manera en que se construye una miniaplicación Elm, creé una abstracción: Envolví el uso de &lt;code&gt;Browser.element&lt;/code&gt; en una función que llamé &lt;code&gt;Run.elementWithFlagsAndRoute&lt;/code&gt;, con la siguiente firma.&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;elementWithFlagsAndRoute&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Cmd&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flagsDecoder&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Decoder&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;
    &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;routeDecoder&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AppUrl&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Maybe&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;
    &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Html&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;
    &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Cmd&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subscriptions&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Sub&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;
    &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;routeChanged&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Cmd&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="p"&gt;)&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;Program&lt;/span&gt; &lt;span class="kt"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;InitModel&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;RouteModel&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;RouteMsg&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esta función centraliza la decodificación de “flags” y, por supuesto, se hace cargo de los detalles del manejo de puertos para el enrutamiento. Esta estrategia está un poco inspirada en &lt;a href="https://juliu.is/elm-at-noredink/#our-elm-programs" rel="noopener noreferrer"&gt;un artículo por Ju Liu de NoRedInk&lt;/a&gt;, y en otro blog de por ahí cuyo link no logré desenterrar, que también habla de esto mismo.&lt;/p&gt;

&lt;p&gt;Internamente, esta función administra los puertos mencionados más arriba para saber cuándo hay un cambio en la URL. &lt;code&gt;routeDecoder : AppUrl -&amp;gt; Maybe route&lt;/code&gt; es la manera en que determinamos cuál es la nueva ruta (aquí usé &lt;a href="https://elm.dmy.fr/packages/lydell/elm-app-url/latest/AppUrl" rel="noopener noreferrer"&gt;lydell/elm-app-url&lt;/a&gt; para esto). Si esta función retorna &lt;code&gt;Nothing&lt;/code&gt; esto significa que navegamos hacia algo externo, entonces la navegación se defiere al navegador, y gatillamos la carga de otro recurso.&lt;/p&gt;

&lt;p&gt;La función &lt;code&gt;routeChanged : route -&amp;gt; model -&amp;gt; ( model, Cmd msg )&lt;/code&gt; que mencioné arriba es parecida a &lt;code&gt;update&lt;/code&gt;, pero dedicada a manejar cambios de ruta. En este primer acercamiento quedó una solución relativamente manual para manejar rutas, pero en todo caso, la idea es que esta función se responsabiliza de almacenar la ruta actual en el modelo y de inicializar el modelo de la ruta actual, potencialmente llamando comandos que la ruta necesita al inicializarse.&lt;/p&gt;

&lt;h1&gt;
  
  
  Arquitectura Elm anidada
&lt;/h1&gt;

&lt;p&gt;Existe un módulo &lt;code&gt;Entrypoint.*.Main&lt;/code&gt; para cada miniaplicación, que se encarga de manejar el estado global y de orquestrar los cambios de ruta. Opté por usar una arquitectura Elm anidada, de modo que este módulo es el encargado de delegar al &lt;code&gt;init&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt; y &lt;code&gt;view&lt;/code&gt; de cada ruta, que está definida en cada módulo &lt;code&gt;Entrypoint.*.Route.*&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;De hacerlo de nuevo, seguramente separaría entre un modelo global, compartido entre rutas, y otro modelo específico para cada ruta. Lo que hice, sin embargo, fue usar un sólo modelo central para cada miniaplicación, y en cada ruta usé “registros extensibles” para definir un subconjunto de llaves del mismo modelo. Por ejemplo, para el listado de usuarios dentro del módulo &lt;code&gt;Entrypoint.Usuarios.Route.List&lt;/code&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;type&lt;/span&gt; &lt;span class="k"&gt;alias&lt;/span&gt; &lt;span class="kt"&gt;Model&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
        &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;usuarios&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;RemoteData&lt;/span&gt; &lt;span class="kt"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Error&lt;/span&gt; &lt;span class="kt"&gt;Usuarios&lt;/span&gt;
        &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiConfig&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Config&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Estas llaves &lt;code&gt;usuarios&lt;/code&gt; y &lt;code&gt;apiConfig&lt;/code&gt; están definidas también en el &lt;code&gt;Model&lt;/code&gt; central. El módulo &lt;code&gt;Main&lt;/code&gt; le pasa el modelo completo al &lt;code&gt;update&lt;/code&gt; de esta ruta, pero gracias a que definimos su “submodelo” como un registro extensible, la ruta sólo ve lo que le interesa dentro de él. El problema de esta estrategia es que hay que tener cuidado en qué datos deben limpiarse al cambiar de ruta y cuáles no, y también implica repetir las mismas llaves en el &lt;code&gt;Model&lt;/code&gt; central y el de la ruta.&lt;/p&gt;

&lt;p&gt;Por ejemplo, si tenemos un tipo &lt;code&gt;Route&lt;/code&gt; así:&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;type&lt;/span&gt; &lt;span class="kt"&gt;Route&lt;/span&gt;
    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;List&lt;/span&gt; &lt;span class="kt"&gt;RouteListQuery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Query&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kt"&gt;Detail&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Podemos actualizar el modelo así en &lt;code&gt;Main&lt;/code&gt;, mapeando duplas de ruta + mensaje:&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;type&lt;/span&gt; &lt;span class="kt"&gt;Msg&lt;/span&gt;
    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;RouteListMsg&lt;/span&gt; &lt;span class="kt"&gt;Entrypoint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Usuarios&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Msg&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kt"&gt;RouteDetailMsg&lt;/span&gt; &lt;span class="kt"&gt;Entrypoint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Usuarios&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Detail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Msg&lt;/span&gt;


&lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Msg&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Model&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;Model&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Cmd&lt;/span&gt; &lt;span class="kt"&gt;Msg&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;List&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;RouteListMsg&lt;/span&gt; &lt;span class="n"&gt;routeMsg&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;newModel&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                    &lt;span class="kt"&gt;Entrypoint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Usuarios&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;routeMsg&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
            &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;newModel&lt;/span&gt;
            &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="kt"&gt;RouteListMsg&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Detail&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;RouteDetailMsg&lt;/span&gt; &lt;span class="n"&gt;routeMsg&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;newModel&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                    &lt;span class="kt"&gt;Entrypoint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Usuarios&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Detail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;routeMsg&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;
            &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;newModel&lt;/span&gt;
            &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="kt"&gt;RouteDetailMsg&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Este tipo de repetición por el uso de “pattern matching” para cada ruta se replica en otras funciones del módulo &lt;code&gt;Main&lt;/code&gt;, como &lt;code&gt;init&lt;/code&gt; y &lt;code&gt;view&lt;/code&gt;. Hay maneras de hacer esto un poco menos repetitivo, pero en general es lo que pasa cuando uno empieza a anidar arquitecturas Elm, y la razón por la que no se suele recomendar.&lt;/p&gt;




&lt;p&gt;Eso por ahora. Tal vez en otro artículo escribiré sobre la vista, o sea la estrategia que usé para crear “componentes” y su documentación usando &lt;a href="https://github.com/dtwrks/elm-book" rel="noopener noreferrer"&gt;ElmBook&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>elm</category>
      <category>spanish</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>On my elm-knobs Elm package</title>
      <dc:creator>agj</dc:creator>
      <pubDate>Tue, 26 Aug 2025 04:03:34 +0000</pubDate>
      <link>https://forem.com/agj/on-my-elm-knobs-elm-package-162g</link>
      <guid>https://forem.com/agj/on-my-elm-knobs-elm-package-162g</guid>
      <description>&lt;p&gt;Two years ago I made &lt;a href="https://elm.dmy.fr/packages/agj/elm-knobs/latest/" rel="noopener noreferrer"&gt;a simple Elm package&lt;/a&gt; to scratch my own itch, named &lt;code&gt;agj/elm-knobs&lt;/code&gt;. I wanted a simple interface to tweak constants dynamically in order to see how they affect a visual algorithm (which was just a project I was working on for fun; I'll post about it if I actually get around to finishing it). I found a few packages that get close to what I wanted, but nothing matching precisely my needs, so I just coded the thing and eventually turned it into a simple package.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkxvz9buvdnm0iayqu5jl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkxvz9buvdnm0iayqu5jl.png" title="Screenshot of the “polygon” elm-knobs example." alt="image" width="800" height="635"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The intended use-case for the package is squarely prototyping, so I didn't put effort into making it look nice or visually versatile. What I did though is add “knobs” (interactive controls) for primitive types (&lt;code&gt;Int&lt;/code&gt;, &lt;code&gt;Float&lt;/code&gt;, &lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Bool&lt;/code&gt;) and a few non-primitives.&lt;/p&gt;

&lt;p&gt;The way it works is through a &lt;code&gt;Knob a&lt;/code&gt; type value, where &lt;code&gt;a&lt;/code&gt; could be any type at all, as long as you have the means to create it. This knob value contains the current value of &lt;code&gt;a&lt;/code&gt; and a view function that returns HTML and emits updates to itself as an event whenever the controls get manipulated. Below is a quick rundown on how to use a knob.&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;knob&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Knob&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;
&lt;span class="n"&gt;knob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="kt"&gt;Knob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;knobValue&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;
&lt;span class="n"&gt;knobValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="kt"&gt;Knob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="n"&gt;knob&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="kt"&gt;Msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="kt"&gt;KnobUpdated&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Knob&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;knobView&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Html&lt;/span&gt; &lt;span class="kt"&gt;Msg&lt;/span&gt;
&lt;span class="n"&gt;knobView&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="kt"&gt;Knob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="kt"&gt;KnobUpdated&lt;/span&gt; &lt;span class="n"&gt;knob&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please note that &lt;em&gt;this example code and the rest of this post are actually based on unreleased v2 code.&lt;/em&gt; I guess I'll have to release that version soon, though.&lt;/p&gt;

&lt;p&gt;And then there's ways to compose, transform and create custom knobs. You can also serialize and deserialize a knob into a string, in order to persist its value using the Web Storage API or however else you like. Some omissions I can think of are knobs for dates, and for data structures such as lists and dictionaries.&lt;/p&gt;

&lt;p&gt;In the remainder of this blog post I'll describe a couple of ways in which I “over-developed” this package, beyond its content proper.&lt;/p&gt;

&lt;h1&gt;
  
  
  Interactive documentation
&lt;/h1&gt;

&lt;p&gt;This was my first Elm package, and I put a lot of effort into the documentation, taking it as a chance to learn and just make it as useful as possible. Elm core package documentation is way above average, so I took a lot of inspiration from it. I think that the &lt;a href="https://elm.dmy.fr/packages/agj/elm-knobs/1.2.0/Knob" rel="noopener noreferrer"&gt;package docs&lt;/a&gt; I wrote are pretty clear and organized, meant to almost work as a tutorial.&lt;/p&gt;

&lt;p&gt;One way I tried to improve the documentation was by creating &lt;a href="https://agj.github.io/elm-knobs/" rel="noopener noreferrer"&gt;“interactive documentation”&lt;/a&gt; using &lt;a href="https://github.com/dtwrks/elm-book" rel="noopener noreferrer"&gt;ElmBook&lt;/a&gt;. It's comprised of code examples for each of the knob functions, whose resulting HTML you can see live in your browser. It's interactive in the sense that the example code shows up as interactive HTML, not in that you can tweak the code yourself, though.&lt;/p&gt;

&lt;p&gt;One of the interesting things I did there was making sure that the example code matches what you see exactly, and that I don't have to forget to copy &amp;amp; paste something. Elm has no metaprogramming capabilities, so using an external tool to do this was necessary—here comes &lt;a href="https://comby.dev/" rel="noopener noreferrer"&gt;Comby&lt;/a&gt; to the rescue! (Think &lt;a href="https://en.wikipedia.org/wiki/Sed" rel="noopener noreferrer"&gt;sed&lt;/a&gt;, but for manipulating code syntax instead of plain text.)&lt;/p&gt;

&lt;p&gt;Take a look at the Elm code below, describing an example for &lt;code&gt;Knob.float&lt;/code&gt;. Using &lt;a href="https://github.com/agj/elm-knobs/blob/d0acf8c5a3d97ef714185e3f090bb33cda988ea1/scripts/update-example-code-strings.nu" rel="noopener noreferrer"&gt;a very short script I wrote using Comby&lt;/a&gt;, the string value in the &lt;code&gt;code&lt;/code&gt; field gets filled with what's in the &lt;code&gt;init_&lt;/code&gt; field automatically. Having these two synchronized makes sure that example code is always valid, since otherwise the interactive docs would not compile either.&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;floatDoc&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;KnobDoc&lt;/span&gt; &lt;span class="kt"&gt;Float&lt;/span&gt; &lt;span class="kt"&gt;Model&lt;/span&gt;
&lt;span class="n"&gt;floatDoc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;float"&lt;/span&gt;
    &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Nothing&lt;/span&gt;
    &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Nothing&lt;/span&gt;
    &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;init_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="kt"&gt;Knob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="s"&gt;"""
        Knob.float { step = 0.01, initial = 0 }
        """&lt;/span&gt;
    &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;\&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float&lt;/span&gt;
    &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;\&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toString&lt;/span&gt; &lt;span class="o"&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;fromFloat&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another thing you might notice from the code block above is that I used some tricks to make writing the documentation more DRY (i.e. more consistent, less error-prone). With ElmBook you write markdown to define the content of each page. I generate this markdown from records like the one you see above. &lt;a href="https://github.com/agj/elm-knobs/blob/d0acf8c5a3d97ef714185e3f090bb33cda988ea1/interactive-docs/src/KnobDoc.elm" rel="noopener noreferrer"&gt;Here's the module I wrote for that purpose&lt;/a&gt;, although it might be a bit hard to follow, especially given that it's written to accomodate the ElmBook API. But at any rate, something like the above turns into &lt;a href="https://agj.github.io/elm-knobs/1.2.0/#/knob-examples/number" rel="noopener noreferrer"&gt;what you see here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3bwrmqj2fqm21lb45f3n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3bwrmqj2fqm21lb45f3n.png" title="Screenshot of the above code as rendered in the interactive docs." alt="image" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm not happy with having to split the “API docs” and the “interactive docs”, so at some point I might figure out a way of automatically parsing the Elm comment docs and inserting that content into the interactive documentation, to keep it all in one place. Sounds like a lot of effort for such a relatively useless package, so if I ever end up going through the trouble, it'll be for the learning opportunity (or perhaps my OCD.)&lt;/p&gt;

&lt;h1&gt;
  
  
  Property-based tests
&lt;/h1&gt;

&lt;p&gt;I test all knobs using property-based tests (which in &lt;a href="https://github.com/elm-explorations/test" rel="noopener noreferrer"&gt;elm-test&lt;/a&gt; are called “fuzzers”, although that is &lt;a href="https://en.wikipedia.org/wiki/Fuzzing" rel="noopener noreferrer"&gt;the wrong term&lt;/a&gt;). The idea is that instead of each test checking for one or a few input values one by one against an expected result, we automatically generate a whole range of inputs, so we can make sure that our function behaves the way we expect given any possible input in the range. This technique receives this name because we can't use the same strategy for standard unit tests and just check one input against one result; we need to instead test that a given property holds.&lt;/p&gt;

&lt;p&gt;For instance, I have a suite of serialization tests, of which there's two types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Something I called “transitive equality”, for lack of a better term: If two knobs correspond to the same value, the result of serializing them should also be equal, and vice-versa.&lt;/li&gt;
&lt;li&gt;A classic &lt;a href="https://fsharpforfunandprofit.com/posts/property-based-testing-3/#inverseRev" rel="noopener noreferrer"&gt;round-trip test&lt;/a&gt;: We serialize and then deserialize, and expect the value of the input knob and of the result to be identical.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other tests check messages emitted when a user manipulates the controls, the behavior for invalid inputs, and such.&lt;/p&gt;

&lt;h1&gt;
  
  
  Individualizing versions
&lt;/h1&gt;

&lt;p&gt;Let me give you an example of a problem I frequently run into when using third-party library code. At my day job, we're using Vue and a component library called &lt;a href="https://www.shadcn-vue.com/" rel="noopener noreferrer"&gt;shadcn-vue&lt;/a&gt;. We are using v1, and recently they released v2. As soon as they did, the documentation for v1 disappeared.&lt;/p&gt;

&lt;p&gt;I don't like this experience. I want to decide when to upgrade, and as long as I'm using an old version, I want to have access to its documentation. Two libraries in the JavaScript space that do great in this respect are &lt;a href="https://ramdajs.com/" rel="noopener noreferrer"&gt;Ramda&lt;/a&gt; and &lt;a href="https://date-fns.org/docs/" rel="noopener noreferrer"&gt;date-fns&lt;/a&gt;. Elm packages automatically keep docs available for every version, so this is much less of a problem in our ecosystem. However, if I point a link at a &lt;code&gt;latest&lt;/code&gt; URL, I'm effectively making my documentation expire.&lt;/p&gt;

&lt;p&gt;So, all of the links to places in the documentation, including the package docs and the interactive docs, are versioned. And in order to make sure I don't forget to update any version string whenever I release a new version, I've implemented checks across the repo. For links in the readme, the &lt;a href="https://package.elm-lang.org/packages/jfmengels/elm-review-documentation/2.0.4/Docs-UpToDateReadmeLinks/" rel="noopener noreferrer"&gt;Docs.UpToDateReadmeLinks&lt;/a&gt; &lt;a href="https://github.com/jfmengels/node-elm-review" rel="noopener noreferrer"&gt;elm-review&lt;/a&gt; rule works great. For other cases, I wrote &lt;a href="https://github.com/agj/elm-knobs/blob/d0acf8c5a3d97ef714185e3f090bb33cda988ea1/scripts/check-version.nu" rel="noopener noreferrer"&gt;a Nushell script&lt;/a&gt; that even checks the git tag and the changelog.&lt;/p&gt;

&lt;p&gt;Interactive docs for older versions are also kept available in Github Pages and in compiled form in the repo.&lt;/p&gt;

</description>
      <category>elm</category>
      <category>testing</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Cómo uso Nix, Nushell y Just para configurar mis proyectos de código</title>
      <dc:creator>agj</dc:creator>
      <pubDate>Tue, 12 Aug 2025 00:08:08 +0000</pubDate>
      <link>https://forem.com/agj/como-uso-nix-nushell-y-just-para-configurar-mis-proyectos-de-codigo-3dcg</link>
      <guid>https://forem.com/agj/como-uso-nix-nushell-y-just-para-configurar-mis-proyectos-de-codigo-3dcg</guid>
      <description>&lt;p&gt;Desde hace un tiempo que en cualquier proyecto personal de código que empiezo, termino usando tres tecnologías: &lt;a href="https://nixos.org/" rel="noopener noreferrer"&gt;Nix&lt;/a&gt;, &lt;a href="https://www.nushell.sh/" rel="noopener noreferrer"&gt;Nushell&lt;/a&gt; y &lt;a href="https://just.systems/" rel="noopener noreferrer"&gt;Just&lt;/a&gt;. En este artículo quiero presentar estas tres herramientas, y compartir la forma en que las uso para configurar las dependencias del proyecto, escribir scripts, y definir sus tareas de desarrollo. Asumo que entiendes cómo usar tu terminal, que trabajas en un entorno tipo Unix, y que sabes programar.&lt;/p&gt;

&lt;p&gt;Vamos a partir viendo el manejo de dependencias usando Nix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nix para declarar dependencias
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://nixos.org/" rel="noopener noreferrer"&gt;Nix&lt;/a&gt; es un ecosistema enorme y muy versátil, pero en este contexto lo que nos interesa es que podemos usar esta herramienta para declarar todas las utilidades necesarias para correr un proyecto, compilarlo, hacer linting, etc., &lt;em&gt;y sin usar contenedores.&lt;/em&gt; Es compatible con casi cualquier entorno tipo Unix, o sea Linux, macOS y Windows bajo WSL.&lt;/p&gt;

&lt;p&gt;En particular, lo que uso es algo llamado “flake”. Este es un archivo de nombre &lt;code&gt;flake.nix&lt;/code&gt; que se pone en la raíz del proyecto, y es donde declaramos los paquetes que necesita.&lt;/p&gt;

&lt;p&gt;Partamos con un ejemplo simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github:nixos/nixpkgs/nixpkgs-unstable"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nv"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;nixpkgs&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="kd"&gt;let&lt;/span&gt;
    &lt;span class="nv"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nv"&gt;nixpkgs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;system&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aarch64-darwin"&lt;/span&gt;&lt;span class="p"&gt;;};&lt;/span&gt;
  &lt;span class="kn"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;devShell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"aarch64-darwin"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mkShell&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;buildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;elmPackages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;elm&lt;/span&gt;
        &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;elmPackages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;elm-format&lt;/span&gt;
        &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;elmPackages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;elm-test&lt;/span&gt;
        &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;just&lt;/span&gt;
        &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;nushell&lt;/span&gt;
      &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&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;Con esto, hemos definido un entorno que contiene algunas herramientas para un proyecto &lt;a href="https://elm-lang.org/" rel="noopener noreferrer"&gt;Elm&lt;/a&gt;, además de los binarios de Nushell y Just, a los que me voy a referir más abajo. Así como está, esto sólo va a funcionar en un Mac de los con procesador Apple Silicon. Después vamos a desarrollar este ejemplo para entenderlo mejor y hacerlo más versátil, pero por ahora partamos viendo en qué nos ayuda tener este archivo.&lt;/p&gt;

&lt;p&gt;Si corremos el comando &lt;code&gt;nix develop&lt;/code&gt; vamos a entrar a un shell Bash que contiene todas las herramientas declaradas en nuestro &lt;code&gt;flake.nix&lt;/code&gt;. Para salir basta con correr el comando &lt;code&gt;exit&lt;/code&gt;, y veremos que ya no tenemos disponibles las herramientas—son totalmente locales al proyecto.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;nix develop
&lt;span class="nv"&gt;$ &lt;/span&gt;which elm
/nix/store/6hx8g6k7ihgaqvy1i0ydiy7v13s04pf4-elm-0.19.1/bin/elm

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;exit
exit&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;which elm
elm not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La primera vez se generará un archivo &lt;code&gt;flake.lock&lt;/code&gt;, el cual podemos integrar en Git (o tu VCS preferido) para mantener la versión exacta de estas dependencias. Luego se descargarán algunos binarios en caché o se compilarán las dependencias desde su código fuente. Pero eso ocurrirá sólo la primera vez, o cuando quieras cambiar las dependencias, ya que se almacenará todo en el “store” global de Nix.&lt;/p&gt;

&lt;h3&gt;
  
  
  Un poco más en profundidad
&lt;/h3&gt;

&lt;p&gt;Puedes saltarte esta sección si no te interesa. Aquí voy a explicar un poquito cómo hacer funcionar el ejemplo de arriba para ti, ya que no quise complejizar con ciertos detalles.&lt;/p&gt;

&lt;p&gt;Primero, hace falta especificar que esta funcionalidad llamada “flakes” es, al momento en que escribo esto, considerada experimental, y por lo tanto no está activada por defecto: hay que configurarla.&lt;/p&gt;

&lt;p&gt;Si no tienes Nix instalado aún, te recomiendo usar el &lt;a href="https://github.com/NixOS/nix-installer" rel="noopener noreferrer"&gt;instalador experimental&lt;/a&gt;. Es mejor que el “normal” en varios aspectos, entre ellos, funciona mejor en macOS, y también incluye una forma fácil de desinstalar. Corre el script de abajo en tu terminal para instalarlo (o copia el script de la dirección vinculada arriba).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sSfL&lt;/span&gt; https://artifacts.nixos.org/nix-installer | sh &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--enable-flakes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nótese que le pasamos la opción &lt;code&gt;--enable-flakes&lt;/code&gt; para inmediatamente dejar activa esta funcionalidad.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Editado 2026-04-11: Quité la recomendación de instalar usando el “Determinate Nix Installer”, ya que &lt;a href="https://determinate.systems/blog/installer-dropping-upstream/" rel="noopener noreferrer"&gt;perdió soporte para instalar Nix original&lt;/a&gt;, ya sólo sirve para instalar su fork, el cual no recomiendo.&lt;/em&gt; 👎&lt;/p&gt;

&lt;p&gt;Si por otro lado ya tienes instalado Nix y no te funcionan los flakes, necesitas añadir un poco de configuración. Abre o crea un archivo &lt;code&gt;~/.config/nix/nix.conf&lt;/code&gt; y ponle esto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="nv"&gt;experimental-features&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;flakes&lt;/span&gt; &lt;span class="nv"&gt;nix-command&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ya con todo configurado correctamente, te pongo una versión ajustada del archivo &lt;code&gt;flake.nix&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github:nixos/nixpkgs/nixpkgs-unstable"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;flake-utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github:numtide/flake-utils"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nv"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;nixpkgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;flake-utils&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="nv"&gt;flake-utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;eachDefaultSystem&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nv"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt;
        &lt;span class="nv"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nv"&gt;nixpkgs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;system&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;system&lt;/span&gt;&lt;span class="p"&gt;;};&lt;/span&gt;
      &lt;span class="kn"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;devShell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mkShell&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nv"&gt;buildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;elmPackages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;elm&lt;/span&gt;
            &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;elmPackages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;elm-format&lt;/span&gt;
            &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;elmPackages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;elm-test&lt;/span&gt;
            &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;just&lt;/span&gt;
            &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;nushell&lt;/span&gt;
          &lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&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;Lo que hicimos fue declarar una nueva entrada bajo &lt;code&gt;inputs&lt;/code&gt;, de nombre &lt;code&gt;flake-utils&lt;/code&gt;, y más abajo usar la función &lt;code&gt;eachDefaultSystem&lt;/code&gt; que provee, la que nos ayuda a configurar un shell de desarrollo para múltiples entornos simultáneamente. Lo demás está igual.&lt;/p&gt;

&lt;p&gt;Para agregar otros paquetes, los encuentras en &lt;a href="https://search.nixos.org/packages?channel=unstable" rel="noopener noreferrer"&gt;la búsqueda de Nixpkgs&lt;/a&gt;. Una vez que encuentres el nombre de lo que quieres, lo puedes agregar en la lista en tu flake.&lt;/p&gt;

&lt;p&gt;Y esto es todo lo esencial que necesitas saber para empezar, aunque por supuesto, hay muchos detalles que podrás ir descubriendo por tu cuenta. Como dije al principio, Nix es un mundo muy grande por explorar. Y de hecho, si Nix te parece muy complejo, existen herramientas que funcionan con Nix por debajo, pero proveen una interfaz más intuitiva para este caso de uso; te recomiendo que le eches una mirada a &lt;a href="https://www.jetify.com/devbox" rel="noopener noreferrer"&gt;Devbox&lt;/a&gt;, &lt;a href="https://flox.dev/" rel="noopener noreferrer"&gt;Flox&lt;/a&gt; y &lt;a href="https://devenv.sh/" rel="noopener noreferrer"&gt;Devenv&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nushell para escribir scripts
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.nushell.sh/" rel="noopener noreferrer"&gt;Nushell&lt;/a&gt; es una alternativa a Bash o zsh. La diferencia es que en vez de seguir los lineamientos &lt;a href="https://es.wikipedia.org/wiki/POSIX" rel="noopener noreferrer"&gt;POSIX&lt;/a&gt; e interactuar sólo con texto plano, trabajamos con datos estructurados. Básicamente, es un shell y lenguaje que reemplaza la necesidad de Bash, grep, sed, awk, jq, curl y muchas otras utilidades de línea de comandos frecuentemente usadas para procesar datos.&lt;/p&gt;

&lt;p&gt;Igual se ajusta hasta cierto punto a nuestras expectativas como usuarios de Bash y similares. Por ejemplo, en un shell Nushell el comando &lt;code&gt;ls&lt;/code&gt; funciona como es de esperarse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt;
╭───┬────────────┬──────┬───────┬─────────────╮
│ &lt;span class="c"&gt;# │    name    │ type │ size  │  modified   │&lt;/span&gt;
├───┼────────────┼──────┼───────┼─────────────┤
│ 0 │ flake.lock │ file │ 569 B │ 8 hours ago │
│ 1 │ flake.nix  │ file │ 405 B │ 7 hours ago │
╰───┴────────────┴──────┴───────┴─────────────╯
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pero esta salida no es puramente texto. En realidad lo que hemos obtenido es una tabla con filas y columnas. Mira el tipo de cosas que podemos hacer con sólo Nushell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; | where name &lt;span class="o"&gt;=&lt;/span&gt;~ &lt;span class="s1"&gt;'[.]lock$'&lt;/span&gt; | get 0.name | open
::: | from json | get nodes.nixpkgs.locked.lastModified
::: | &lt;span class="nv"&gt;$in&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; 1_000_000_000 | into datetime
Tue, 5 Aug 2025 11:35:34 +0000 &lt;span class="o"&gt;(&lt;/span&gt;2 days ago&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Obviamente este ejemplo es puramente demostrativo, pero fíjate: estamos listando archivos, filtrando en base a un regex, sacando el nombre del primero en la lista, leyendo el contenido del archivo, interpretándolo como JSON, recuperando un valor numérico dentro del JSON, multiplicando para convertir segundos a nanosegundos, y finalmente convirtiendo eso a una fecha.&lt;/p&gt;

&lt;p&gt;Es tan práctico que lo tengo como mi shell por defecto. Pero para el caso de este artículo, lo relevante es su utilidad para escribir scripts simples y legibles que transforman archivos, levantan servicios, recuperan datos de internet, y más. Los archivos llevan la extensión &lt;code&gt;.nu&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Veamos un pequeño ejemplo. Este es un script que actualiza una lista de exclusiones para evitar visitas de robots de IA, con datos que descargamos del &lt;a href="https://github.com/ai-robots-txt/ai.robots.txt/" rel="noopener noreferrer"&gt;Github del proyecto ai.robots.txt&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Algunas constantes.
let aiRobotsTxtBaseUrl = "https://raw.githubusercontent.com/ai-robots-txt/ai.robots.txt/refs/heads/main"
let startMarkerLine = "# Start ai.robots.txt"
let endMarkerLine = "# End ai.robots.txt"

# Una pequeña función para simplificar el código más adelante.
def splitLines [] {
  split row "\n"
}

# Función que procesa los datos para un archivo específico, ya que son
# dos los que queremos actualizar.
def updateFile [$filename] {
  # Leemos el archivo local y lo dejamos en la variable como una lista
  # de líneas.
  let localLines = open $"./public/($filename)" | splitLines
  # Lo mismo para el archivo remoto.
  let updateLines = http get $"($aiRobotsTxtBaseUrl)/($filename)" | splitLines

  # El archivo local tiene líneas que marcan el comienzo y el final
  # del contenido que queremos actualizar, marcados por las constantes
  # de arriba de nombre `$startMarkerLine` y `$endMarkerLine`. En base
  # a ese contenido, cortamos la lista para recuperar el contenido
  # que queremos mantener, el cual viene antes y después dentro del
  # archivo.
  let firstSplit = $localLines | split list $startMarkerLine
  let linesBeforeUpdate = $firstSplit | get 0
  let secondSplit = $firstSplit | get 1 | split list $endMarkerLine
  let linesAfterUpdate = $secondSplit | get 1

  # Insertamos las líneas provenientes del archivo remoto en la mitad,
  # concatenando todo en una misma lista.
  let updatedLines = (
    $linesBeforeUpdate
    ++ [$startMarkerLine]
    ++ $updateLines
    ++ [$endMarkerLine]
    ++ $linesAfterUpdate
  )

  # Sobreescribimos el archivo antiguo con las nuevas líneas.
  $updatedLines | str join "\n" | save --force $"./public/($filename)"
}

# Usamos la función definida arriba para procesar dos archivos.
updateFile ".htaccess"
updateFile "robots.txt"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No sé lo que pienses tú, pero yo creo que queda un código extremadamente legible y conciso. Es fácil de escribir, trae “pilas incluídas” como quien dice, con soporte para procesar muchos formatos de archivo. Y cuando hay algo que no se puede hacer con Nushell, puedes echar mano a cualquier herramienta de línea de comandos, idéntico a como haríamos en un script de Bash.&lt;/p&gt;

&lt;p&gt;El lenguaje Nushell toma las “pipes” (&lt;code&gt;|&lt;/code&gt;) de los shell Unix y las combina con estructuras de datos inmutables. Es un lenguaje bastante funcional, de tipado dinámico pero estricto, o sea que si una función recibe un valor con un tipo inesperado, la ejecución falla con un mensaje bien explícito, como este:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"hola"&lt;/span&gt;, &lt;span class="s2"&gt;"cómo"&lt;/span&gt;, &lt;span class="s2"&gt;"estás"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; | &lt;span class="nb"&gt;date &lt;/span&gt;to-timezone &lt;span class="s2"&gt;"UTC"&lt;/span&gt;
Error: nu::parser::input_type_mismatch

  × Command does not support list&amp;lt;string&amp;gt; input.
   ╭─[entry &lt;span class="c"&gt;#6:1:31]&lt;/span&gt;
 1 │ &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"hola"&lt;/span&gt;, &lt;span class="s2"&gt;"cómo"&lt;/span&gt;, &lt;span class="s2"&gt;"estás"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; | &lt;span class="nb"&gt;date &lt;/span&gt;to-timezone &lt;span class="s2"&gt;"UTC"&lt;/span&gt;
   ·                             ────────┬───────
   ·                                     ╰── &lt;span class="nb"&gt;command &lt;/span&gt;doesn&lt;span class="s1"&gt;'t support list&amp;lt;string&amp;gt; input
   ╰────
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Just para definir tareas
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://just.systems/" rel="noopener noreferrer"&gt;Just&lt;/a&gt; es una herramienta con un alcance muy moderado: permitir definir tareas repetibles para un proyecto. Lo típico es definir una tarea “build” para compilar el código, o una “dev” para ejecutarlo localmente, o una “test” para correr los tests. La manera en que funcionaría usando Just es con los comandos &lt;code&gt;just build&lt;/code&gt;, &lt;code&gt;just dev&lt;/code&gt; y &lt;code&gt;just test&lt;/code&gt;, cuyas tareas uno mismo define en un archivo de nombre &lt;code&gt;justfile&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Seguro que hasta ahí no hay nada nuevo, es lo mismo que uno haría con Make o con &lt;code&gt;npm run&lt;/code&gt;, por ejemplo. Y es verdad, Just no ofrece un paradigma novedoso. Lo único que ofrece es simpleza.&lt;/p&gt;

&lt;p&gt;El modelo de Just es Make. No sé tú, pero yo al menos he escrito muchos &lt;a href="https://dev.to/djsurgeon/como-hacer-un-buen-makefile-2pol"&gt;makefile&lt;/a&gt; para definir tareas para un proyecto. El problema con eso es que Make tiene muchas asperezas para este objetivo. Por ejemplo, si defines una tarea &lt;code&gt;test&lt;/code&gt; para correr tus tests, y resulta que tienes una carpeta &lt;code&gt;test&lt;/code&gt; en el mismo directorio, no se va a correr la tarea. Esto es porque Make está hecho para compilar cosas, donde el nombre de la tarea es el nombre del archivo, y cuando el archivo o directorio ya existe, simplemente no se ejecuta la tarea.&lt;/p&gt;

&lt;p&gt;La razón por la que me gusta Just es porque es como un Make pero hecho para correr tareas, con todas sus asperezas pulidas. La sintaxis de un “justfile” es igual a la de un makefile, pero un poquito mejor. Por ejemplo, puedes indentar usando espacios en vez de tabs, y puedes poner comentarios de documentación. Además puedes listar tareas usando &lt;code&gt;just --list&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Te pongo un ejemplo. Este archivo lleva el nombre &lt;code&gt;justfile&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;port := "1237"

[private]
default:
  just --list

# Compila archivos.
build: install
  rm -rf dist
  pnpm exec vite build

# Levanta el servidor de desarrollo.
develop: install qr
  pnpm exec vite --port {{port}} --host

[private]
install:
  pnpm install

[private]
qr:
  #!/usr/bin/env nu
  let ip = sys net | where name == "en0" | get 0.ip | where protocol == "ipv4" | get 0.address
  let url = $"http://($ip):{{port}}"
  qrtool encode -t ansi256 $url
  print $url
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En la línea 4 definí la primera tarea, a la que le puse nombre &lt;code&gt;default&lt;/code&gt; porque es la que se ejecutará por defecto si uno escribe &lt;code&gt;just&lt;/code&gt;. Puede ser cualquier tarea, pero a mí me gusta que la primera tarea sirva para ver la lista de tareas disponibles, por eso la hice “escondida” usando el modificador &lt;code&gt;[private]&lt;/code&gt;. Mira cómo se ve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;just
just &lt;span class="nt"&gt;--list&lt;/span&gt;
Available recipes:
    build   &lt;span class="c"&gt;# Compila archivos.&lt;/span&gt;
    develop &lt;span class="c"&gt;# Levanta el servidor de desarrollo.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bastante bonito, ¿no? Te puedes dar cuenta de que los comentarios escritos sobre cada tarea sirven como documentación de esa tarea, y que aparecen al usar el comando &lt;code&gt;--list&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Otras aspectos notables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;En la primera línea definí una constante que después uso escribiendo &lt;code&gt;{{port}}&lt;/code&gt; en dos lugares.&lt;/li&gt;
&lt;li&gt;Hay una tarea &lt;code&gt;install&lt;/code&gt; que definí como prerrequisito de dos otras, o sea que cuando, por ejemplo, llame &lt;code&gt;just build&lt;/code&gt;, se ejecutarán primero los comandos en &lt;code&gt;install&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;La tarea &lt;code&gt;qr&lt;/code&gt; comienza con la línea &lt;code&gt;#!/usr/bin/env nu&lt;/code&gt;, la cual sirve para definir con qué programa quieres que se ejecuten los comandos de esa tarea. En este caso, escribí un pequeño script de Nushell. Puedes usar Python si quisieras, o Clojure vía &lt;a href="https://babashka.org/" rel="noopener noreferrer"&gt;Babashka&lt;/a&gt;, o cualquier otro lenguaje aquí. Esta funcionalidad es súper útil.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Esa fue mi presentación sobre estas herramientas que uso para configurar mis proyectos. Hay una otra herramienta que no mencioné: &lt;a href="https://direnv.net/" rel="noopener noreferrer"&gt;direnv&lt;/a&gt;, la cual hace que sea mucho más placentero trabajar con entornos Nix. Tal vez otro día escribiré algo al respecto, no sé.&lt;/p&gt;

&lt;p&gt;Ojalá que te haya sido útil, y cuéntame si encontraste algo incorrecto o confuso.&lt;/p&gt;

</description>
      <category>spanish</category>
      <category>nix</category>
      <category>nushell</category>
      <category>devex</category>
    </item>
  </channel>
</rss>
