Here is my alternative explanation:
- In any language, it is impossible (or impractical) to prevent from building a bad value.
- The bad value is problematic when side effects happens from it (e.g. calling database, calling APIs, updating cache…)
- Elixir makes easy to find where side effects happen, and naturally encourages to organize code around the data flow, not around the data itself (like “object” in OOP)
- Once using such pattern, it is easy to place “validation” at the right place.
For example, if you put the validation around data - then you’ll end up one giant module with lots of validation for all different purposes - like Rails fat models!
Going back to the author of this post… @laiboonh
Why do we want to make impossible to a user struct having negative integer for age? Only because it doesn’t make sense? The reason is that “it is incorrect input for functions using the age”. It is totally fine to pass such value to functions using only name - isn’t it?
Example - Date.
There is Date.new/4
, and the Date module doc says:
Developers should avoid creating the Date structs directly and instead rely on the functions provided by this module as well as the ones in third-party calendar libraries.
But it is possible to any code to create a value. How does Date
handles it?
# you can create a invalid value
%Date{year: 1, month: 0, day: -1}
# some operations do not care about the bad value
%Date{year: 1, month: 0, day: -1} |> Date.to_erl()
{1, 0, -1}
# it is validated when needed
%Date{year: 1, month: 0, day: -1} |> Date.add(1)
# ** (ArgumentError) invalid date: 0001-00--01
# (elixir 1.10.4) lib/calendar/iso.ex:1331: Calendar.ISO.ensure_day_in_month!/3
# (elixir 1.10.4) lib/calendar/iso.ex:521: Calendar.ISO.date_to_iso_days/3
# (elixir 1.10.4) lib/calendar/date.ex:544: Date.add/2
Please note that I’m not saying static type is meaningless. There are some benefits (e.g. not only catching certain errors on compile time, but potentially better performance in certain cases by avoid expensive runtime validation).
However, in Elixir (or dynamic typed FP) you can put such expensive validation only when needed, at the exact time, instead of “magic behind encapsulation” (e.g. you can do it when you transform API inputs before passing to your domain module) - so you can keep your world “clean” easily.