Elixir 1.18 ships with “type checking of function calls, alongside gradual inference of patterns and return types.”
I’m particularly excited about that because of the help the compiler can provide when changing return values for a function.
Consider the following two modules:
- A
MyCalendar
which has anensure_valid_week
function that returns:ok
or:error
if the week is less than 52 weeks. And,- An
Exercise
module that usesMyCalendar
to validate a week, returning a success or error message.
defmodule MyCalendar do@weeks_in_a_year 52def ensure_valid_week(value) when is_integer(value) doif value <= @weeks_in_a_year do:okelse:errorendendend
defmodule Exercise dodef do_something doweek = 2case MyCalendar.ensure_valid_week(week) do:ok -> "We did it!":error -> "Oh, oh! That's not a valid week."endendendIt’s a contrived example, but I think you get the idea.
Now, what happens if we change the error return value in
ensure_valid_week
from:error
to{:error, :invalid_week}
, but we don’t update the pattern match in theExercise
module?
defmodule MyCalendar do@weeks_in_a_year 52def ensure_valid_week(value) when is_integer(value) doif value <= @weeks_in_a_year do:okelse+ {:error, :invalid_week}- :errorendendendIf we’re using Elixir 1.17, our compiler won’t tell us anything about the change. We hope our tests catch the error before we ship the code to production.
But if we’re using Elixir 1.18, when we compile, we get a beautiful warning:
$ mix compileCompiling 1 file (.ex)warning: the following clause will never match::errorbecause it attempts to match on the result of:MyCalendar.ensure_valid_week(week)which has type:dynamic(:ok or {:error, :invalid_week})typing violation found at:│7 │ :error -> "Oh, oh! That's not a valid week."│ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~│└─ lib/exercise.ex:7: Exercise.do_something/0🤩 That’s a huge improvement that we, as Elixir users, get for free!
Of course, it’s not perfect yet. If we don’t reference the
MyCalendar
module directly, but instead make it the default argument that we can pass into the function (i.e. only making it one of the possible calendar implementations), then the compiler doesn’t know for sure that the:error
value won’t match.To see that, change the
MyCalendar.ensure_valid_week(week)
call and makeMyCalendar
the default argument for the function:
defmodule Exercise do- def do_something do+ def do_something(calendar \\ MyCalendar) doweek = 2- case MyCalendar.ensure_valid_week(week) do+ case calendar.ensure_valid_week(week) do:ok -> "We did it!":error -> "Oh, oh! That's not a valid week."endendendNow,
mix compile
in Elixir 1.18 will not give us a warning.But that makes sense. It’s now possible to pass a
calendar
argument that does return ``:error-- so there are scenarios in which the
:errorpattern would match. The compiler just doesn't know anymore that
MyCalendar` is the only possible implementation.It would be amazing if the compiler could figure out what are all the call sites to
do_something/1
, calculate all their return values, and let us know if:error
is ever going to be returned. But I don’t know if that’s in the cards. For now, I’ll happily take the huuuuge improvement.
continue reading on www.elixirstreams.com
⚠️ This post links to an external website. ⚠️
If this post was enjoyable or useful for you, please share it! If you have comments, questions, or feedback, you can email my personal email. To get new posts, subscribe use the RSS feed.