- Published on
Elixir v1.20: Now a gradual typed language
- Authors

- Name
- aimode.news
- @aimode_news
Elixir v1.20 Publication: is now a progressive type of language
In 2022, we announced an effort to add a class theory to Elixir. In June 2023, we published an award-winning paper on the design of Elixir-type systems and stated that our work was moving from research to development.
Through Elixir v.1.20, we have completed the first development milestone, i.e., the type of execution extrapolation and a gradual type check of each Elixir program without the need to introduce a type note. This means that Elixir is increasingly reporting dead codes and verified errors: if implemented, typing violations will fail when running. Elixir is able to efficiently identify validated bugs in existing procedures without the need to introduce developer costs and with very low rates of misstatement.
In this bulletin, we'll break down the goals of the type system, dynamic()
type means in Elixir, and how it discovers verified errors. In particular, we achieved well in the “If T: Type Downscaling Baseline” benchmark test. Elixir passed 12 of the 13 categories, indicating that it can recover accurate type information from the normal Elixir code, which we use to find verified errors in dynamic type programs.
This type of system was created thanks to cooperation between CNRS and Remote. Development is currently sponsored by Fresha and Tidewave.
Type, in my Elixir?
Our goal is to introduce a type of system:
- What?
sound - Type system extrapolation and allocation type consistent with program behaviour
- What?
Gradual - Elixir Type Systems
Dynamic ()
type, you can use when checking the type of variable or expression while running. Without dynamic()
, Elixir type system is expressed as static type system -
Developer friendly - Use basic assembly operations to describe, achieve, and group types: combined, intersect and deny (and therefore it is a collection theory type system) with clear error messages
The introduction of a type system into existing languages is a complex change. Our first milestone, therefore, is to achieve a type system without introducing a type note, but still provide value to developers by finding dead code and verified errors. It's done through dynamic.
type, this is very different in Elixir from other progressive languages. Let's break it down.
Dynamic ()
Type
Many progressive types of systems have anny()
Type, from the point of view of type systems, usually means “everything normal” and no type of violation is reported. On the other hand, Elixir's progressive type is called dynamic()
It has two important attributes: compatibility and narrowing.
In the static type system, when you have the type of integer () or binary () shape
And you call a function that must accept two types. However, because the type system does not accurately capture the intent of all our programs, this may lead to misstatement. For example, the following simple codes are used:
dev percentage or error(value) is integer(value)
Value or error=
Execute if value > 1
Value
Otherwise...
“Not good”
End
...more code...
Execute if value > 1
Value or error / 100
Otherwise...
Spring.upcase (value or error)
End
End
Although value or error
Type is integer() or binary()
, Operator /
Accept numbers and string.upcase only
Accepts only binary files/strings, and the above program is valid and does not cause anomalies when running. However, the type system still reports two violations because of the type provided/
And Spring.upcase
Not the subtype of acceptance.
While the above procedures can be better prepared as no type of irregularities, the type systems will always reject effective procedures and, if Elixir introduces too many misstatements in the existing code repository, it will quickly weaken confidence in the type systems. So Elixir's progressive type system is marked with value or error
The variable type above is dynamic(integer() or binary()
, which means the type is either integer() or binary()
On run.
When calling a function using dynamic()
Type, Elixir will issue a type violation only if the type provided does not intersect with the type accepted. In the program above, even/
Only numbers, dynamics (integer() or binary())
Could be an integer ()
There are also no types of irregularities, given that the types of acceptances and offers are not interchangeable. But if we change the procedure to:
Value or error=
Execute if value > 1
Value
Otherwise...
“Not good”
End
Map.fetch!
Because of Map.fetch!
Need a map data structure, and value or error
It can only be integer or binary at the time of operation, and the types of acceptances and offers are not interchangeable, which can lead to violations. This is called compatibility properties, which explains how Elixir only reports verified errors.
However, if we could not find many errors at the outset, it would be useless to report only those errors that had been verified. We can solve this by ensuring that Elixir's dynamic type can be reduced. Take this code:
def add a and b (data) do
Data.a + Data.b
End
In the above program, data
Start with Dynamic ()
Type. Then we use it as a data.a
and datab
Elixir optimizes the data by adding a number of operators
Variable type %..., a: number(), b: number()}
It means it's a simultaneous.
and b
Fields with values (possibly any other field, leading by ...
I'm not sure. So if you forget to choose .b
field and write:
def add a and b (data) do
Data.a + data
End
Data
Map that will be reduced to shape %... a: number()} first
, and then try to be a number()
This could trigger a violation.
In other words, dynamic()
type in Elixir works effectively as a scope that can be refined when it is used throughout the process and reports violations when the type check is out of scope. This contrasts with other progressive types of systems, which use dynamic types to discard all types of information.
Behind the scenes, our type extrapolation and type-check algorithm behavior is as if we had a dynamic note for all parameter types.
I don't know. Once we have introduced the user type note, the Elixir type system will run like any static type language, just dynamic()
Not used. Every time you cross the static-dynamic boundary, we develop new technologies to ensure that our gradual typing is correct without additional running-time checks.
Typing protection, sentences, etc.
Most of the work behind this version is to introduce type checks and downsize several structures. Let's see some of them.
When it comes to guards, we can infer and gather, intersect and deny:
dev example(x, y) is list(x) and is integer(y)
The code above correctly extrapolates x
A list and y
is an integer.
default({:ok, x}=y) is binary(x) or is integer(x)
The extrapolation above x is binary or integer, y
A binary containing: ok Group
As the first element, binary or integer is the second element.
default(x) is map key(x,:foo)
Code extrapolation above x
A map containing: foo
key, for %{..., foo:dynamic()}
I don't know. Remember the leader...
indicates that the map may have other keys.
dev example(x) is map key(x,:foo)
The code above extrapolates x
A map without: foo
Key, type: %..., foo: not set()}
I don't know. Therefore, x.foo
There are types of conflicts in the function.
You can also use expressions that assert the size of the data structure:
dev example(x) when tuple size(x) < 3
Elixir will track the cells correctly up to two elements, so visit elem(x, 3)
Typing irregularities will be issued. For maps and lists, we convert size checks to empty checks. In other words, Elixir can look at complex protection, extrapolation type and use this information to find errors in the code.
When it comes to structures like casework,
and condition statement, Elixir uses the information in the previous sentence to refine the subsequent sentence:
Case Systems.get env("SOME VAR")
Zero-:not found
Value - {ok, string.upcase}
End
System.get env("SOME VAR")
Return zero
Or binary()
I don't know. 'Cause the first line matches the nil
Type system knows the value
No more zeros.
, so it can only be a binary
, this allows the second sentence to be subject to type checks. Reducing the size of the sub-rule would also help the type system to find redundant sentences and dead codes in the existing code repository.
In addition, we type many functions in the standard library with metagroups and maps. You can find more details in your distribution notes.
Improved compilation time
Elixir v1.20 has also again reduced the time of compilation, particularly for applications operating on multi-nuclear computers. Although the BEAM language is often very efficient, our comprehensive benchmarking test now lists Elixir's construction tool as one of the fastest. If you want to contribute more examples and scenes, start the discussion so that we can provide a transparent set of benchmarks and results.
It also introduced a new compiler option named: Modeule definion
, it specifies whether the module definition should be: compiled
(Default) or: interpreted
I don't know. This may shorten the time for the compilation of large projects and will not affect
The file is written on the disk.
He was executed. You can enable it by setting Elixirc options:
In your mix.ex
I don't know. Read the document for more information.
What's next?
The biggest question we face is: when will Elixir introduce a new type of signature using the cluster theory? As I recently discussed in ElixirConf EU 2026, we still have research and development work to do. We will only introduce the type of signature:
- If we are satisfied with the type of system in Elixir v.1.20.
- If we can do this effectively,
- If we can achieve the parameter type effectively
- If we can effectively achieve cross-map key pairs as a list of possible solutions
Once these issues are resolved, we will begin to explore and discuss the definition of a typology structure and, finally, a type of signature. As usual, we will deliver information to the community through news and the Elixir forum.
We thank all those who tried the candidate version, ran the benchmark test and provided us with feedback! Try Elixir v1.20 and remember to fix all the mistakes it'll find!
