I have a function that returns a map which I would like to define a custom type for.
The process of doing it has been pretty easy, except when dealing with key_type
from the docs here:
%{required(key_type) => value_type} # map with required pairs of key_type and value_type
For this new type I'm defining, key_type
should be any string that is a representation of the current year or a year in the future.
Keys like "2019"
or "2025
are good, but keys like "2008"
are no good.
I was hoping this would work:
@type home_sharing_rate :: number
@type home_sharing_days :: [number]
@type home_sharing_booking :: { home_sharing_days, home_sharing_rate }
@type income_year :: "2019" | "2020" | "2021" | "2022" | "2023"
@type income_month :: "Jan" | "Feb" | "Mar" | "Apr" | "May" | "Jun" | "Jul" | "Aug" | "Sep" | "Oct" | "Nov" | "Dec"
@type earned_home_sharing_income_summary :: %{ required(income_year) => %{ required(income_month ) => [home_sharing_booking] } }
but no such luck. However, I see that Elixir has some built-in types like non_neg_integer()
which lead me to believe I could define a type as an enumeration, but now I'm not so sure. Is there any way to do this?
TypeScript has plenty of tools to make this happen so I was kind of hoping Elixir did as well. To me, it seems odd that a type can't be defined as an enumeration of its values, but perhaps the way that Elixir intends types to be used is different than Typescript and I'm just missing something.
Typespecs only handle binaries specified with <<>>
syntax, and, unfortunately, only the size and unit are supported. This piece of code would produce a more descriptive error message:
defmodule Foo do
@typep income_year :: <<"2019"::binary-size(4)>> | <<"2020"::binary-size(4)>>
@type earned :: %{ required(income_year) => any() }
end
results in
** (CompileError) iex:4: invalid binary specification,
expected <<_::size>>, <<_::_*unit>>, or <<_::size, _::_*unit>>
with size and unit being non-negative integers
That said, you might resort to @typep income_year :: <<_::binary-size(4)>>
at best.
I would suggest to use the guard instead of type whether you are to expect to deal with this type:
@allowed_years ~w|2019 2020|
Enum.each(@allowed_years, fn year ->
def do_something(%{unquote(year) => value} = earned) do
...
end
end)
# sink-all clause
def do_something(_), do: raise "Year is not allowed."