{"article":{"slug":"another-step-towards-elm-1-0","title":"Another step towards Elm 1.0","subtitle":null,"summary":"Evan Czaplicki announces Elm 0.19.3, the second incremental release on the road to an end-to-end Elm experience with Acadia, outlining the rough roadmap for upcoming releases and the compiler infrastructure work aimed at making it correct by construction.","content_type":"announcement","language":"en","canonical_url":"https://elm-lang.org/news/another-step-towards-elm-v1","author":{"name":"Evan Czaplicki","url":null,"person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"Elm","url":"https://elm-lang.org/","listing_slug":null,"listing":null},"topics":[{"name":"Elm","slug":"elm","url":"https://listedarticles.com/topics/elm"},{"name":"Programming Languages","slug":"programming-languages","url":"https://listedarticles.com/topics/programming-languages"},{"name":"Compilers","slug":"compilers","url":"https://listedarticles.com/topics/compilers"},{"name":"Functional Programming","slug":"functional-programming","url":"https://listedarticles.com/topics/functional-programming"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":1737,"reading_minutes":8,"published_at":"2026-10-05T00:00:00.000Z","added_at":"2026-10-06T11:14:08.423Z","updated_at":"2026-10-06T11:14:08.423Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/another-step-towards-elm-1-0","markdown_url":"https://listedarticles.com/articles/another-step-towards-elm-1-0.md","example":false,"citation":"Evan Czaplicki, Elm. \"Another step towards Elm 1.0.\" 5 Oct 2026. https://elm-lang.org/news/another-step-towards-elm-v1 (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://elm-lang.org/news/another-step-towards-elm-v1"},"body_markdown":"Elm is known for friendly error messages, easy refactoring, and strong correctness guarantees. Many people have such a nice time writing their frontend code with Elm that they end up wanting the same level of quality in their backend code as well. So we have been hard at work expanding “the Elm experience” to the server and database in a thoughtful and coherent way. Check out [Acadia](https://acadia.engineering/) and [`elm-simple-server`](https://github.com/acadia-engineering/elm-simple-server) if you are interested in that! Acadia is also the “Patreon” for Elm, so we are also on the way to a stable and sustainable financial foundation. (Thank you! This work is not possible without your [support](https://acadia.engineering/support)!)\n\nToday marks another step on the road to “the end-to-end Elm experience” with the second incremental Elm release. You can get the 0.19.3 binaries [here](https://github.com/elm/compiler/releases/tag/0.19.3)!\n\nThe rest of this post gets into (1) the rough roadmap for the next few Elm releases and (2) the infrastructure improvements in Elm 0.19.3 which focus on making the compiler “correct by construction”. These “correct by construction” techniques are useful in any program, so hopefully you will be inspired to improve your own code with these ideas!\n\n\n## The Rough Roadmap\n\nThe compiler for [Acadia](https://acadia.engineering/) is significantly more advanced than the Elm compiler, so we need to backport some infrastructure improvements to get them more aligned (blue changes). This will unlock “more exciting” improvements down the line (black changes).\n\n![feature tree](https://elm-lang.org/assets/blog/0.19.3/feature-tree.svg)\n\nI am quite excited about the projects that will be possible after we line up the two compilers! **We are also experimenting with a faster release cycle**, so the current plan is to break the infrastructure changes (in blue) into a couple of smaller releases.\n\nNow on to the details of the 0.19.3 release!\n\n\n## Better Types for Better Programs\n\nElm takes the perspective that our programs should be “correct by construction” such that invalid data is not even representable. **Entire categories of “invalid data” bugs can be eliminated when you use custom types to guarantee that such data simply cannot exist.** This concept has been called “make invalid states unrepresentable” or “make impossible states impossible” and it is great for code quality.\n\nTo that end, many of the changes in Elm 0.19.3 focus on using more precise types in the compiler. We will focus on two little examples that may be inspiring to people interested in functional programming techniques.\n\n\n### A `String` is not always a `String`\n\nIn Elm, it is common to create a unique type for certain kinds of data. For example, if you want a 100% guarantee that you are working with a validated email address, you can create a module like this:\n\n```elm\nmodule Email exposing (Email, validate, toString)\n\ntype Email = Email String\n\nvalidate : String -> Maybe Email\n\ntoString : Email -> String\n```\n\nNow the *only* way to construct an `Email` value is to call `Email.validate`. You now have a 100% guarantee that all `Email` values have been validated!\n\nThis also means that you can never accidentally use an `Email` as a regular `String`. These are two distinct types. This is great as you start working with concepts like `UserName` or `LastName` because now you have a 100% guarantee that you never use a `LastName` as a `UserName`. **This category of bug can be fully eliminated!**\n\n**Elm also imposes zero runtime overhead for creating `Email` values.** The Elm compiler recognizes that it is just a wrapper around the `String` type, so it uses a plain `String` in the generated JS code.\n\nThis technique gives you strong guarantees that can be useful in all sorts of programs! It is extremely nice in my Acadia databases, where my `Email` columns are 100% guaranteed to be separate from my `UserName` columns. It is even useful for compilers! In the old Elm compiler, every variable name was represented by a `Name` type like this:\n\n```elm\ntype Name = Name String\n```\n\nThis is an improvement over using a plain `String`, but there are a couple different categories of names in Elm. There are type names, module names, module prefixes, variable names, type variable names, and operator names. Each has their own rules and they should never be mixed. So in Elm 0.19.3 each of these categories now is a distinct type. This makes the code a lot nicer to read, and it gives us a 100% guarantee that these names are never crossing over into incompatible categories.\n\nWhen making these kinds of changes, there is a chance you uncover bugs that have been overlooked. Maybe a module name is getting used as a variable name in some weird scenario? **I actually discovered a pretty subtle bug during this refactor!** In some cases, a hand-written type alias must be “dealiased” and expanded into its underlying type, and in rare cases, the hand-written type involves an “extended record” that leaves some fields open as a type variable, and in even rarer cases, the hand-written type involves *nested* extended records which are possible-but-generally-recommended-against. Switching to a distinct `ExtVar` type made it clear that the extension variable was not being properly transformed in the nested case! We had known of the rather rare symptoms of this issue for a long time, but **the root cause was only revealed by an independent initiative to have a “correct by construction” compiler!**\n\nI wonder if you can find similar bugs in your code with the same simple technique!\n\n\n### A `List` that is never empty\n\nAnother nice example of data structures that are “correct by construction” is in how we detect module cycles. People using Elm in larger applications may have a couple hundred modules, and the compiler provides friendly error messages to help disentangle any cycles that emerge:\n\n<pre><code class=\"lang-elm\"><span class=\"hljs-type\">-- IMPORT CYCLE ----------------------------------------------------------------</span>\n\nYour module imports form a cycle:\n\n    ┌─────┐\n    │    <span class=\"hljs-name\">User</span>\n    │     ↓\n    │    <span class=\"hljs-name\">Post</span>\n    │     ↓\n    │    <span class=\"hljs-name\">Blog</span>\n    └─────┘\n\nLearn more about why this is disallowed and how to break cycles here:\nhttps://elm-lang.org/0.19.3/import-cycles</code></pre>\n\nOur algorithm detects [strongly connected components](https://en.wikipedia.org/wiki/Strongly_connected_component), and it turns out that a strongly connected component (SCC) may not *always* be a nice linear cycle. Here are some examples of “difficult” strongly connected components:\n\n![strongly connected components](https://elm-lang.org/assets/blog/0.19.3/strongly-connected-components.svg)\n\nRendering these non-linear cyclic graphs in a clear way is not very easy, especially in the terminal and especially as the graphs get larger. Graphs with many nodes and edges often look very confusing when flattened into 2D. These sorts of cases are rare in normal programs, so for a long time, we did not realize that our cycle renderer was not showing them properly!\n\nSo we took the approach of making our `Graph` types more precise with an API like this:\n\n```elm\nmodule Graph exposing\n  ( Node, SCC(..), Component, toStronglyConnectedComponents\n  , MinimalCycle(..), toMinimalCycle\n  )\n\ntype alias Node key value =\n  { key : key\n  , value : value\n  , edges : List key\n  }\n\ntoStronglyConnectedComponents : List (Node k v) -> List (SCC k v)\n\ntype SCC k v\n  = Acyclic (Node k v)\n  | Cyclic (Component k v)\n\ntype Component k v =\n  Component (Node k v) (List (Node k v))\n\ntoMinimalCycle : (k -> v -> a) -> Component k v -> MinimalCycle a\n\ntype MinimalCycle a =\n  MinimalCycle a (List a)\n```\n\nThere is a lot to process here, so we will start with the `MinimalCycle` type at the very end. There are a couple of things we want to guarantee about these cycles:\n\n  1. The cycles are never empty. There is always at least one module in a cycle.\n  2. The cycles are always linear. The arrows we draw in the error messages should show the shortest path from one module back to itself.\n\nFor our first guarantee, we use a common technique for guaranteeing that “this list is never empty”. Notice that the `MinimalCycle` type requires that you provide one value, and then a list of additional values, like `MinimalCycle 1 [2,3]` or `MinimalCycle 1 []`. This makes it is impossible to create a `MinimalCycle` with zero entries! There must always be one value, and then the list with any additional values. **Now we have a 100% guarantee that our cycles are non-empty.** They are “correct by construction”.\n\nThe second guarantee is enforced by having strong module boundaries. Running `toStronglyConnectedComponents` will produce a cyclic `Component` value when there are module cycles, and these components may have all sorts of confusing edges that are difficult to render. We keep these details internal to the `Graph` module. If you want to see what is inside a `Component`, the only option is to call `toMinimalCycle` to convert it into a minimal linear cycle. There is no other way! **Now we have a 100% guarantee that we always render nice linear cycles in our error messages!** All code outside of the `Graph` module must convert a `Component` to a `MinimalCycle` if they want to look at the values.\n\nNext time you run into a cycle in an Elm error message, perhaps you will be reminded of these techniques for getting strong guarantees in your own code!\n\n\n## Conclusion\n\n**When our programs are “correct by construction”, we rule out entire categories of bugs.** It is no longer a matter of testing or vigilance. The bugs are just not possible! The simple techniques we discussed here are useful whether you are writing websites, databases, or compilers, and I hope you will give them a try with the online [examples](https://elm-lang.org/examples) or with [Elm 0.19.3](https://github.com/elm/compiler/releases/tag/0.19.3) and the [guide](https://guide.elm-lang.org)!\n\nIn addition to the compiler changes described in this post, we also made improvements to our primitives for concurrency, binary serialization, and file locking. All the changes are within the overall theme of “correct by construction” such that guarantees like “file writes can only happen when you have a valid project lock” are enforced by the type system. These more precise primitives give us a strong foundation for the user-facing language work we have lined up.\n\n**If you like what you are seeing, please consider supporting Elm and Acadia development [here](https://acadia.engineering/support)!** We write everything by hand with a focus on quality and performance, and we hope our tools help you do the same.\n\nFinally, thank you to everyone using and supporting Elm and Acadia! We love writing beautiful and simple code, and we are glad you do too!\n\n> **Note:** If you want to follow Elm news, I recommend [twitter](https://x.com/evancz) for just the “big news” and [bluesky](https://bsky.app/profile/acadia.engineering) or [discourse](https://discourse.elm-lang.org/) for more frequent and more interactive updates. We try to make sure posts make it around on Reddit and Hacker News, but obviously not every post gets good distribution, like [this nice one](https://acadia.engineering/blog/simple-and-efficient-row-level-security) about row-level security and symbolic evaluation.\n","body_html":"<p>Elm is known for friendly error messages, easy refactoring, and strong correctness guarantees. Many people have such a nice time writing their frontend code with Elm that they end up wanting the same level of quality in their backend code as well. So we have been hard at work expanding “the Elm experience” to the server and database in a thoughtful and coherent way. Check out <a href=\"https://acadia.engineering/\" rel=\"nofollow ugc noopener\">Acadia</a> and <a href=\"https://github.com/acadia-engineering/elm-simple-server\" rel=\"nofollow ugc noopener\"><code>elm-simple-server</code></a> if you are interested in that! Acadia is also the “Patreon” for Elm, so we are also on the way to a stable and sustainable financial foundation. (Thank you! This work is not possible without your <a href=\"https://acadia.engineering/support\" rel=\"nofollow ugc noopener\">support</a>!)</p>\n<p>Today marks another step on the road to “the end-to-end Elm experience” with the second incremental Elm release. You can get the 0.19.3 binaries <a href=\"https://github.com/elm/compiler/releases/tag/0.19.3\" rel=\"nofollow ugc noopener\">here</a>!</p>\n<p>The rest of this post gets into (1) the rough roadmap for the next few Elm releases and (2) the infrastructure improvements in Elm 0.19.3 which focus on making the compiler “correct by construction”. These “correct by construction” techniques are useful in any program, so hopefully you will be inspired to improve your own code with these ideas!</p>\n<h2 id=\"the-rough-roadmap\">The Rough Roadmap</h2>\n<p>The compiler for <a href=\"https://acadia.engineering/\" rel=\"nofollow ugc noopener\">Acadia</a> is significantly more advanced than the Elm compiler, so we need to backport some infrastructure improvements to get them more aligned (blue changes). This will unlock “more exciting” improvements down the line (black changes).</p>\n<figure><img src=\"https://elm-lang.org/assets/blog/0.19.3/feature-tree.svg\" alt=\"feature tree\" loading=\"lazy\" decoding=\"async\" referrerpolicy=\"no-referrer\" /></figure>\n<p>I am quite excited about the projects that will be possible after we line up the two compilers! <strong>We are also experimenting with a faster release cycle</strong>, so the current plan is to break the infrastructure changes (in blue) into a couple of smaller releases.</p>\n<p>Now on to the details of the 0.19.3 release!</p>\n<h2 id=\"better-types-for-better-programs\">Better Types for Better Programs</h2>\n<p>Elm takes the perspective that our programs should be “correct by construction” such that invalid data is not even representable. <strong>Entire categories of “invalid data” bugs can be eliminated when you use custom types to guarantee that such data simply cannot exist.</strong> This concept has been called “make invalid states unrepresentable” or “make impossible states impossible” and it is great for code quality.</p>\n<p>To that end, many of the changes in Elm 0.19.3 focus on using more precise types in the compiler. We will focus on two little examples that may be inspiring to people interested in functional programming techniques.</p>\n<h3 id=\"a-string-is-not-always-a-string\">A <code>String</code> is not always a <code>String</code></h3>\n<p>In Elm, it is common to create a unique type for certain kinds of data. For example, if you want a 100% guarantee that you are working with a validated email address, you can create a module like this:</p>\n<pre><code class=\"language-elm\">module Email exposing (Email, validate, toString)\n\ntype Email = Email String\n\nvalidate : String -&gt; Maybe Email\n\ntoString : Email -&gt; String</code></pre>\n<p>Now the <em>only</em> way to construct an <code>Email</code> value is to call <code>Email.validate</code>. You now have a 100% guarantee that all <code>Email</code> values have been validated!</p>\n<p>This also means that you can never accidentally use an <code>Email</code> as a regular <code>String</code>. These are two distinct types. This is great as you start working with concepts like <code>UserName</code> or <code>LastName</code> because now you have a 100% guarantee that you never use a <code>LastName</code> as a <code>UserName</code>. <strong>This category of bug can be fully eliminated!</strong></p>\n<p><strong>Elm also imposes zero runtime overhead for creating <code>Email</code> values.</strong> The Elm compiler recognizes that it is just a wrapper around the <code>String</code> type, so it uses a plain <code>String</code> in the generated JS code.</p>\n<p>This technique gives you strong guarantees that can be useful in all sorts of programs! It is extremely nice in my Acadia databases, where my <code>Email</code> columns are 100% guaranteed to be separate from my <code>UserName</code> columns. It is even useful for compilers! In the old Elm compiler, every variable name was represented by a <code>Name</code> type like this:</p>\n<pre><code class=\"language-elm\">type Name = Name String</code></pre>\n<p>This is an improvement over using a plain <code>String</code>, but there are a couple different categories of names in Elm. There are type names, module names, module prefixes, variable names, type variable names, and operator names. Each has their own rules and they should never be mixed. So in Elm 0.19.3 each of these categories now is a distinct type. This makes the code a lot nicer to read, and it gives us a 100% guarantee that these names are never crossing over into incompatible categories.</p>\n<p>When making these kinds of changes, there is a chance you uncover bugs that have been overlooked. Maybe a module name is getting used as a variable name in some weird scenario? <strong>I actually discovered a pretty subtle bug during this refactor!</strong> In some cases, a hand-written type alias must be “dealiased” and expanded into its underlying type, and in rare cases, the hand-written type involves an “extended record” that leaves some fields open as a type variable, and in even rarer cases, the hand-written type involves <em>nested</em> extended records which are possible-but-generally-recommended-against. Switching to a distinct <code>ExtVar</code> type made it clear that the extension variable was not being properly transformed in the nested case! We had known of the rather rare symptoms of this issue for a long time, but <strong>the root cause was only revealed by an independent initiative to have a “correct by construction” compiler!</strong></p>\n<p>I wonder if you can find similar bugs in your code with the same simple technique!</p>\n<h3 id=\"a-list-that-is-never-empty\">A <code>List</code> that is never empty</h3>\n<p>Another nice example of data structures that are “correct by construction” is in how we detect module cycles. People using Elm in larger applications may have a couple hundred modules, and the compiler provides friendly error messages to help disentangle any cycles that emerge:</p>\n<p>&lt;pre&gt;&lt;code class=&quot;lang-elm&quot;&gt;&lt;span class=&quot;hljs-type&quot;&gt;-- IMPORT CYCLE ----------------------------------------------------------------&lt;/span&gt;</p>\n<p>Your module imports form a cycle:</p>\n<pre><code>┌─────┐\n│    &lt;span class=&quot;hljs-name&quot;&gt;User&lt;/span&gt;\n│     ↓\n│    &lt;span class=&quot;hljs-name&quot;&gt;Post&lt;/span&gt;\n│     ↓\n│    &lt;span class=&quot;hljs-name&quot;&gt;Blog&lt;/span&gt;\n└─────┘</code></pre>\n<p>Learn more about why this is disallowed and how to break cycles here:\n<a href=\"https://elm-lang.org/0.19.3/import-cycles\" rel=\"nofollow ugc noopener\">https://elm-lang.org/0.19.3/import-cycles</a>&lt;/code&gt;&lt;/pre&gt;</p>\n<p>Our algorithm detects <a href=\"https://en.wikipedia.org/wiki/Strongly_connected_component\" rel=\"nofollow ugc noopener\">strongly connected components</a>, and it turns out that a strongly connected component (SCC) may not <em>always</em> be a nice linear cycle. Here are some examples of “difficult” strongly connected components:</p>\n<figure><img src=\"https://elm-lang.org/assets/blog/0.19.3/strongly-connected-components.svg\" alt=\"strongly connected components\" loading=\"lazy\" decoding=\"async\" referrerpolicy=\"no-referrer\" /></figure>\n<p>Rendering these non-linear cyclic graphs in a clear way is not very easy, especially in the terminal and especially as the graphs get larger. Graphs with many nodes and edges often look very confusing when flattened into 2D. These sorts of cases are rare in normal programs, so for a long time, we did not realize that our cycle renderer was not showing them properly!</p>\n<p>So we took the approach of making our <code>Graph</code> types more precise with an API like this:</p>\n<pre><code class=\"language-elm\">module Graph exposing\n  ( Node, SCC(..), Component, toStronglyConnectedComponents\n  , MinimalCycle(..), toMinimalCycle\n  )\n\ntype alias Node key value =\n  { key : key\n  , value : value\n  , edges : List key\n  }\n\ntoStronglyConnectedComponents : List (Node k v) -&gt; List (SCC k v)\n\ntype SCC k v\n  = Acyclic (Node k v)\n  | Cyclic (Component k v)\n\ntype Component k v =\n  Component (Node k v) (List (Node k v))\n\ntoMinimalCycle : (k -&gt; v -&gt; a) -&gt; Component k v -&gt; MinimalCycle a\n\ntype MinimalCycle a =\n  MinimalCycle a (List a)</code></pre>\n<p>There is a lot to process here, so we will start with the <code>MinimalCycle</code> type at the very end. There are a couple of things we want to guarantee about these cycles:</p>\n<ol><li>The cycles are never empty. There is always at least one module in a cycle.</li><li>The cycles are always linear. The arrows we draw in the error messages should show the shortest path from one module back to itself.</li></ol>\n<p>For our first guarantee, we use a common technique for guaranteeing that “this list is never empty”. Notice that the <code>MinimalCycle</code> type requires that you provide one value, and then a list of additional values, like <code>MinimalCycle 1 [2,3]</code> or <code>MinimalCycle 1 []</code>. This makes it is impossible to create a <code>MinimalCycle</code> with zero entries! There must always be one value, and then the list with any additional values. <strong>Now we have a 100% guarantee that our cycles are non-empty.</strong> They are “correct by construction”.</p>\n<p>The second guarantee is enforced by having strong module boundaries. Running <code>toStronglyConnectedComponents</code> will produce a cyclic <code>Component</code> value when there are module cycles, and these components may have all sorts of confusing edges that are difficult to render. We keep these details internal to the <code>Graph</code> module. If you want to see what is inside a <code>Component</code>, the only option is to call <code>toMinimalCycle</code> to convert it into a minimal linear cycle. There is no other way! <strong>Now we have a 100% guarantee that we always render nice linear cycles in our error messages!</strong> All code outside of the <code>Graph</code> module must convert a <code>Component</code> to a <code>MinimalCycle</code> if they want to look at the values.</p>\n<p>Next time you run into a cycle in an Elm error message, perhaps you will be reminded of these techniques for getting strong guarantees in your own code!</p>\n<h2 id=\"conclusion\">Conclusion</h2>\n<p><strong>When our programs are “correct by construction”, we rule out entire categories of bugs.</strong> It is no longer a matter of testing or vigilance. The bugs are just not possible! The simple techniques we discussed here are useful whether you are writing websites, databases, or compilers, and I hope you will give them a try with the online <a href=\"https://elm-lang.org/examples\" rel=\"nofollow ugc noopener\">examples</a> or with <a href=\"https://github.com/elm/compiler/releases/tag/0.19.3\" rel=\"nofollow ugc noopener\">Elm 0.19.3</a> and the <a href=\"https://guide.elm-lang.org\" rel=\"nofollow ugc noopener\">guide</a>!</p>\n<p>In addition to the compiler changes described in this post, we also made improvements to our primitives for concurrency, binary serialization, and file locking. All the changes are within the overall theme of “correct by construction” such that guarantees like “file writes can only happen when you have a valid project lock” are enforced by the type system. These more precise primitives give us a strong foundation for the user-facing language work we have lined up.</p>\n<p><strong>If you like what you are seeing, please consider supporting Elm and Acadia development <a href=\"https://acadia.engineering/support\" rel=\"nofollow ugc noopener\">here</a>!</strong> We write everything by hand with a focus on quality and performance, and we hope our tools help you do the same.</p>\n<p>Finally, thank you to everyone using and supporting Elm and Acadia! We love writing beautiful and simple code, and we are glad you do too!</p>\n<blockquote><p><strong>Note:</strong> If you want to follow Elm news, I recommend <a href=\"https://x.com/evancz\" rel=\"nofollow ugc noopener\">twitter</a> for just the “big news” and <a href=\"https://bsky.app/profile/acadia.engineering\" rel=\"nofollow ugc noopener\">bluesky</a> or <a href=\"https://discourse.elm-lang.org/\" rel=\"nofollow ugc noopener\">discourse</a> for more frequent and more interactive updates. We try to make sure posts make it around on Reddit and Hacker News, but obviously not every post gets good distribution, like <a href=\"https://acadia.engineering/blog/simple-and-efficient-row-level-security\" rel=\"nofollow ugc noopener\">this nice one</a> about row-level security and symbolic evaluation.</p></blockquote>","headings":[{"level":2,"text":"The Rough Roadmap","id":"the-rough-roadmap"},{"level":2,"text":"Better Types for Better Programs","id":"better-types-for-better-programs"},{"level":3,"text":"A String is not always a String","id":"a-string-is-not-always-a-string"},{"level":3,"text":"A List that is never empty","id":"a-list-that-is-never-empty"},{"level":2,"text":"Conclusion","id":"conclusion"}]}}