DEV Community

Cover image for What's New in Elixir 1.16
Sophie DeBenedetto for AppSignal

Posted on • Originally published at blog.appsignal.com

What's New in Elixir 1.16

The Elixir 1.16 release candidate is out now, and it comes with some compelling improvements to diagnostics, documentation, and a few other enhancements that make Elixir an even better choice for developers.

We'll dive into some of these changes and highlight Elixir's continued focus on developer happiness and community building.

Let's get started!

Better Diagnostics for Compiler Errors in Elixir

We'll begin with the improvements to compiler diagnostics provided in Elixir 1.16. Now, your mix compile errors come with detailed code snippets that tell you exactly where your code goes wrong.

You'll benefit from these improvements when you have syntax errors, mismatched delimiter errors, and generic compiler errors.

Syntax Errors

First up, you can see below that syntax errors now come with a pointer to exactly where the error happened. Let's say we have the following code:

defmodule Doctor do
  def show_prescription_codes do
    ["DR", "DS", "amp", &]
  end
end
Enter fullscreen mode Exit fullscreen mode

If you run mix compile, you'll see a syntax error with an associated code snippet, like this:

sophiedebenedetto[ 7:30PM] ~/personal-projects/doctor  💖 mix compile
Compiling 1 file (.ex)

== Compilation error in file lib/doctor.ex ==
** (SyntaxError) invalid syntax found on lib/doctor.ex:48:26:
    error: syntax error before: ']'
    │
 48 │     ["DR", "DS", "amp", &]
    │                          ^
    │
    └─ lib/doctor.ex:48:26
    (elixir 1.16.0-rc.1) lib/kernel/parallel_compiler.ex:428: anonymous fn/5 in Kernel.ParallelCompiler.spawn_workers/8
Enter fullscreen mode Exit fullscreen mode

Note the ^ pointer to the exact location of the syntax error. With this, it's even easier to quickly spot and correct such errors.

Mismatched Delimiter Errors

Another diagnostic improvement comes with the code snippet representation for MismatchedDelimiterError occurrences. Given the following code with a mismatched delimiter:

defmodule Doctor do
  def show_prescription_codes do
    ["DR", "DS", "amp", "ad")
  end
end
Enter fullscreen mode Exit fullscreen mode

Running mix compile will now show you the following:

sophiedebenedetto[ 7:29PM] ~/personal-projects/doctor  💖 mix compile
Compiling 1 file (.ex)

== Compilation error in file lib/doctor.ex ==
** (MismatchedDelimiterError) mismatched delimiter found on lib/doctor.ex:48:29:
    error: unexpected token: )
    │
 48 │     ["DR", "DS", "amp", "ad")
    │     │                       └ mismatched closing delimiter (expected "]")
    │     └ unclosed delimiter
    │
    └─ lib/doctor.ex:48:29
    (elixir 1.16.0-rc.1) lib/kernel/parallel_compiler.ex:428: anonymous fn/5 in Kernel.ParallelCompiler.spawn_workers/8
Enter fullscreen mode Exit fullscreen mode

This error includes a display of the code snippet that is causing it, and even highlights the unclosed and mismatched delimiters associated to the MismatchedDelimiterError.

Other Errors

You'll now see similarly detailed code snippets for all kinds of compiler errors, including errors that describe undefined variables. Let's look at an example of that now.

Given the following code:

def list_patients do
  Patient.for_doctor(doctor)
end
Enter fullscreen mode Exit fullscreen mode

Running mix compile will display the following error:

sophiedebenedetto[ 7:36PM] ~/personal-projects/doctor  💖 mix compile
Compiling 1 file (.ex)
    error: undefined variable "doctor"
    │
 53 │     Patient.for_doctor(doctor)
    │                        ^^^^^^
    │
    └─ lib/doctor.ex:53:24: Doctor.list_patients/0


== Compilation error in file lib/doctor.ex ==
** (CompileError) lib/doctor.ex: cannot compile module Doctor (errors have been logged)
Enter fullscreen mode Exit fullscreen mode

These improved diagnostics make it even easier for Elixir developers to understand compilation errors, catch and remedy basic mistakes, and write clean and functioning code. These improvements show Elixir's continued focus on developer experience.

ExDoc Content Matures with Elixir

Some of the most exciting changes coming in Elixir 1.16 are the improvements to official Elixir language docs, offered through ExDoc. The "Getting Started" guide from elixir-lang.org has been incorporated into the official Elixir documentation on Hex docs.

While this may not seem too exciting on the surface, it represents a concerted effort to refine and unify official guidance on the Elixir programming language. The language and the community have matured to such a stage that we can better align on official guides like this and provide the best agreed-upon direction for new developers.

Anti-Patterns

In this same vein, the official Elixir docs now include robust content on Elixir anti-patterns. This content is divided into four separate sections, covering code, design, processes, and metaprogramming.

Anti-patterns documentation in Elixir

Once again, this content is made possible by the maturity of the Elixir language. As Elixir has grown up, and as adoption and the community have grown, we're at a point where Elixir developers can align on what good and bad Elixir code looks like.

With this extensive guidance in the official docs, including framing context and code samples, Elixir developers have the resources they need to ship clean code that meets generally agreed-upon standards. It will be even easier for experienced and novice Elixir developers alike to write and maintain clean code in various scenarios.

And that's not it for docs enhancements.

ExDoc Cheatsheets In the Official Elixir Docs

The official docs now include an ExDoc cheatsheet for the first time. ExDoc cheatsheets allow developers to author quick, summary-type guides to modules and libraries.

The official Elixir docs now include an Enum module cheatsheet, and that's just the beginning.

Elixir maintainers are looking for more community members to contribute cheatsheets to the official docs. This is a great way for first-timers to get an Elixir language contribution! Get started by opening an issue that outlines what cheatsheet you'd like to add.

Taken together, these docs improvements all point to the maturity of the Elixir language and community and the continued focus on education and support for Elixir newcomers. The official introduction docs, the anti-pattern docs, and the inclusion of cheatsheets provide clear and accessible guidance to Elixir developers at every level. This kind of alignment and clarity makes Elixir even more accessible to novices. At the same time, it empowers experienced Elixir devs to deliver clean, standardized Elixir code that performs well.

More Enhancements

You might be interested in taking advantage of some of the smaller enhancements from this release in your upgraded Elixir apps. Let's explore them briefly now.

The String.replace_invalid/2 Function

First up, there is a new String.replace_invalid/2 function that (you guessed it) lets you replace invalid characters in a string. Let's take a look at an example:

iex> String.replace_invalid("asd" <> <<0xFF::8>>)
"asd�"
Enter fullscreen mode Exit fullscreen mode

The default behavior is to replace the invalid character with a �, but you can replace it with any character you like:

iex> String.replace_invalid("asd" <> <<0xFF::8>>, "😵")
"asd😵"
Enter fullscreen mode Exit fullscreen mode

This will come in handy if you find yourself processing strings of text from external sources. Such text may contain invalid characters, and identifying and replacing such characters just got a lot easier.

New Functionality for Task.yield_many/2

Next up, let's take a look at the new option you can pass to Task.yield_many. Previously, you could tell Task.yield_many to timeout and stop waiting for tasks if a certain time limit was exceeded. Now, you can provoke that same behavior if a certain number of tasks has been reached. You can do so by providing the limit option.

Let's say you have the following code to spawn ten tasks. Each task will sleep for i number of seconds and then return a message indicating what happened. Then, we yield each task with its result, and iterate over those {task, result} tuples to operate on the result by IO.inspect-ing them:

def MyModule do
  def do_tasks do
    tasks =
      for i <- 1..10 do
        Task.async(fn ->
          Process.sleep(i * 1000)
          "I slept for #{i} seconds"
        end)
      end

    tasks_with_results = Task.yield_many(tasks, timeout: 10000)

    results =
      Enum.map(tasks_with_results, fn {task, res} ->
        # Shut down the tasks that did not reply nor exit
        res || Task.shutdown(task, :brutal_kill)
      end)

    # Here we are matching only on {:ok, value} and
    # ignoring {:exit, _} (crashed tasks) and `nil` (no replies)
    for {:ok, value} <- results do
      IO.inspect(value)
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

This code will continue to yield tasks and their results until all the tasks are done or the timeout is exceeded. Now, you can use the limit: task_num option instead of timeout, like this:

tasks_with_results = Task.yield_many(tasks, limit: 1)
Enter fullscreen mode Exit fullscreen mode

If we execute our function in IEx, you'll see that the code stops waiting for tasks after just one task is yielded:

iex> MyModule.do_tasks()
"I slept for 1 seconds"
["I slept for 1 seconds"]
Enter fullscreen mode Exit fullscreen mode

If the limit is reached before the default timeout (5000), or before all the tasks are done, then Task.yield_many returns immediately without triggering the :on_timeout behaviour. You can dig further into this functionality in the docs.

This new functionality gives you even more fine-grained control over how your program orchestrates async tasks.

The Logger.levels/0 Function

The last enhancement I'll highlight here is the new Logger.levels/0 function. This function returns the list of all available log levels:

iex> Logger.levels()
[:error, :info, :debug, :emergency, :alert, :critical, :warning, :notice]
Enter fullscreen mode Exit fullscreen mode

This provides a quick reminder of the levels you can use to configure your logger.

Wrap Up

With the 1.16 release, Elixir continues to be a compelling choice for developers looking for:

  • An excellent developer experience
  • A welcoming and supportive community
  • Strong and opinionated development guidelines
  • Powerful language capabilities to meet the needs of various programming scenarios.

The new developments in diagnostics, documentation, and language functionality continue to make Elixir a pleasure to work with. Better, more detailed diagnostic messages for compiler errors show Elixir's emphasis on developer experience, making it easier than ever before to identify and fix these errors.

Improved docs and the addition of the anti-patterns guide empowers both experienced and newbie Elixir devs to write clean and standardized code. And minor language improvements, like the functions we explored above, show Elixir's continued commitment to language excellence.

Happy coding!

P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, subscribe to our Elixir Alchemy newsletter and never miss a single post!

Top comments (2)

Collapse
 
aaronblondeau profile image
aaronblondeau

Elixir is my language to learn for 2024 so I am just getting started. Posts like this make me even more excited about the language!

Collapse
 
ctnkaan profile image
Çetin Kaan Taşkıngenç

Ah Elixir the language I want to learn so much but could not find the time to do so. This blog post reminded me of it so I think I'll dive into Elixir this week 🚀