Elixir v1.4 released
Elixir v1.4 brings new features, enhancements and bug fixes. The most notable changes are the addition of the Registry
module, the Task.async_stream/3
and Task.async_stream/5
function which aid developers in writing concurrent software, and the new application inference and commands added to Mix.
In this post we will cover the main additions. The complete release notes are also available.
Registry
The Registry
is a new module in Elixir’s standard library that allows Elixir developers to implement patterns such as name lookups, code dispatching or even a pubsub system in a simple and scalable way.
Broadly speaking, the Registry is a local, decentralized and scalable key-value process storage. Let’s break this in parts:
- Local because keys and values are only accessible to the current node (opposite to distributed)
- Decentralized because there is no single entity responsible for managing the registry
- Scalable because performance scales linearly with the addition of more cores upon partitioning
A registry may have unique or duplicate keys. Every key-value pair is associated to the process registering the key. Keys are automatically removed once the owner process terminates. Starting, registering and looking up keys is quite straight-forward:
iex> Registry.start_link(:unique, MyRegistry)
iex> {:ok, _} = Registry.register(MyRegistry, "hello", 1)
iex> Registry.lookup(MyRegistry, "hello")
[{self(), 1}]
Finally, huge thanks to Bram Verburg who has performed extensive benchmarks on the registry to show it scales linearly with the number of cores by increasing the number of partitions.
Syntax coloring
Elixir v1.4 introduces the ability to syntax color inspected data structures and IEx automatically relies on this feature to provide syntax coloring for evaluated shell results:
This behaviour can be configured via the :syntax_colors
coloring option:
IEx.configure [colors: [syntax_colors: [atom: :cyan, string: :green]]]
To disable coloring altogether, simply pass an empty list to :syntax_colors
.
Task.async_stream
When there is a need to traverse a collection of items concurrently, Elixir developers often resort to tasks:
collection
|> Enum.map(&Task.async(SomeMod, :function, [&1]))
|> Enum.map(&Task.await/1)
The snippet above will spawn a new task by invoking SomeMod.function(element)
for every element in the collection and then await for the task results.
However, the snippet above will spawn and run concurrently as many tasks as there are items in the collection. While this may be fine in many occasions, including small collections, sometimes it is necessary to restrict amount of tasks running concurrently, specially when shared resources are involved.
Elixir v1.4 adds Task.async_stream/3
and Task.async_stream/5
which brings some of the lessons we learned from the GenStage project directly into Elixir:
collection
|> Task.async_stream(SomeMod, :function, [], max_concurrency: 8)
|> Enum.to_list()
The code above will also start the same SomeMod.function(element)
task for every element in the collection except it will also guarantee we have at most 8 tasks being processed at the same time. You can use System.schedulers_online
to retrieve the number of cores and balance the processing based on the amount of cores available.
The Task.async_stream
functions are also lazy, allowing developers to partially consume the stream until a condition is reached. Furthermore, Task.Supervisor.async_stream/4
and Task.Supervisor.async_stream/6
can be used to ensure the concurrent tasks are spawned under a given supervisor.
Application inference
In previous Mix versions, most of your dependencies had to be added both to your dependencies list and applications list. Here is how a mix.exs
would look like:
def application do
[applications: [:logger, :plug, :postgrex]]
end
def deps do
[{:plug, "~> 1.2"},
{:postgrex, "~> 1.0"}]
end
This was a common source of confusion and quite error prone as many developers would not list their dependencies in the applications list.
Mix v1.4 now automatically infers your applications list as long as you leave the :applications
key empty. The mix.exs
above can be rewritten to:
def application do
[extra_applications: [:logger]]
end
def deps do
[{:plug, "~> 1.2"},
{:postgrex, "~> 1.0"}]
end
With the above, Mix will automatically build your application list based on your dependencies. Developers now only need to specify which applications shipped as part of Erlang or Elixir that they require, such as :logger
.
Finally, if there is a dependency you don’t want to include in the application runtime list, you can do so by specifying the runtime: false
option:
{:distillery, "> 0.0.0", runtime: false}
We hope this feature provides a more streamlined workflow for developers who are building releases for their Elixir projects.
Mix install from SCM
Mix v1.4 can now install escripts and archives from both Git and Hex, providing you with even more options for distributing Elixir code.
This makes it possible to distribute CLI applications written in Elixir by publishing a package which builds an escript to Hex. ex_doc
has been updated to serve as an example of how to use this new functionality.
Simply running:
mix escript.install hex ex_doc
will fetch ex_doc
and its dependencies, build them, and then install ex_doc
to ~/.mix/escripts
(by default). After adding ~/.mix/escripts
to your PATH
, running ex_doc
is as simple as:
ex_doc
You can now also install archives from Hex in this way. Since they are fetched and built on the user’s machine, they do not have the same limitations as pre-built archives. However, keep in mind archives are loaded on every Mix command and may conflict with modules or dependencies in your projects. For this reason, escripts is the preferred format for sharing executables.
It is also possible to install escripts and archives by providing a Git/GitHub repo. See mix help escript.install
and mix help archive.install
for more details.
Summing up
The full list of changes is available in our release notes. Don’t forget to check the Install section to get Elixir installed and our Getting Started guide to learn more.
Happy coding!