<?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>DEV Community: Yawar Amin</title>
    <description>The latest articles on DEV Community by Yawar Amin (@yawaramin).</description>
    <link>https://dev.to/yawaramin</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%2F21713%2F7b6defe4-0769-4d3b-8a01-9afde30305be.png</url>
      <title>DEV Community: Yawar Amin</title>
      <link>https://dev.to/yawaramin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yawaramin"/>
    <language>en</language>
    <item>
      <title>Open source is about vendor lock-in</title>
      <dc:creator>Yawar Amin</dc:creator>
      <pubDate>Tue, 09 Dec 2025 23:24:45 +0000</pubDate>
      <link>https://dev.to/yawaramin/open-source-is-about-vendor-lock-in-3fhf</link>
      <guid>https://dev.to/yawaramin/open-source-is-about-vendor-lock-in-3fhf</guid>
      <description>&lt;p&gt;TIME and again, people try to redefine the term 'open source', which has a well-known &lt;a href="https://opensource.org/osd" rel="noopener noreferrer"&gt;definition&lt;/a&gt;, to suit their own needs. We've seen people come up with all sorts of arguments why their stuff is really 'open source', why 'open source' does not mean what we think it does, and why it's not even important. But it's always notable that they're doing this while trying to co-opt the term.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://x.com/dhh/status/1996627792008827342" rel="noopener noreferrer"&gt;latest salvo&lt;/a&gt; in this inane war is by the infamous David Heinemeier Hansson (or DHH to save us some typing):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Love how calling Fizzy open source is triggering some because our MIT-derived O'Saasy License reserves SaaS monetization rights to us as creators. Same nerds will demoan lack of "sustainable OSS" or argue that handing over all changes under GPL is akshually freedom. Hilarious.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'm reminded of the immortal &lt;a href="https://www.youtube.com/watch?v=2sRS1dwCotw" rel="noopener noreferrer"&gt;Luke Skywalker quote&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Amazing. Every word of what you just said, is wrong.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's break it down:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;triggering&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Triggering? Annoying, maybe. Like, here we go again. 'Triggering' is the word you use, I guess, when you want to instantly dismiss the other side. Oh well.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Same nerds will demoan&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'll have you know I've never demoaned anything in my life. Bemoaned, maybe. But never demoaned!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;lack of "sustainable OSS"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As far as I've seen, no one has claimed this. In fact, we've actually been saying it's &lt;em&gt;not&lt;/em&gt; OSS, so whatever sustainability strategy you're going for here (SaaS monetization), isn't even relevant. The part we've been protesting is that you've been calling it 'open source'. We'll circle back to that.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;handing over all changes under GPL is akshually freedom&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Well, GPL is meant to guarantee the important freedoms of software to &lt;em&gt;users.&lt;/em&gt; OSS is meant to achieve the same goal. The goal being: &lt;strong&gt;avoiding vendor lock-in.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Vendor lock-in
&lt;/h2&gt;

&lt;p&gt;People forget this, but vendor lock-in is what gave rise to the Free Software movement, and thus to open source. Richard Stallman couldn't get his printer to work and couldn't just fix it himself because the software was proprietary and they &lt;a href="https://www.gnu.org/philosophy/rms-nyu-2001-transcript.txt" rel="noopener noreferrer"&gt;refused to give him the source code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The whole idea behind Free Software, and by extension OSS, is that you're not locked in to your vendor. If tomorrow they go away, or jack up your price, you're not just stuck. You have the option to go to a different vendor and get them to maintain the software for you. This creates, in a sense, a perfect free market condition. Economists fantasize about markets like this. With OSS we've actually created it.&lt;/p&gt;

&lt;p&gt;But people today still haven't learned their lesson! They commit to the Oracles, the Salesforces, the Jiras, and tons of other vendors hoping and praying that they don't get screwed over. And when they inevitably &lt;em&gt;do&lt;/em&gt; get screwed over, they finally start looking for alternatives–at massive cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  OSS as anti-monopoly
&lt;/h2&gt;

&lt;p&gt;OSS is the antidote to this situation. If tomorrow your PostgreSQL host jacks up their prices, you just move to a different host. Same with Linux, Redis, PHP, WordPress, etc.&lt;/p&gt;

&lt;p&gt;And users (think: companies, aka paying customers) &lt;em&gt;love&lt;/em&gt; this. This is one of their de-risking strategies: avoid vendor lock-in. This has made OSS a massive success, for decades. And (a large) part of this success is due to the definition of open source ensuring that all OSS licenses allow avoiding vendor lock-in. You can just pick up your software and take it to a different vendor.&lt;/p&gt;

&lt;p&gt;And people look at this massive success and brand recognition and think: if I just put my code on GitHub and call it 'open source', it looks great and gives me a huge boost. And when you point out it's &lt;em&gt;not&lt;/em&gt; open source, they argue, who are you to gatekeep the definition of open source?&lt;/p&gt;

&lt;p&gt;Well I would argue, why do you care so much about calling your thing 'open source'? Why not 'source available'? Why not 'public source'? Or whatever? It's because you know the brand recognition and cachet of the term 'open source'. This didn't just happen out of thin air, spontaneously. It took the work of many, over decades, to build a massive success story of a competitive, free market, commons.&lt;/p&gt;

&lt;p&gt;And you want to come in and cash in on this success while refusing to allow the &lt;em&gt;one thing&lt;/em&gt; that made the success possible: to let anyone be a vendor for the software. You want to be the sole vendor? Keep it proprietary. Use a source-available license. Nobody will bat an eye. Nobody cares! Go and make your money.&lt;/p&gt;

&lt;p&gt;Just don't call it 'open source'.&lt;/p&gt;

</description>
      <category>opensource</category>
    </item>
    <item>
      <title>Why hx-boost is actually the most important feature of htmx</title>
      <dc:creator>Yawar Amin</dc:creator>
      <pubDate>Fri, 20 Jun 2025 00:37:06 +0000</pubDate>
      <link>https://dev.to/yawaramin/why-hx-boost-is-actually-the-most-important-feature-of-htmx-3nc0</link>
      <guid>https://dev.to/yawaramin/why-hx-boost-is-actually-the-most-important-feature-of-htmx-3nc0</guid>
      <description>&lt;p&gt;SOME of you may know by now that I'm a fan of &lt;a href="https://htmx.org" rel="noopener noreferrer"&gt;htmx&lt;/a&gt; and its hypermedia-driven website/application design philosophy. When people first learn about htmx, they are often captivated by the idea of &lt;code&gt;hx-get&lt;/code&gt;, &lt;code&gt;hx-post&lt;/code&gt;, &lt;code&gt;hx-delete&lt;/code&gt;, etc. attributes being able to fire off HTTP requests with these methods from &lt;em&gt;any&lt;/em&gt; tag on the page. I know I was!&lt;/p&gt;

&lt;p&gt;But it turns out there's a really good reason why we &lt;em&gt;shouldn't&lt;/em&gt;  just fire off requests with &lt;em&gt;any&lt;/em&gt; method from &lt;em&gt;any&lt;/em&gt; tag on the page: graceful degradation. The idea that even if the JavaScript on the page does not work for whatever reason–turned off on the user's browser, not loaded yet due to big scripts, or maybe due to a slow connection–the user should still be able to interact with most (if not all) of the functionality on the page.&lt;/p&gt;

&lt;p&gt;For example, if JavaScript doesn't load, we want users to be able to submit a sign-up form or click on a link to load a new page.&lt;/p&gt;

&lt;p&gt;This is what the &lt;a href="https://htmx.org/attributes/hx-boost/" rel="noopener noreferrer"&gt;hx-boost&lt;/a&gt; attribute is for. As mentioned on that page:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This has the nice fallback that, if the user does not have javascript enabled, the site will continue to work.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But graceful degradation isn't just a way to make websites that work without JavaScript; it leads us towards a more hypermedia-oriented design.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;hx-boost&lt;/code&gt;–the starting point
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;hx-boost&lt;/code&gt; is a widely misunderstand and, I feel, misused attribute. Its documentation seems to suggest that it should be applied on an entire section of the page to 'boost' all the links inside it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;hx-boost=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;/page1&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Go To Page 1&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;/page2&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Go To Page 2&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that 'boosting' would simply send the request to the given URL, swap out the entire &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tag with the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tag from the response, and update the URL in the URL bar.&lt;/p&gt;

&lt;p&gt;However, I think this is an &lt;em&gt;incorrect&lt;/em&gt; way to use &lt;code&gt;hx-boost&lt;/code&gt;. The biggest reason why is that applying htmx attributes and inheriting them into large sections of the page tends to lead to unpredictable behaviours and bugs. Also, it is missing the key point of htmx, which I feel is to do &lt;em&gt;targeted&lt;/em&gt; updates to &lt;em&gt;parts&lt;/em&gt; of the page, not the &lt;em&gt;entire&lt;/em&gt; &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; of the page.&lt;/p&gt;

&lt;p&gt;So why is &lt;code&gt;hx-boost&lt;/code&gt; misused then? I believe it was intended as a shortcut to give people with classic multi-page 'apps' an easy way to make their apps 'feel' like SPAs without having to do the actual work to break down the pages into components. But I believe that this work is actually crucial to making an HDA–a Hypermedia Driven App–that has a great user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boosting only
&lt;/h2&gt;

&lt;p&gt;The first decision we need to make is to &lt;em&gt;eliminate&lt;/em&gt; or at least &lt;em&gt;minimize&lt;/em&gt; the use of the &lt;code&gt;hx-get&lt;/code&gt;, &lt;code&gt;hx-post&lt;/code&gt;, etc. attributes and use boosting &lt;em&gt;only&lt;/em&gt; unless we have a very good reason not to. Why? Remember–graceful degradation. The &lt;code&gt;hx-&lt;/code&gt; + method attributes work as long as JavaScript and htmx are available, but become no-ops otherwise. Boosted elements gracefully degrade to their basic browser-provided functionality!&lt;/p&gt;

&lt;h2&gt;
  
  
  Boosted links
&lt;/h2&gt;

&lt;p&gt;Links ie &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tags do two things: load the resource from the given URL, and update the URL in the URL bar (+ pushing into the page's history stack, but we will treat both as the same thing in this article). Boosted links do the same thing but can additionally swap only &lt;em&gt;parts&lt;/em&gt; of the current page, instead of the entire page. Eg:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;/page1&lt;/span&gt; &lt;span class="na"&gt;hx-boost=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt; &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;#page&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Go To Page 1&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will make htmx request &lt;code&gt;/page1&lt;/code&gt; and swap the response HTML into the element with ID &lt;code&gt;page&lt;/code&gt;. Remember that the default swap style is &lt;code&gt;innerHTML&lt;/code&gt;, so the content will be &lt;em&gt;inside&lt;/em&gt; the &lt;code&gt;#page&lt;/code&gt; element, which will be preserved.&lt;/p&gt;

&lt;p&gt;But wait...how should the server know to respond with a fragment that is appropriate to be swapped inside &lt;code&gt;#page&lt;/code&gt;? Well, htmx sends a request header &lt;code&gt;HX-Request: true&lt;/code&gt; with &lt;em&gt;every&lt;/em&gt; request. So the backend server can use a fairly simple heuristic to figure out that it should respond with a fragment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The request has a header &lt;code&gt;HX-Request&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The request does &lt;em&gt;not&lt;/em&gt; have a header &lt;code&gt;HX-History-Restore-Request&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If these two conditions are fulfilled, it can respond with a fragment. Otherwise, it can respond with a full page ie &lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;...&lt;/code&gt; and so on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; when the server does use the values of these headers to determine the response content, it &lt;em&gt;must&lt;/em&gt; set the response header &lt;code&gt;Vary: HX-Request, HX-History-Restore-Request&lt;/code&gt;. This is to ensure that the browser caches the response correctly.&lt;/p&gt;

&lt;p&gt;Then, htmx will automatically update the URL in the URL bar for boosted links; you don't have to manage that explicitly. Basically, boosting works as closely as possible to a normal, non-JavaScript hyperlink.&lt;/p&gt;

&lt;p&gt;Lastly and &lt;strong&gt;very crucially,&lt;/strong&gt; boosted links work with not only right-click 'open in background tab', but &lt;em&gt;also&lt;/em&gt; with Ctrl-click/Cmd-click which users have come to expect from normal links. This may be surprising but Ctrl-click/Cmd-click does &lt;em&gt;not&lt;/em&gt; work with &lt;code&gt;hx-get&lt;/code&gt;–it works &lt;em&gt;only&lt;/em&gt; with regular and boosted links!&lt;/p&gt;

&lt;h2&gt;
  
  
  Boosted forms
&lt;/h2&gt;

&lt;p&gt;The other thing that automatically 'just works' with boosting is HTML forms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;post&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;/page1&lt;/span&gt; &lt;span class="na"&gt;hx-boost=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt; &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;#page&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;contents&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;submit&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Save Page&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When this form is submitted, htmx will automatically do the submission asynchronously using XHR and swap in the response. However, it will &lt;em&gt;not&lt;/em&gt; update the URL in the URL bar automatically, because that's not how normal form submits work. Again, like most things in htmx, you can also override this (with the &lt;a href="https://htmx.org/attributes/hx-push-url/" rel="noopener noreferrer"&gt;&lt;code&gt;hx-push-url&lt;/code&gt;&lt;/a&gt; attribute).&lt;/p&gt;

&lt;p&gt;Just like before, the server can figure out whether to respond with a fragment or a full page based on the request headers sent by htmx. For fragment responses, you just respond with an appropriate status code like 200 or 201 and the HTML fragment. For full page responses you respond with a 302 redirect as usual for the HTML form &lt;a href="https://en.wikipedia.org/wiki/Post/Redirect/Get" rel="noopener noreferrer"&gt;post-redirect-get&lt;/a&gt; pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accessibility
&lt;/h2&gt;

&lt;p&gt;As many have pointed out, certain patterns that are possible with htmx are not great for accessibility (and for that matter, SEO). Eg, look at this: &lt;code&gt;&amp;lt;div hx-get=/page1&amp;gt;Get Page1&amp;lt;/div&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Or this: &lt;code&gt;&amp;lt;a hx-get=/page1&amp;gt;Get Page1&amp;lt;/a&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;These are using htmx to essentially perform the function of &lt;code&gt;&amp;lt;a href=/page1&amp;gt;Get Page1&amp;lt;/a&amp;gt;&lt;/code&gt;. But the &lt;code&gt;&amp;lt;a href=...&amp;gt;...&amp;lt;/a&amp;gt;&lt;/code&gt; hyperlink is kind of well defined in HTML and many technologies, eg assistive technologies like screen readers, expect them to be structured in a certain way. Crucially, the &lt;code&gt;href&lt;/code&gt; attribute needs to be present to allow assistive tech (and SEO crawlers) to understand the link. When we reinvent these patterns, these tools don't work as well.&lt;/p&gt;

&lt;p&gt;Or look at this: &lt;code&gt;&amp;lt;a hx-post=/page1&amp;gt;Save Page&amp;lt;/a&amp;gt;&lt;/code&gt;. Or even: &lt;code&gt;&amp;lt;a hx-delete=/page1&amp;gt;Delete Page&amp;lt;/a&amp;gt;&lt;/code&gt;. I hope you can agree with me that the venerable anchor tag should not be repurposed to post or delete resources!&lt;/p&gt;

&lt;p&gt;On top of that, using buttons for data loads and navigations takes away the standard web affordance of opening links in background tabs, which can be a huge productivity drain.&lt;/p&gt;

&lt;p&gt;If we stick with boosted links and forms, we are making a commitment to adhere to established standards of web usability and accessibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application design
&lt;/h2&gt;

&lt;p&gt;Boosted links also have a big impact on the structure of your application. When most of the links will be shown in the URL bar at some point, you start carefully considering the set of URLs exposed by the app and the views that should be rendered when the user navigates to them. It becomes very important that URLs consistently render the same views on every visit. Eg,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/payment-methods&lt;/code&gt; should render a list of payment methods.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/payment-methods/new&lt;/code&gt; should render an 'Add payment method' form on the page.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/payment-methods/$id&lt;/code&gt; should render the payment method with the given ID,&lt;/li&gt;
&lt;li&gt;and so on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ability to copy and paste, and bookmark, and send URLs to refer to specific parts of your app is a superpower that should not be underestimated. It's the difference between being able to point your user directly at the entry form to add a payment method and having to ask them to click around in the app looking for buttons to press to find the correct page or dialog.&lt;/p&gt;

&lt;p&gt;Of course, this is nothing new in web application design; but a shocking number of SPAs nowadays don't follow these basic principles.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cult of hx-boost
&lt;/h2&gt;

&lt;p&gt;I don't believe in being dogmatic about this approach. There can be great reasons to use the &lt;code&gt;hx-&lt;/code&gt; + method attributes. Eg, I've found it useful to do something like &lt;code&gt;&amp;lt;tr hx-get=/foo hx-target=#detail-view style=cursor:pointer&amp;gt;&lt;/code&gt; in tables where I want the user to be able to easily select a row in a table and load some details. If JavaScript or htmx are not working, we can just have a &lt;code&gt;&amp;lt;td&amp;gt;&amp;lt;a href=/foo&amp;gt;Foo&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;&lt;/code&gt; cell in each row to gracefully provide only a slightly less convenient version of the user experience.&lt;/p&gt;

&lt;p&gt;A lot is possible in htmx, but a little goes a long way. Think about these tenets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you are requesting something, try using a boosted link (or a form with &lt;code&gt;method=get&lt;/code&gt; for a more complex query)&lt;/li&gt;
&lt;li&gt;If you are changing something, try using a boosted form&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These simple building blocks can provide a solid, accessible foundation for your app.&lt;/p&gt;

</description>
      <category>web</category>
      <category>htmx</category>
    </item>
    <item>
      <title>Easy parsing with reasonable error messages in OCaml's Angstrom</title>
      <dc:creator>Yawar Amin</dc:creator>
      <pubDate>Thu, 08 May 2025 22:00:40 +0000</pubDate>
      <link>https://dev.to/yawaramin/easy-parsing-with-reasonable-error-messages-in-ocamls-angstrom-g5f</link>
      <guid>https://dev.to/yawaramin/easy-parsing-with-reasonable-error-messages-in-ocamls-angstrom-g5f</guid>
      <description>&lt;p&gt;PARSER combinators are widely used in the world of functional programming, and OCaml's &lt;a href="https://github.com/inhabitedtype/angstrom" rel="noopener noreferrer"&gt;Angstrom&lt;/a&gt; library is one of them. It is used to implement many foundational parsers in the OCaml ecosystem, eg HTTP parsers for the &lt;a href="https://github.com/inhabitedtype/httpaf" rel="noopener noreferrer"&gt;httpaf&lt;/a&gt; stack.&lt;/p&gt;

&lt;p&gt;However, one of their bigger downsides is the lack of accurate parse error reporting. Let's take a look. Suppose you want to parse records of this format: &lt;code&gt;1 Bob&lt;/code&gt; ie an ID number followed by one or more spaces, followed by an alphabetic word (a name). Here's a basic Angstrom parser for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nc"&gt;Angstrom&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;skip_many1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="k"&gt;'&lt;/span&gt; &lt;span class="k"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;take_while1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="k"&gt;'&lt;/span&gt;&lt;span class="nn"&gt;A'&lt;/span&gt; &lt;span class="p"&gt;..&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nc"&gt;Z'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;'&lt;/span&gt;&lt;span class="n"&gt;a'&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="k"&gt;'&lt;/span&gt;&lt;span class="n"&gt;z'&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;true&lt;/span&gt; &lt;span class="o"&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="bp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;take_while1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="k"&gt;'&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="k"&gt;'&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="k"&gt;'&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="k"&gt;'&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;true&lt;/span&gt; &lt;span class="o"&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="bp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt;
  &lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;
  &lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;
  &lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;end_of_input&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;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;int_of_string&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's try out various bad inputs and check the errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# parse_string ~consume:Consume.All person "";;
- : (person, string) result = Error ": count_while1"

# parse_string ~consume:Consume.All person "1";;
- : (person, string) result = Error ": not enough input"

# parse_string ~consume:Consume.All person "1 ";;
- : (person, string) result = Error ": count_while1"

# parse_string ~consume:Consume.All person "1 1";;
- : (person, string) result = Error ": count_while1"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The error messages are not great, unfortunately! It's hard to tell what went wrong. Of course, in this case we know what caused each error because we are feeding small inputs to the parser. But it's easy to imagine that for larger inputs it may be difficult to understand why a parse is failing.&lt;/p&gt;

&lt;p&gt;Fortunately, parser combinator libraries usually provide a 'label' function to improve the error messages slightly. In Angstrom, a label works like this: &lt;code&gt;parser &amp;lt;?&amp;gt; "label string"&lt;/code&gt;. But the default label functionality allows labelling with only a static string. Let's improve labelling even more! Using a little-known feature of Angstrom, we can take a snapshot of the remaining string left to parse and actually include it in the error message if parsing fails.&lt;/p&gt;

&lt;p&gt;Here, we are just augmenting the built-in label operator with a more powerful, snapshotting version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;min&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;peek_string&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sprintf&lt;/span&gt; &lt;span class="s2"&gt;"%s, got: [%s]"&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's redefine our parsers to use this augmented labelling operator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;skip_many1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="k"&gt;'&lt;/span&gt; &lt;span class="k"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"expected one or more spaces"&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;take_while1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="k"&gt;'&lt;/span&gt;&lt;span class="nn"&gt;A'&lt;/span&gt; &lt;span class="p"&gt;..&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nc"&gt;Z'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;'&lt;/span&gt;&lt;span class="n"&gt;a'&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="k"&gt;'&lt;/span&gt;&lt;span class="n"&gt;z'&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;true&lt;/span&gt; &lt;span class="o"&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="bp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"expected a word"&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;take_while1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="k"&gt;'&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="k"&gt;'&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="k"&gt;'&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="k"&gt;'&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;true&lt;/span&gt; &lt;span class="o"&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="bp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"expected a number"&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"expected a numeric ID"&lt;/span&gt;
   &lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;
   &lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"expected a name"&lt;/span&gt;
   &lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;end_of_input&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"expected end of input"&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;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;int_of_string&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"expected a person"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's try the same error scenarios:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# parse_string ~consume:Consume.All person "";;
- : (person, string) result =
Error
 "expected a person, got: [] &amp;gt; expected a numeric ID, got: [] &amp;gt; expected a number, got: []: count_while1"

# parse_string ~consume:Consume.All person "1";;
- : (person, string) result =
Error
 "expected a person, got: [1] &amp;gt; expected one or more spaces, got: []: not enough input"

# parse_string ~consume:Consume.All person "1 ";;
- : (person, string) result =
Error
 "expected a person, got: [1 ] &amp;gt; expected a name, got: [] &amp;gt; expected a word, got: []: count_while1"

# parse_string ~consume:Consume.All person "1 b1";;
- : (person, string) result =
Error
 "expected a person, got: [1 b1] &amp;gt; expected end of input, got: [1]: end_of_input"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The text in the brackets shows a snapshot of the string remaining to parse, which narrows down at each level to the exact string where the parse failed! With this snapshot of the remaining string, we can easily figure out where the parse failed.&lt;/p&gt;

&lt;p&gt;Happy parsing!&lt;/p&gt;

</description>
      <category>ocaml</category>
    </item>
    <item>
      <title>You're thinking about passkeys wrong</title>
      <dc:creator>Yawar Amin</dc:creator>
      <pubDate>Wed, 29 Jan 2025 02:37:19 +0000</pubDate>
      <link>https://dev.to/yawaramin/youre-thinking-about-passkeys-wrong-171o</link>
      <guid>https://dev.to/yawaramin/youre-thinking-about-passkeys-wrong-171o</guid>
      <description>&lt;p&gt;PASSKEYS have been in the &lt;a href="https://www.youtube.com/watch?v=lf5L_1oqHDs" rel="noopener noreferrer"&gt;news&lt;/a&gt; recently and with good reason–the big tech companies are seriously ramping up efforts to move all their users to this newfangled tech.&lt;/p&gt;

&lt;p&gt;But there's a fly in the ointment: certain influential tech people &lt;a href="https://world.hey.com/dhh/passwords-have-problems-but-passkeys-have-more-95285df9" rel="noopener noreferrer"&gt;really dislike&lt;/a&gt; them. According to DHH:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;if you sign up for a service on Windows, and you then want to access it on iPhone, you're going to be stuck (unless you're so forward thinking as to add a second passkey, somehow, from the iPhone will on the Windows computer!)...A decent alternative to passkeys, if you need the extra layer of security, is to lean on email for the first login from a new device. Treating email as a second factor, but only on the first login from that device. Everyone has email, everyone understands email. (Just don't force us all to go through magic links exclusively, as that's a pain too for those who've actually adopted a password manager!).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;DHH is &lt;em&gt;very&lt;/em&gt; close to the ideal UX for passkeys for ordinary non-technical folks, but can't quite put it together. Here's what I recommend:&lt;/p&gt;

&lt;h2&gt;
  
  
  Initial user sign-up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Don't make the user set up a password!&lt;/li&gt;
&lt;li&gt;Send a magic link to their email&lt;/li&gt;
&lt;li&gt;Once they click and enter the webapp, they are in a logged-in state. Prominently show a passkey setup CTA and ask the user to set up a passkey on the device.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Subsequent logins on the same device
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;a href="https://developer.chrome.com/docs/identity/webauthn-conditional-ui" rel="noopener noreferrer"&gt;Conditional UI&lt;/a&gt; to allow selecting the passkey directly from the Email input autocomplete. Browsers support this now!&lt;/li&gt;
&lt;li&gt;If JavaScript disabled: instead of Conditional UI and passkey login, send a magic link&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Subsequent logins on a &lt;em&gt;new&lt;/em&gt; device
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Send a magic link to the user's email address&lt;/li&gt;
&lt;li&gt;Once they click and enter the webapp, they are in a logged-in state. Prominently show a passkey setup CTA and ask the user to set up a passkey on the device.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice how this is almost exactly the same as the initial user sign up process? It's so simple and low-ceremony, it should hardly take the user out of their flow.&lt;/p&gt;

&lt;p&gt;It doesn't need to be a whole song-and-dance about having to 'recover' the account on the new device! There's nothing to recover. The user can just log in with a magic link and set up a passkey so that the next login is even easier. That's all!&lt;/p&gt;

&lt;p&gt;Obviously, for this UX to work, the app &lt;em&gt;must&lt;/em&gt; support being able to store multiple passkeys per account. This is non-negotiable! Passkeys are cheap and easy to store. There's no reason to be overly restrictive here. Once this is supported, you never have to worry about password leaks again. And your users never have to worry about getting phished (in the sense of accidentally entering their credentials into third-party sites).&lt;/p&gt;

&lt;p&gt;Prominent security experts &lt;a href="https://bsky.app/profile/filippo.abyssdomain.expert/post/3lerf6hxg7c2s" rel="noopener noreferrer"&gt;agree&lt;/a&gt; - passkeys and magic links basically solve the password problem for regular users. We can effectively retire passwords now.&lt;/p&gt;

</description>
      <category>auth</category>
      <category>web</category>
    </item>
    <item>
      <title>Powerful form validation with OCaml's Dream framework</title>
      <dc:creator>Yawar Amin</dc:creator>
      <pubDate>Sun, 01 Dec 2024 17:24:21 +0000</pubDate>
      <link>https://dev.to/yawaramin/powerful-form-validation-with-ocamls-dream-framework-4ggj</link>
      <guid>https://dev.to/yawaramin/powerful-form-validation-with-ocamls-dream-framework-4ggj</guid>
      <description>&lt;p&gt;I'VE been a long-time proponent of the power of HTML forms and how natural they make it to build web pages that allow users to input data. Recently, I got a chance to refurbish an old internal application we use at work using &lt;a href="https://htmx.org/" rel="noopener noreferrer"&gt;htmx&lt;/a&gt; and Scala's Play Framework. In this app we often use HTML forms to submit information. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;new-user-form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;post&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;/users&lt;/span&gt; &lt;span class="na"&gt;hx-post=&lt;/span&gt;&lt;span class="s"&gt;/users&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;checkbox&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;accept-terms&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;submit&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Add User&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Play has &lt;a href="https://www.playframework.com/documentation/3.0.x/ScalaForms" rel="noopener noreferrer"&gt;great support&lt;/a&gt; for decoding submitted HTML form data into values of custom types and reporting all validation errors that may have occurred, which allowed me to render the errors directly alongside the forms themselves using the Constraint Validation API. I wrote about that in a &lt;a href="https://dev.to/yawaramin/handling-form-errors-in-htmx-3ncg"&gt;previous post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But in the OCaml world, the situation was not that advanced. We had some basic tools for parsing form data into a key-value pair list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ocaml.org/p/uri/4.2.0/doc/Uri/index.html#val-query_of_encoded" rel="noopener noreferrer"&gt;Uri.query_of_encoded&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ocaml.org/p/dream/latest/doc/Dream/index.html#val-form" rel="noopener noreferrer"&gt;Dream.form&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But if we wanted to decode this list into a custom type, we'd need something more sophisticated, like maybe &lt;a href="https://ocaml.org/p/conformist/latest/doc/Conformist/index.html" rel="noopener noreferrer"&gt;conformist&lt;/a&gt;. &lt;del&gt;However, conformist has the issue that it reports only one error at a time.&lt;/del&gt; Actually, conformist has a separate &lt;code&gt;validate&lt;/code&gt; function that can report all errors together!&lt;/p&gt;

&lt;p&gt;If we have to decode a form submission like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;accept-terms: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We would want to see a &lt;em&gt;list&lt;/em&gt; of validation errors, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: required
email: required
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  dream-html form validation
&lt;/h2&gt;

&lt;p&gt;Long story short, I decided we needed a more ergonomic form validation experience in OCaml. And since I already maintain a &lt;a href="https://ocaml.org/p/dream-html" rel="noopener noreferrer"&gt;package&lt;/a&gt; which adds type-safe helpers on top of the Dream web framework, I thought it would make a good addition. Let's take a it for a spin in the REPL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ utop -require dream-html
# type add_user = {
    name : string;
    email : string;
    accept_terms : bool;
  };;

# let add_user =
    let open Dream_html.Form in
    let+ name = required string "name"
    and+ email = required string "email"
    and+ accept_terms = optional bool "accept-terms" in
    {
      name;
      email;
      accept_terms = Option.value accept_terms ~default:false;
    };;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have a value &lt;code&gt;add_user : add_user Dream_html.Form.t&lt;/code&gt;, which is a decoder to our custom type. Let's try it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Dream_html.Form.validate add_user [];;
- : (add_user, (string * string) list) result =
Error [("email", "error.required"); ("name", "error.required")]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We get back a list of form validation errors, with field names and error message keys (this allows localizing the app).&lt;/p&gt;

&lt;p&gt;Let's try a successful decode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Dream_html.Form.validate add_user ["name", "Bob"; "email", "bob@example.com"];;
- : (add_user, (string * string) list) result =
Ok {name = "Bob"; email = "bob@example.com"; accept_terms = false}

# Dream_html.Form.validate add_user ["name", "Bob"; "email", "bob@example.com"; "accept-terms", "true"];;
- : (add_user, (string * string) list) result =
Ok {name = "Bob"; email = "bob@example.com"; accept_terms = true}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's check for a type error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Dream_html.Form.validate add_user ["name", "Bob"; "email", "bob@example.com"; "accept-terms", "1"];;
- : (add_user, (string * string) list) result =
Error [("accept-terms", "error.expected.bool")]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It wants a &lt;code&gt;bool&lt;/code&gt;, ie only &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt; values. You can make sure your checkboxes always send &lt;code&gt;true&lt;/code&gt; on submission by setting &lt;code&gt;value=true&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom value decoders
&lt;/h2&gt;

&lt;p&gt;You can decode custom data too. Eg suppose your form has inputs that are supposed to be &lt;a href="https://ocaml.org/p/decimal/latest/doc/Decimal/index.html" rel="noopener noreferrer"&gt;decimal numbers&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;height-m&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can write a custom data decoder that can parse a decimal number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"decimal"&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="nc"&gt;Ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;of_string&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Invalid_argument&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="s2"&gt;"error.expected.decimal"&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can use it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;height_m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt; &lt;span class="s2"&gt;"height-m"&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding constraints to the values
&lt;/h2&gt;

&lt;p&gt;You can add further constraints to values that you decode. Eg, in most form submissions it doesn't make sense for any strings to be empty. So let's define a helper that constrains strings to be non-empty:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;nonempty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="n"&gt;ensure&lt;/span&gt; &lt;span class="s2"&gt;"expected.nonempty"&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can write the earlier form definition with stronger constraints for the strings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;add_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Dream_html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Form&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nonempty&lt;/span&gt; &lt;span class="s2"&gt;"name"&lt;/span&gt;
  &lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nonempty&lt;/span&gt; &lt;span class="s2"&gt;"email"&lt;/span&gt;
  &lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;accept_terms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;optional&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="s2"&gt;"accept-terms"&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;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;accept_terms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="n"&gt;accept_terms&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="bp"&gt;false&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;h2&gt;
  
  
  Validating forms in Dream handlers
&lt;/h2&gt;

&lt;p&gt;In a Dream application, the built-in form handling would look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="c"&gt;(* POST /users *)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;post_users&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;lwt&lt;/span&gt; &lt;span class="nn"&gt;Dream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nt"&gt;`Ok&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"accept-terms"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;accept_terms&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;(* ...success... *)&lt;/span&gt;
  &lt;span class="o"&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="nn"&gt;Dream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt; &lt;span class="nt"&gt;`Bad_Request&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But with our form validation abilities, we can do something more:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="c"&gt;(* POST /users *)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;post_users&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;lwt&lt;/span&gt; &lt;span class="nn"&gt;Dream_html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="n"&gt;add_user&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nt"&gt;`Ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;accept_terms&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;(* ...success... *)&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nt"&gt;`Invalid&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nn"&gt;Dream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&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="mi"&gt;422&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="c"&gt;(* ...turn the error list into a JSON object... *)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&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="nn"&gt;Dream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt; &lt;span class="nt"&gt;`Bad_Request&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Decoding variant type values
&lt;/h2&gt;

&lt;p&gt;Of course, variant types are a big part of programming in OCaml, so you might want to decode a form submission into a value of a variant type. Eg,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Logged_out&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Logged_in&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could have a form submission that looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type: logged-out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type: logged-in
admin: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Etc.&lt;/p&gt;

&lt;p&gt;To decode this kind of submission, you can break it down into decoders for each case, then join them together with &lt;code&gt;Dream_html.Form.( or )&lt;/code&gt;, eg:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;logged_out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ensure&lt;/span&gt; &lt;span class="s2"&gt;"expected.type"&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="s2"&gt;"logged-out"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"type"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="nc"&gt;Logged_out&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;logged_in&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ensure&lt;/span&gt; &lt;span class="s2"&gt;"expected.type"&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="s2"&gt;"logged-in"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"type"&lt;/span&gt;
  &lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="s2"&gt;"admin"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="nc"&gt;Logged_in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logged_out&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;logged_in&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's try it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# validate user [];;
- : (user, (string * string) list) result =
Error [("admin", "error.required"); ("type", "error.required")]

# validate user ["type", "logged-out"];;
- : (user, (string * string) list) result = Ok Logged_out

# validate user ["type", "logged-in"];;
- : (user, (string * string) list) result = Error [("admin", "error.required")]

# validate user ["type", "logged-in"; "admin", ""];;
- : (user, (string * string) list) result =
Error [("admin", "error.expected.bool")]

# validate user ["type", "logged-in"; "admin", "true"];;
- : (user, (string * string) list) result = Ok (Logged_in { admin = true })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the decoder can handle either case and all the requirements therein.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decoding queries into custom types
&lt;/h2&gt;

&lt;p&gt;The decoding functionality works not just with 'forms' but also with queries eg &lt;code&gt;/foo?a=1&amp;amp;b=2&lt;/code&gt;. Of course, here we are using 'forms' as a shorthand for the &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt; data that is submitted with a POST request, but actually an HTML form that has &lt;code&gt;action=get&lt;/code&gt; submits its input data as a &lt;em&gt;query,&lt;/em&gt; part of the URL, not as form data. A little confusing, but the key thing to remember is that Dream can work with both, and so can dream-html.&lt;/p&gt;

&lt;p&gt;In Dream, you can get query data using functions like &lt;code&gt;let a = Dream.query request "a"&lt;/code&gt;. But if you are submitting more sophisticated data via the query, you can decode them into a custom type using the above form decoding functionality. Eg suppose you want to decode &lt;a href="https://en.wikipedia.org/wiki/UTM_parameters#Parameters" rel="noopener noreferrer"&gt;UTM parameters&lt;/a&gt; into a custom type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;utm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;medium&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;campaign&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;term&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;utm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;optional&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"utm_source"&lt;/span&gt;
  &lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;medium&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;optional&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"utm_medium"&lt;/span&gt;
  &lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;campaign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;optional&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"utm_campaign"&lt;/span&gt;
  &lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;optional&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"utm_term"&lt;/span&gt;
  &lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;optional&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"utm_content"&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;source&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;medium&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;campaign&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, you can use this very similarly to a POST form submission:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;some_page&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="nn"&gt;Dream_html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="n"&gt;utm&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nt"&gt;`Ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;medium&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;campaign&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;(* ...success... *)&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nt"&gt;`Invalid&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;(* ...handle errors... *)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the cool thing is, since they are literally the same form definition, you can switch back and forth between making your request handle POST form data or GET query parameters, with very few changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  So...
&lt;/h2&gt;

&lt;p&gt;Whew. That was a lot. And I didn't really dig into the even more advanced use cases. But hopefully at this point you might be convinced that forms and queries are now easy to handle in Dream. Of course, you might not really need all this power. For simple use cases, you can probably get away with Dream's built-in capabilities. But for larger apps that maybe need to handle a lot of forms, I think it can be useful.&lt;/p&gt;

</description>
      <category>ocaml</category>
      <category>html</category>
      <category>web</category>
    </item>
    <item>
      <title>Handling form errors in htmx</title>
      <dc:creator>Yawar Amin</dc:creator>
      <pubDate>Fri, 11 Oct 2024 05:41:59 +0000</pubDate>
      <link>https://dev.to/yawaramin/handling-form-errors-in-htmx-3ncg</link>
      <guid>https://dev.to/yawaramin/handling-form-errors-in-htmx-3ncg</guid>
      <description>&lt;p&gt;FROM time to time I hear a criticism of htmx that it's not good at handling errors. I'll show an example of why I don't think that's the case. One of the common operations with htmx is submitting an HTML form (of the type &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt;) to your backend server and getting a response. The happy path of course is when the response is a success and htmx does the HTML fragment swap. But let's look at the sad path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Form validation messaging
&lt;/h2&gt;

&lt;p&gt;A common UX need is to show an error message next to each field that failed to validate. Look at this example from the Bulma CSS framework documentation: &lt;a href="https://bulma.io/documentation/form/general/#complete-form-example" rel="noopener noreferrer"&gt;https://bulma.io/documentation/form/general/#complete-form-example&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%2Fudcpnq10pn7f3z199sjv.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%2Fudcpnq10pn7f3z199sjv.png" alt="Bulma CSS framework example showing a form with an error message" width="670" height="824"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That does look nice...but it also requires custom markup and layout for potentially every field. What if we take advantage of modern browser support for the HTML &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation#the_constraint_validation_api" rel="noopener noreferrer"&gt;Constraint Validation API&lt;/a&gt;? This allows us to attach an error message to each field with its own pop-up that lives &lt;em&gt;outside&lt;/em&gt; the document's markup. You can see an example here: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/reportValidity#results" rel="noopener noreferrer"&gt;https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/reportValidity#results&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%2F7x458jjkazdm6ksmn5f0.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%2F7x458jjkazdm6ksmn5f0.png" alt="Mozilla Developer Network showing a form with an error message using the Constraint Validation API" width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What if we had a message like this pop up for &lt;em&gt;every&lt;/em&gt; field that failed validation? This is the question of this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example form
&lt;/h2&gt;

&lt;p&gt;Suppose you have an endpoint &lt;code&gt;POST /users&lt;/code&gt; which handles a form with the payload &lt;code&gt;fullname=Foo&amp;amp;email=bar@baz.com&lt;/code&gt;. You get the data in the backend, decode it, and if successful you are on the happy path as mentioned earlier. But if the form decode fails, we come to the interesting bit.&lt;/p&gt;

&lt;p&gt;Here's the key point: if the form decode fails, you need some way to let htmx know about this specific error as opposed to some other error that could have happened. We need to make a decision here. Let's say we use the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422" rel="noopener noreferrer"&gt;422 Unprocessable Content&lt;/a&gt; status code for a form which fails validation.&lt;/p&gt;

&lt;p&gt;Now, we need to decide how exactly to format the validation error message. The Constraint Validation API mentioned earlier is a JavaScript API, so that pretty much makes the decision for us. We will format the errors as JSON.&lt;/p&gt;

&lt;p&gt;Here's an example form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt;
  &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;add-user-form&lt;/span&gt;
  &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;post&lt;/span&gt;
  &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;/users&lt;/span&gt;
  &lt;span class="na"&gt;hx-post=&lt;/span&gt;&lt;span class="s"&gt;/users&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;fullname&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;submit&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Add User"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, in a real app both these inputs would have the &lt;code&gt;required&lt;/code&gt; attribute; here I am just leaving them out for demonstration purposes.&lt;/p&gt;

&lt;p&gt;If we submit this form with the &lt;code&gt;fullname&lt;/code&gt; and &lt;code&gt;email&lt;/code&gt; fields left empty, then the backend should fail to validate the form and respond with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP 422
Content-Type: application/json

{
  "fullname": "Please fill out this field",
  "email": "Please fill out this field"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How do we make this happen? Our form validation function should tell us the names of the fields that failed to validate and the error message for each. We just convert this mapping into a JSON object.&lt;/p&gt;

&lt;h2&gt;
  
  
  The error handler
&lt;/h2&gt;

&lt;p&gt;With this response from the backend, we need some JavaScript to traverse the JSON and attach the error messages to each corresponding form field.&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;htmx:responseError&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;evt&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;xhr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;422&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;responseText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[name="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"]`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setCustomValidity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
      &lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onfocus&lt;/span&gt; &lt;span class="o"&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="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reportValidity&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onchange&lt;/span&gt; &lt;span class="o"&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="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setCustomValidity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reportValidity&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Handle the error some other way&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;responseText&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;We are doing three key things here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;For each form field that failed validation, attach the error message to it&lt;/li&gt;
&lt;li&gt;Attach an event listener to pop up the error message when the field gets focus&lt;/li&gt;
&lt;li&gt;Attach an event listener to clear out the error message when the field's value is changed&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fourth action above, while not critical, is a nice to have: we just tell one of the fields to make it pop up its error message. This shows the user that something went wrong with the form submission. Of course, you can give even bigger hints, like highlighting inputs in an invalid state with CSS by targeting the &lt;code&gt;input:invalid&lt;/code&gt; pseudo-selector.&lt;/p&gt;

&lt;p&gt;Now, any time the form is submitted and there is a validation error, the response will automatically populate the error messages to the right places.&lt;/p&gt;

&lt;p&gt;Note: we are using the &lt;code&gt;field.onevent = ...&lt;/code&gt; properties for setting the event handlers so that we don't end up with duplicate event listeners added to each field.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not htmx?
&lt;/h2&gt;

&lt;p&gt;If you have been paying close attention, you may be thinking that this technique seems to be not limited to htmx–and you're right! This technique based on the Constraint Validation API can be used with any frontend which uses forms. It doesn't need to be used specifically with htmx. You just need to adapt it to handle a form validation error from the backend server.&lt;/p&gt;

&lt;p&gt;By taking advantage of a built-in feature of modern browsers, we make the code more adaptable and benefit from future improvements that browsers make to their UIs.&lt;/p&gt;

</description>
      <category>htmx</category>
      <category>javascript</category>
    </item>
    <item>
      <title>A type theory mnemonic for boolean operator precedence</title>
      <dc:creator>Yawar Amin</dc:creator>
      <pubDate>Mon, 16 Sep 2024 01:11:25 +0000</pubDate>
      <link>https://dev.to/yawaramin/a-type-theory-mnemonic-for-boolean-operator-precedence-144c</link>
      <guid>https://dev.to/yawaramin/a-type-theory-mnemonic-for-boolean-operator-precedence-144c</guid>
      <description>&lt;p&gt;IN COMPUTER languages which support boolean operators like &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; (&lt;code&gt;and&lt;/code&gt;) and &lt;code&gt;||&lt;/code&gt; (&lt;code&gt;or&lt;/code&gt;), &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; typically has higher precedence than &lt;code&gt;||&lt;/code&gt;. In other words, the following happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p &amp;amp;&amp;amp; q || r == (p &amp;amp;&amp;amp; q) || r
p || q &amp;amp;&amp;amp; r == p || (q &amp;amp;&amp;amp; r)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can take advantage of this to construct simpler expressions which don't need so many parentheses. Eg, let's look at &lt;a href="https://developers.cloudflare.com/ruleset-engine/rules-language/operators/#logical-operators" rel="noopener noreferrer"&gt;Cloudflare's expression language&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;http.host eq "app.foo.com" and http.request.uri.path eq "/api" or http.host eq "api.foo.com" and http.request.uri.path eq "/app"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above is equivalent to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(http.host eq "app.foo.com" and http.request.uri.path eq "/api") or (http.host eq "api.foo.com" and http.request.uri.path eq "/app")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's leave aside for a moment the argument that you &lt;em&gt;should&lt;/em&gt; use parentheses for explicit grouping and readability; readability is a very subjective question. The bigger problem might be that, unless you regularly design or use such logical expressions, it might be difficult to &lt;em&gt;remember&lt;/em&gt; this operator precedence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Math operator precedence
&lt;/h2&gt;

&lt;p&gt;However, one precedence rule that most people remember pretty easily is that of math operators like &lt;code&gt;+&lt;/code&gt; and &lt;code&gt;*&lt;/code&gt; (yes, I'm using the programming language symbol &lt;code&gt;*&lt;/code&gt; instead of the math symbol ✖️). We of course know,  probably since childhood, that multiplication has higher precedence than addition. So,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a * b + c == (a * b) + c
a + b * c == a + (b * c)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look familiar? These are the same precedence rules as for the logical operators shown above, where:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;amp;&amp;amp; is equivalent to *
|| is equivalent to +
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The type theory connection
&lt;/h2&gt;

&lt;p&gt;Of course, type theory enthusiasts know pretty well that the symbols &lt;code&gt;+&lt;/code&gt; and &lt;code&gt;*&lt;/code&gt; are used to represent &lt;em&gt;sum types&lt;/em&gt; and &lt;em&gt;product types.&lt;/em&gt; The name is no coincidence: a sum type like &lt;code&gt;a + b&lt;/code&gt; has the same number of values as the &lt;em&gt;sum&lt;/em&gt; of the numbers of values of &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt;. And a product type like &lt;code&gt;a * b&lt;/code&gt; has the same number of values as the &lt;em&gt;product&lt;/em&gt; of the numbers of values of &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Try working through an example where &lt;code&gt;type a = bool&lt;/code&gt; (2 possible values) and &lt;code&gt;type b = unit&lt;/code&gt; (1 possible value). How many values does the type &lt;code&gt;a + b&lt;/code&gt; have, and how many does &lt;code&gt;a * b&lt;/code&gt; have?&lt;/p&gt;

&lt;p&gt;In type theory, sum types represent &lt;em&gt;alternatives,&lt;/em&gt; so &lt;code&gt;a + b&lt;/code&gt; means 'a value that can be &lt;code&gt;a&lt;/code&gt; &lt;em&gt;or&lt;/em&gt; &lt;code&gt;b&lt;/code&gt;', while &lt;code&gt;a * b&lt;/code&gt; means 'a value that contains &lt;code&gt;a&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; &lt;code&gt;b&lt;/code&gt;' (commonly known as a tuple or pair). So in the type theory world,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* == &amp;amp;&amp;amp;
+ == ||
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And very conveniently, they have the same precedence rules! So, if you are ever trying to remember the precedence, just remind yourself that '&lt;em&gt;and&lt;/em&gt; is &lt;em&gt;times,&lt;/em&gt; &lt;em&gt;or&lt;/em&gt; is &lt;em&gt;plus&lt;/em&gt;'.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Constructing XML output with dream-html</title>
      <dc:creator>Yawar Amin</dc:creator>
      <pubDate>Sun, 14 Jul 2024 21:53:00 +0000</pubDate>
      <link>https://dev.to/yawaramin/constructing-xml-output-with-dream-html-1pgb</link>
      <guid>https://dev.to/yawaramin/constructing-xml-output-with-dream-html-1pgb</guid>
      <description>&lt;p&gt;FOR some time now, I have been maintaining an OCaml library called &lt;a href="https://github.com/yawaramin/dream-html" rel="noopener noreferrer"&gt;dream-html&lt;/a&gt;. This library is primarily intended to render correctly-constructed HTML, SVG, and MathML. Recently, I added the ability to render well-formed XML markup, which has slightly different rules than HTML. For example, in HTML if you want to write an empty &lt;code&gt;div&lt;/code&gt; tag, you do: &lt;code&gt;&amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;. But according to the rules of XML, you could &lt;em&gt;also&lt;/em&gt; write &lt;code&gt;&amp;lt;div/&amp;gt;&lt;/code&gt; ie a self-closing tag, however HTML 5 does not have the concept of self-closing tags!&lt;/p&gt;

&lt;p&gt;So by having the library take care of these subtle but crucial details, you can just concentrate on writing code that generates the markup. Of course, this has many other advantages too, but in this post I will just look at XML.&lt;/p&gt;

&lt;p&gt;It turns out that often we need to serialize some data into XML format, for storage or communication purposes. There are a few packages in the OCaml ecosystem which handle XML, however I think dream-html actually does it surprisingly well now. Let's take a look.&lt;/p&gt;

&lt;p&gt;But first, a small clarification about the dream-html package itself. Recently I split it up into &lt;em&gt;two&lt;/em&gt; packages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;pure-html&lt;/code&gt; has all the functionality needed to write valid HTML and XML&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dream-html&lt;/code&gt; has all of the above, plus some integration with the &lt;a href="https://aantron.github.io/dream" rel="noopener noreferrer"&gt;Dream&lt;/a&gt; web framework for ease of use.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As you might imagine, the reason for the split was to allow using the HTML/XML functionality of the package without having to pull in the entire Dream dependency cone, which is quite large, especially if you happen to be using a different dependency cone as well. So &lt;code&gt;pure-html&lt;/code&gt; depends only on the &lt;code&gt;uri&lt;/code&gt; package to help construct correct URI strings.&lt;/p&gt;

&lt;p&gt;To start using it, just install: &lt;code&gt;opam install pure-html&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And add to your &lt;code&gt;dune&lt;/code&gt; file: &lt;code&gt;(libraries pure-html)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now, let's look at an example of how you can use it to construct XML. Suppose you have the following type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;person&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&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;And you need to serialize it to XML like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;person&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Bob"&lt;/span&gt; &lt;span class="na"&gt;email=&lt;/span&gt;&lt;span class="s"&gt;"bob@info.com"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's write a serializer using the &lt;code&gt;pure-html&lt;/code&gt; package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nc"&gt;Pure_html&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;person_xml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;std_tag&lt;/span&gt; &lt;span class="s2"&gt;"person"&lt;/span&gt;
  &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;string_attr&lt;/span&gt; &lt;span class="s2"&gt;"name"&lt;/span&gt;
  &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;string_attr&lt;/span&gt; &lt;span class="s2"&gt;"email"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="k"&gt;fun&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="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="s2"&gt;"%s"&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="s2"&gt;"%s"&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's test it out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ utop -require pure-html
# open Pure_html;;
# let pp = pp_xml ~header:true;;
val pp : Format.formatter -&amp;gt; node -&amp;gt; unit = &amp;lt;fun&amp;gt;
# #install_printer pp;;
# type person = {
  name : string;
  email : string;
};;
type person = { name : string; email : string; }
# let person_xml =
  let person = std_tag "person"
  and name = string_attr "name"
  and email = string_attr "email" in
  fun { name = n; email = e } -&amp;gt; person [name "%s" n; email "%s" e] [];;
val person_xml : person -&amp;gt; node = &amp;lt;fun&amp;gt;
# person_xml { name = "Bob"; email = "bob@example.com" };;
- : node =
&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;
&amp;lt;person
name="Bob"
email="bob@example.com" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OK cool, so our &lt;code&gt;person&lt;/code&gt; record is serialized in this specific way. But, what if we need to serialize it like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;person&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;name&amp;gt;&lt;/span&gt;Bob&lt;span class="nt"&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;email&amp;gt;&lt;/span&gt;bob@example.com&lt;span class="nt"&gt;&amp;lt;/email&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/person&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After all, this is a common way of formatting records in XML. Let's write the serializer in this style:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;person_xml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;std_tag&lt;/span&gt; &lt;span class="s2"&gt;"person"&lt;/span&gt;
  &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;std_tag&lt;/span&gt; &lt;span class="s2"&gt;"name"&lt;/span&gt;
  &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;std_tag&lt;/span&gt; &lt;span class="s2"&gt;"email"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="k"&gt;fun&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="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt; &lt;span class="s2"&gt;"%s"&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt; &lt;span class="s2"&gt;"%s"&lt;/span&gt; &lt;span class="n"&gt;e&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;Let's try it out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# let person_xml =
  let person = std_tag "person"
  and name = std_tag "name"
  and email = std_tag "email" in
  fun { name = n; email = e } -&amp;gt;
    person [] [
      name [] [txt "%s" n];
      email [] [txt "%s" e];
    ];;
val person_xml : person -&amp;gt; node = &amp;lt;fun&amp;gt;
# person_xml { name = "Bob"; email = "bob@example.com" };;
- : node =
&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;
&amp;lt;person&amp;gt;&amp;lt;name&amp;gt;Bob&amp;lt;/name&amp;gt;&amp;lt;email&amp;gt;bob@example.com&amp;lt;/email&amp;gt;&amp;lt;/person&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks good! Let's examine the functions from the &lt;code&gt;pure-html&lt;/code&gt; package used here to achieve this.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;std_tag&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This function lets us define a custom tag: &lt;code&gt;let person = std_tag "person"&lt;/code&gt;. Note that it's trivial to add a namespace: &lt;code&gt;let person = std_tag "my:person"&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;string_attr&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This allows us to define a custom attribute which takes a &lt;em&gt;string&lt;/em&gt; payload: &lt;code&gt;let name = string_attr "name"&lt;/code&gt;. Again, easy to add a namespace: &lt;code&gt;let name = string_attr "my:name"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There are other attribute definition functions which allow &lt;code&gt;int&lt;/code&gt; payloads and so on. See the package documentation for details.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;pp_xml&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This allows us to define a &lt;a href="https://dev.to/yawaramin/how-to-print-anything-in-ocaml-1hkl"&gt;printer&lt;/a&gt; which renders XML correctly according to its syntactic rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;pp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pp_xml&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="bp"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The optional &lt;code&gt;header&lt;/code&gt; argument lets us specify whether we want to always print the XML header or not. In many serialization cases, we do.&lt;/p&gt;

&lt;p&gt;There's also a similar function which, instead of defining a &lt;em&gt;printer,&lt;/em&gt; just &lt;em&gt;converts&lt;/em&gt; the constructed node into a string directly: &lt;code&gt;to_xml&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;With these basic functions, it's possible to &lt;em&gt;precisely&lt;/em&gt; control how the serialized XML looks. Note that &lt;code&gt;dream-html&lt;/code&gt; and &lt;code&gt;pure-html&lt;/code&gt; support only &lt;em&gt;serialization&lt;/em&gt; of data into XML format, and not &lt;em&gt;deserialization&lt;/em&gt; ie &lt;em&gt;parsing&lt;/em&gt; XML. For that, there are &lt;a href="https://ocaml.org/cookbook/xml-parse/ezxmlm" rel="noopener noreferrer"&gt;other packages&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>ocaml</category>
      <category>xml</category>
    </item>
    <item>
      <title>Bare-bones unit testing in OCaml with dune</title>
      <dc:creator>Yawar Amin</dc:creator>
      <pubDate>Mon, 01 Jul 2024 01:30:17 +0000</pubDate>
      <link>https://dev.to/yawaramin/bare-bones-unit-testing-in-ocaml-with-dune-1lkb</link>
      <guid>https://dev.to/yawaramin/bare-bones-unit-testing-in-ocaml-with-dune-1lkb</guid>
      <description>&lt;p&gt;THERE are various techniques and tools to do unit testing in OCaml. A small selection:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/mirage/alcotest/" rel="noopener noreferrer"&gt;Alcotest&lt;/a&gt; - a colourful unit testing framework&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/gildor478/ounit?tab=readme-ov-file" rel="noopener noreferrer"&gt;OUnit2&lt;/a&gt; - an xUnit-style test framework&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/janestreet/ppx_expect" rel="noopener noreferrer"&gt;ppx_expect&lt;/a&gt; - a snapshot testing framework&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/stroiman/introducing-speed-2ofk"&gt;Speed&lt;/a&gt;, a new framework announced right here on dev.to, with an emphasis on a fast feedback loop.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While these have various benefits, it is undeniable that they all involve using a third-party library to write the tests, learning the various assertion functions and their helpers, and learning how to read and deal with test failure outputs. I have lately been wondering if we can simplify and distill this process to its very essence.&lt;/p&gt;

&lt;p&gt;When you run a unit test, you have some expected output, some 'actual' output from the system under test, and then you compare the two. If they are the same, then the test passes, if they are different, the test fails. Ideally, you get the test failure report as an easily readable diff so you can see &lt;em&gt;exactly&lt;/em&gt; what went wrong. Of course, this is a simplified view of unit testing–there are tests that require more sophisticated checks–but for many cases, this simple approach is often 'good enough'.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter dune
&lt;/h2&gt;

&lt;p&gt;And here is where dune, OCaml's build system, comes in. It turns out that dune ships out of the box with a &lt;a href="https://dune.readthedocs.io/en/stable/concepts/promotion.html" rel="noopener noreferrer"&gt;'diff-and-promote'&lt;/a&gt; workflow. You can tell it to diff two files, running silently if they have the same content, or failing and printing out a diff if they don't. Then you can run a simple &lt;code&gt;dune promote&lt;/code&gt; command to update the 'expected' or 'snapshot' file with the 'actual' content.&lt;/p&gt;

&lt;p&gt;Let's look at an example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example project
&lt;/h2&gt;

&lt;p&gt;Let's set up a tiny example project to test out this workflow. Here are the files:&lt;/p&gt;

&lt;h3&gt;
  
  
  dune-project
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(lang dune 3.0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file is needed for dune to recognize a project. You can use any supported version of dune here, I just default to 3.0.&lt;/p&gt;

&lt;h3&gt;
  
  
  lib/dune
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(library
 (name lib))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This declares a dune library inside the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  lib/lib.ml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the implementation source code of the library. Here we are just setting up two dummy functions that we will 'test' for demonstration purposes. Of course in real-world code there will be more complex functions.&lt;/p&gt;

&lt;h3&gt;
  
  
  test/test.expected
&lt;/h3&gt;

&lt;p&gt;(This file is initally left empty–will be filled later.)&lt;/p&gt;

&lt;h3&gt;
  
  
  test/test.ml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%s: %d&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nc"&gt;Lib&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"add 1 1"&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"sub 1 1"&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file defines a &lt;code&gt;test&lt;/code&gt; helper function whose only job is to just print out a message and then the result of the test, together, to standard output. Then we use the helper repeatedly to test various scenarios. This has the effect that we just print out a bunch of things to standard output.&lt;/p&gt;

&lt;h3&gt;
  
  
  test/dune
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(test
 (name test)
 (libraries lib))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all you need! Dune runs the test mostly on a 'convention-over-configuration' basis.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The test component has name &lt;code&gt;test&lt;/code&gt;, meaning that dune will build it into an executable &lt;code&gt;test.exe&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The test executable will print some output to standard output, which dune will automatically capture in a file &lt;code&gt;test.exe.output&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Then dune will diff this output against the expected output in &lt;code&gt;test.expected&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The file &lt;code&gt;test.expected&lt;/code&gt; is meant to be committed into the codebase. It is initially empty, and we will update it as part of our testing workflow.&lt;/p&gt;

&lt;p&gt;Notice that dune automatically understands these inputs and outputs and their relation to each other, and will rebuild the test whenever necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  First test
&lt;/h2&gt;

&lt;p&gt;Now let's run the initial test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ dune test
File "test/test.expected", line 1, characters 0-0:
diff --git a/_build/default/test/test.expected b/_build/default/test/test.exe.output
index e69de29..1522c5b 100644
--- a/_build/default/test/test.expected
+++ b/_build/default/test/test.exe.output
@@ -0,0 +1,4 @@
+add 1 1: 2
+
+sub 1 1: 0
+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Promotion
&lt;/h2&gt;

&lt;p&gt;The diff says that the actual output content is not what we 'expected'. Of course, we deliberately started with an empty file here, so let's update the 'expected file' to match the 'actual' one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ dune promote
Promoting _build/default/test/test.exe.output to test/test.expected.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Rerun test
&lt;/h2&gt;

&lt;p&gt;After the promotion, let's check that the test passes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ dune test
$
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No output, meaning the test succeeded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add tests
&lt;/h2&gt;

&lt;p&gt;Let's add a new test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"add 1 1"&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"sub 1 1"&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"sub 1 -1"&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;~-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ dune test
File "test/test.expected", line 1, characters 0-0:
diff --git a/_build/default/test/test.expected b/_build/default/test/test.exe.output
index 1522c5b..17ccf8e 100644
--- a/_build/default/test/test.expected
+++ b/_build/default/test/test.exe.output
@@ -2,3 +2,5 @@ add 1 1: 2

 sub 1 1: 0

+sub 1 -1: 2
+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OK, we just need to promote it: &lt;code&gt;dune promote&lt;/code&gt;. Then the next &lt;code&gt;dune test&lt;/code&gt; succeeds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix a bug
&lt;/h2&gt;

&lt;p&gt;Let's say we introduce a bug into our implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's run the tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ dune test
File "test/test.expected", line 1, characters 0-0:
diff --git a/_build/default/test/test.expected b/_build/default/test/test.exe.output
index 17ccf8e..29adb0b 100644
--- a/_build/default/test/test.expected
+++ b/_build/default/test/test.exe.output
@@ -1,6 +1,6 @@
 add 1 1: 2

-sub 1 1: 0
+sub 1 1: 2

-sub 1 -1: 2
+sub 1 -1: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It gives us a diff of exactly the failing tests. Obviously, in this case we are not going to run &lt;code&gt;dune promote&lt;/code&gt;. We need to fix the implementation: &lt;code&gt;let sub x y = x - y&lt;/code&gt;, then rerun the test. And we see that after fixing and rerunning, &lt;code&gt;dune test&lt;/code&gt; exits silently, meaning the tests are passing again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Discussion
&lt;/h2&gt;

&lt;p&gt;So...should you actually do this? Let's look at the pros and cons.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pros
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;No need for a third-party testing library. Dune already does the heavy lifting of running tests and diffing outputs.&lt;/li&gt;
&lt;li&gt;No need to learn a set of testing APIs that someone else created. You can just write your own helpers that are custom-made for testing your libraries. All you need to do is make the output understandable and diffable.&lt;/li&gt;
&lt;li&gt;Diff-and-promote workflow is really quite good, even with a bare-bones setup like this. Conventional unit test frameworks really struggle to provide diff output as good as this (Jane Street's ppx_expect is an exception which takes a hybrid approach and wants to make the workflow &lt;a href="https://blog.janestreet.com/the-joy-of-expect-tests/" rel="noopener noreferrer"&gt;a joyful experience&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;You have all expected test results in a single file for easy inspection.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Cons
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;It's tied to dune. While dune is today and for the foreseeable future clearly the recommended build system for OCaml, not everyone is using it, and there's no guarantee that the ecosystem will stick to it in perpetuity. It's just highly likely.&lt;/li&gt;
&lt;li&gt;You have to define your own output format and helpers. While usually not that big of a deal, it may still need some &lt;a href="https://dev.to/yawaramin/how-to-print-anything-in-ocaml-1hkl"&gt;thought and knowledge&lt;/a&gt; to define printers for complex custom types.&lt;/li&gt;
&lt;li&gt;You can't run only a subset or a single test. You have to run all tests defined in the executable test module. This is not a huge deal if tests usually run fast, but can become problematic when you have slow tests. Of course, many things become problematic when you have slow unit tests.&lt;/li&gt;
&lt;li&gt;It doesn't output results in a structured format that can be processed by other tools, eg &lt;code&gt;junit.xml&lt;/code&gt; that can be used by CI pipelines to report test failures, or test coverage.&lt;/li&gt;
&lt;li&gt;It goes against the 'common wisdom'. People expect unit tests to use conventional-style frameworks, and can be taken aback when they don't.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Overall, in my opinion this approach is fine for simple cases. If you have more complex needs, fortunately there are plenty of options for more powerful test frameworks.&lt;/p&gt;

</description>
      <category>ocaml</category>
      <category>testing</category>
    </item>
    <item>
      <title>Why I don't use a third-party assertion library in Go unit tests</title>
      <dc:creator>Yawar Amin</dc:creator>
      <pubDate>Mon, 20 May 2024 18:54:02 +0000</pubDate>
      <link>https://dev.to/yawaramin/why-i-dont-use-a-third-party-assertion-library-in-go-unit-tests-1gak</link>
      <guid>https://dev.to/yawaramin/why-i-dont-use-a-third-party-assertion-library-in-go-unit-tests-1gak</guid>
      <description>&lt;p&gt;TL;DR: I don't need it, and you probably don't either. I'll explain below.&lt;/p&gt;

&lt;p&gt;As we know of course, Go ships with a built-in unit testing framework in its standard library and toolchain, as explained in the &lt;a href="https://pkg.go.dev/testing" rel="noopener noreferrer"&gt;&lt;code&gt;testing&lt;/code&gt;&lt;/a&gt; package documentation. Assuming you have some code to test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The documentation shows a test like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"testing"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestAdd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Add(1, 2) = %d; want 4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;got&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;And if the test fails you get an error like: &lt;code&gt;Add(1, 2) = 3; want 4&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Of course, as soon as people saw this, the third-party assertion helper libraries started appearing. The most popular one seems to be &lt;a href="https://github.com/stretchr/testify" rel="noopener noreferrer"&gt;testify&lt;/a&gt; (although I've never used it). Personally, I thought that the explicit check would be good enough for me, but it's true that after writing a bunch of tests, the boilerplate does seem unnecessarily verbose.&lt;/p&gt;

&lt;p&gt;But do we really need a third-party library to abstract it? Pre-Go generics, I might have said yes. But post-generics, I think it's pretty simple to write a helper function directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;assert&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"testing"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;V&lt;/span&gt; &lt;span class="n"&gt;comparable&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="n"&gt;V&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Helper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`assert.Equal(
t,
got:
%v
,
expected:
%v
)`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected&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;As you can see, generics are the key here, especially the fact that we specify that types must be &lt;em&gt;comparable.&lt;/em&gt; Before generics, we would have had to use things like reflection, or even just not use a helper function at all. To my eyes, many pre-generics Go code patterns seem to be about avoiding things that would have required generics to express safely. Eg, we were using a generic comparison operator &lt;code&gt;got != 4&lt;/code&gt; directly instead of encapsulating the comparison in a function call that would have needed generics.&lt;/p&gt;

&lt;p&gt;So, how does this look like in practice? Here's the above test case rewritten to use the helper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func TestAdd(t *testing.T) {
    assert.Equal(t, Add(1, 2), 4)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Much nicer, I think, even though we lose the ability to format a custom error message. Speaking of error message, let's look at that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ go test gt/add
--- FAIL: TestAdd (0.00s)
    add_test.go:9: assert.Equal(
        t,
        got:
        3
        ,
        expected:
        4
        )
FAIL
FAIL    gt/add  0.119s
FAIL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sure, we don't have a custom error message that tells us what operation was performed here, &lt;em&gt;but&lt;/em&gt; we do have the exact line number in the test so we can just see for ourselves. And also, VSCode and I assume other good editors can show test results inline:&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%2Fzegzz10v6jyf8ulj5vc8.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%2Fzegzz10v6jyf8ulj5vc8.png" alt="Inline test result" width="800" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With a good editor showing inline results, it's pretty obvious what the code under test did and what result it expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  API design considerations
&lt;/h2&gt;

&lt;p&gt;Deciding on the right API and the right output is a little tricky, but it's worth taking the time to do it right. The function signature is important:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;V&lt;/span&gt; &lt;span class="n"&gt;comparable&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="n"&gt;V&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that we pass in the &lt;em&gt;actual&lt;/em&gt; ie 'got' value &lt;em&gt;first,&lt;/em&gt; and the &lt;em&gt;expected&lt;/em&gt; value second. The reason for this interconnects with my testing philosophy: one test function should &lt;em&gt;try&lt;/em&gt; (as hard as possible) to assert &lt;em&gt;one&lt;/em&gt; thing. Often, we need to test more complex data. In these cases, I believe the best approach is to snapshot the data as a string and assert on the string. Eg:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dataframe1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataframe2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="s"&gt;`---------
| a | b |
---------
| 1 | 2 |`&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;This allows us to elegantly capture the entire 'behaviour' of the code under test, and update it quickly in the future if needed. If one day the dataframe result changes, the test failure output will show the new value and we can update it with a simple copy-paste. Think of it as a proto-&lt;a href="https://jestjs.io/docs/snapshot-testing" rel="noopener noreferrer"&gt;snapshot testing&lt;/a&gt; style. Maybe in the future editor support tools will even be able to offer a one-click way to update the expected value.&lt;/p&gt;

&lt;p&gt;Finally, note the layout of the failure message: &lt;code&gt;assert.Equal(t, got: x, expected: y)&lt;/code&gt;. This is deliberately chosen to teach the user how to call this helper even if they don't start by reading its documentation. By just looking at the error, they learn that the 'actual' value is the &lt;em&gt;second&lt;/em&gt; argument, and the &lt;em&gt;expected&lt;/em&gt; value is the &lt;em&gt;third.&lt;/em&gt; This is informative while also being fairly succinct.&lt;/p&gt;

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

&lt;p&gt;As we see here, it doesn't take many lines of code to write a very useful test assertion helper directly on top of the standard library, thanks to Go generics. In my opinion this covers 99% of Go unit testing needs. The remaining 1% is left as an exercise for the reader!&lt;/p&gt;

</description>
      <category>go</category>
    </item>
    <item>
      <title>Format strings in OCaml</title>
      <dc:creator>Yawar Amin</dc:creator>
      <pubDate>Sun, 25 Feb 2024 02:34:10 +0000</pubDate>
      <link>https://dev.to/yawaramin/format-strings-in-ocaml-59ci</link>
      <guid>https://dev.to/yawaramin/format-strings-in-ocaml-59ci</guid>
      <description>&lt;p&gt;OCAML doesn't have string interpolation, but it does have C-style format strings (but type-safe). Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;hello&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"Hello, %s!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="c"&gt;(* Can be written as: let hello = Printf.printf "Hello, %s!" *)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is type-safe in an almost magical way (example REPL session):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# hello 1;;
Error: This expression has type int but an expression was expected of type
         string
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can however be a little tricky to wrap your head around:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# let bob = "Bob";;
val bob : string = "Bob"

# Printf.printf bob;;
Error: This expression has type string but an expression was expected of type
         ('a, out_channel, unit) format =
           ('a, out_channel, unit, unit, unit, unit) format6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This error is saying that the &lt;code&gt;printf&lt;/code&gt; function wants a 'format string', which is distinct from a regular string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# let bob = format_of_string "bob";;
val bob : ('_weak1, '_weak2, '_weak3, '_weak4, '_weak4, '_weak1) format6 =
  CamlinternalFormatBasics.Format
   (CamlinternalFormatBasics.String_literal ("bob",
     CamlinternalFormatBasics.End_of_format),
   "bob")

# Printf.printf bob;;
bob- : unit = ()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OCaml distinguishes between regular strings and format strings. The latter are complex structures which encode type information inside them. They are parsed and turned into these structures either when the compiler sees a string literal and'realizes' that a format string is expected, &lt;em&gt;or&lt;/em&gt; when you (the programmer) explicitly asks for the conversion. Another example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# let fmt = "Hello, %s!\n" ^^ "";;
val fmt :
  (string -&amp;gt; '_weak5, '_weak6, '_weak7, '_weak8, '_weak8, '_weak5) format6 =
  CamlinternalFormatBasics.Format
   (CamlinternalFormatBasics.String_literal ("Hello, ",
     CamlinternalFormatBasics.String (CamlinternalFormatBasics.No_padding,
      CamlinternalFormatBasics.String_literal ("!\n",
       CamlinternalFormatBasics.End_of_format))),
   "Hello, %s!\n%,")

# Printf.printf fmt "Bob";;
Hello, Bob!
- : unit = ()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;^^&lt;/code&gt; operator is the &lt;a href="https://v2.ocaml.org/api/Stdlib.html#VAL(^^)" rel="noopener noreferrer"&gt;format string concatenation&lt;/a&gt; operator. Think of it as a more powerful version of the string concatenation operator, &lt;code&gt;^&lt;/code&gt;. It can concatenate either format strings that have already been bound to a name, or string literals which it interprets as format strings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# bob ^^ bob;;
- : (unit, out_channel, unit, unit, unit, unit) format6 =
CamlinternalFormatBasics.Format
 (CamlinternalFormatBasics.String_literal ("bob",
   CamlinternalFormatBasics.String_literal ("bob",
    CamlinternalFormatBasics.End_of_format)),
 "bob%,bob")

# bob ^^ "!";;
- : (unit, out_channel, unit, unit, unit, unit) format6 =
CamlinternalFormatBasics.Format
 (CamlinternalFormatBasics.String_literal ("bob",
   CamlinternalFormatBasics.Char_literal ('!',
    CamlinternalFormatBasics.End_of_format)),
 "bob%,!")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Custom formatting functions
&lt;/h2&gt;

&lt;p&gt;The really amazing thing about format strings is that you can define your own functions which use them to output formatted text. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# let shout fmt = Printf.ksprintf (fun s -&amp;gt; s ^ "!") fmt;;
val shout : ('a, unit, string, string) format4 -&amp;gt; 'a = &amp;lt;fun&amp;gt;

# shout "hello";;
- : string = "hello!"

# let jim = "Jim";;
val jim : string = "Jim"

# shout "Hello, %s" jim;;
- : string = "Hello, Jim!"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is really just a simple example; you actually are not restricted to outputting only strings from &lt;code&gt;ksprintf&lt;/code&gt;. You can output any data structure you like. Think of &lt;code&gt;ksprintf&lt;/code&gt; as '(k)ontinuation-based sprintf'; in other words, it takes a format string (&lt;code&gt;fmt&lt;/code&gt;), any arguments needed by the format string (eg &lt;code&gt;jim&lt;/code&gt;), builds the output string, then passes it to the continuation that you provide (&lt;code&gt;fun s -&amp;gt; ...&lt;/code&gt;), in which you can build any value you want. This value will be the final output value of the function call.&lt;/p&gt;

&lt;p&gt;Again, this is just as type-safe as the basic &lt;code&gt;printf&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# shout "Hello, jim" jim;;
Error: This expression has type
         ('a -&amp;gt; 'b, unit, string, string, string, 'a -&amp;gt; 'b)
         CamlinternalFormatBasics.fmt
       but an expression was expected of type
         ('a -&amp;gt; 'b, unit, string, string, string, string)
         CamlinternalFormatBasics.fmt
       Type 'a -&amp;gt; 'b is not compatible with type string
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This error message looks a bit scary, but the real clue here is in the last line: an extra &lt;code&gt;string&lt;/code&gt; argument was passed in, but it was expecting &lt;code&gt;'a -&amp;gt; 'b&lt;/code&gt;. Unfortunately the type error here is not that great because of how powerful and general this function is. Because it could potentially accept any number of arguments depending on the format string, its type is expressed in a very general way. This is a drawback of format strings to watch out for. But once you are familiar with it, it's typically not a big problem. You just need to match up the conversion specifications like &lt;code&gt;%&lt;/code&gt; with the actual arguments passed in after the format string.&lt;/p&gt;

&lt;p&gt;You might have noticed that the function is defined with &lt;code&gt;let shout fmt = ...&lt;/code&gt;. It doesn't look like it could accept 'any number of arguments'. The trick here is that in OCaml, every function accepts only a single argument and returns either a final non-function value, or a new function. In the case of functions which use format strings, it depends on the conversion specifications, so the formal definition &lt;code&gt;shout fmt&lt;/code&gt; could potentially turn into a call like &lt;code&gt;shout "%s bought %d apples today" bob num_apples&lt;/code&gt;. As a shortcut, you can think of the format string &lt;code&gt;fmt&lt;/code&gt; as a variadic argument which can potentially turn into any number of arguments at the callsite.&lt;/p&gt;

&lt;h2&gt;
  
  
  More reading
&lt;/h2&gt;

&lt;p&gt;You can read more about OCaml's format strings functionality in the documentation for the &lt;a href="https://v2.ocaml.org/api/Printf.html" rel="noopener noreferrer"&gt;Printf&lt;/a&gt; and &lt;a href="https://v2.ocaml.org/api/Format.html" rel="noopener noreferrer"&gt;Format&lt;/a&gt; modules. There is also a &lt;a href="https://ocaml.org/docs/formatting-text" rel="noopener noreferrer"&gt;gentle guide&lt;/a&gt; to formatting text, something OCaml has fairly advanced support for because it turns out to be a pretty common requirement to print out the values of various things at runtime.&lt;/p&gt;

&lt;p&gt;On that note, I have also written more about defining custom formatted printers for any value &lt;a href="https://dev.to/yawaramin/how-to-print-anything-in-ocaml-1hkl"&gt;right here&lt;/a&gt; on dev.to. Enjoy 🐫&lt;/p&gt;

</description>
      <category>ocaml</category>
    </item>
    <item>
      <title>Bookmarklets, and why you should use them</title>
      <dc:creator>Yawar Amin</dc:creator>
      <pubDate>Tue, 02 Jan 2024 17:48:51 +0000</pubDate>
      <link>https://dev.to/yawaramin/bookmarklets-and-why-you-should-use-them-33j0</link>
      <guid>https://dev.to/yawaramin/bookmarklets-and-why-you-should-use-them-33j0</guid>
      <description>&lt;p&gt;MOST of us have been using web browsers for a long time, yet relatively few of us know this somewhat obscure feature: browsers understand URLs that begin with &lt;code&gt;javascript:&lt;/code&gt; instead of &lt;code&gt;http:&lt;/code&gt; or &lt;code&gt;https:&lt;/code&gt; protocols, and can actually execute the JavaScript code in the URL when you request it. Sounds like a security nightmare? Potentially, yes. You should be careful about what URLs you click on.&lt;/p&gt;

&lt;p&gt;However! Harnessed in the right way, these &lt;code&gt;javascript:&lt;/code&gt; URLs can be incredibly useful–enter bookmarklets.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are they
&lt;/h2&gt;

&lt;p&gt;Bookmarklets are just regular browser bookmarks &lt;em&gt;except&lt;/em&gt; that they have a &lt;code&gt;javascript:&lt;/code&gt; URL:&lt;/p&gt;

&lt;p&gt;Name: (whatever name you want)&lt;/p&gt;

&lt;p&gt;URL: &lt;code&gt;javascript:/* code */&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For example, &lt;code&gt;javascript:alert('hi!')&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;OK, so an alert pop-up is not that useful. But bookmarklets can do anything that JavaScript running on the page can. Even better, they allow you to replace some browser extensions that have similar functionality. For power users, instead of the memory overhead of a loaded browser extension, you replace it with a zero-cost bookmarklet.&lt;/p&gt;

&lt;p&gt;Let me show you two very simple and useful bookmarklets and explain how they work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Allow paste
&lt;/h2&gt;

&lt;p&gt;URL: &lt;code&gt;javascript:document.addEventListener('paste', e =&amp;gt; e.stopImmediatePropagation(), true)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;When you click on this, it will 'trap' all paste events from further handling by JavaScript code, &lt;em&gt;but&lt;/em&gt; allow the actual paste to go ahead. Why would you need this? Well, some annoying websites like to disable paste functionality by setting their own &lt;code&gt;paste&lt;/code&gt; event handlers on certain text inputs. They think that eg if you can't paste a password, you will be forced to type it in, which for some reason will make it more secure.&lt;/p&gt;

&lt;p&gt;Of course, that's nonsense. But that's the kind of reasoning these sites have (I guess). So we circumvent these geniuses by taking control of our own paste events.&lt;/p&gt;

&lt;p&gt;This allows you to get rid of: &lt;a href="https://chromewebstore.google.com/detail/dont-f-with-paste/nkgllhigpcljnhoakjkgaieabnkmgdkb" rel="noopener noreferrer"&gt;Don't F*** With Paste&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Scroll top/bottom
&lt;/h2&gt;

&lt;p&gt;URL: &lt;code&gt;javascript:window.scrollTo(0, window.scrollY == 0 ? document.body.scrollHeight : 0)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;When clicked, this will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If at the top of the page, scroll to the bottom&lt;/li&gt;
&lt;li&gt;Else, scroll to the top&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simple, yet effective.&lt;/p&gt;

&lt;p&gt;This allows you to get rid of: &lt;a href="https://chromewebstore.google.com/detail/scroll-to-top/hegiignepmecppikdlbohnnbfjdoaghj" rel="noopener noreferrer"&gt;Scroll To Top&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  And more
&lt;/h2&gt;

&lt;p&gt;These are just two simple use cases. As you can imagine, anything you can do with JavaScript on a web page, you can do with bookmarklets. Even making HTTP requests with the Fetch API! You're limited only by your imagination.&lt;/p&gt;

&lt;p&gt;I have a &lt;a href="https://yawaramin.github.io/bookmarklets/" rel="noopener noreferrer"&gt;collection of bookmarklets&lt;/a&gt; if you are interested in more. Have fun 🙂&lt;/p&gt;

</description>
      <category>browser</category>
    </item>
  </channel>
</rss>
