Skip to content

Commit 19d3aee

Browse files
committed
Mention module attributes as compile-time constants
1 parent f766078 commit 19d3aee

File tree

1 file changed

+44
-29
lines changed

1 file changed

+44
-29
lines changed

lib/elixir/pages/getting-started/module-attributes.md

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
# Module attributes
22

3-
Module attributes in Elixir serve two purposes:
3+
Module attributes in Elixir serve three purposes:
44

5-
1. They serve to annotate the module and its functions.
6-
2. They work as a temporary module storage to be used during compilation.
5+
1. as module and function annotations
6+
2. as temporary module storage to be used during compilation
7+
3. as compile-time constants
78

89
Let's check these examples.
910

@@ -76,6 +77,8 @@ defmodule MyServer do
7677
end
7778
```
7879

80+
> #### Newlines {: .warning}
81+
>
7982
> Do not add a newline between the attribute and its value, otherwise Elixir will assume you are reading the value, rather than setting it.
8083
8184
Trying to access an attribute that was not defined will print a warning:
@@ -115,7 +118,7 @@ end
115118

116119
This can be useful for pre-computing values and then injecting its results into the module. This is what we mean by temporary storage: after the module is compiled, the module attribute is discarded, except for the functions that have read the attribute. Note you cannot invoke functions defined in the same module as part of the attribute itself, as those functions have not yet been defined.
117120

118-
Every time an attribute is read inside a function, Elixir takes a snapshot of its current value. Therefore if you read the same attribute multiple times inside multiple functions, you end-up increasing compilation times as Elixir now has to compile every snapshot. Generally speaking, you want to avoid reading the same attribute multiple times and instead move it to function. For example, instead of this:
121+
Every time we read an attribute inside a function, Elixir takes a snapshot of its current value. Therefore if you read the same attribute multiple times inside multiple functions, you end-up increasing compilation times as Elixir now has to compile every snapshot. Generally speaking, you want to avoid reading the same attribute multiple times and instead move it to function. For example, instead of this:
119122

120123
```elixir
121124
def some_function, do: do_something_with(@example)
@@ -130,31 +133,43 @@ def another_function, do: do_something_else_with(example())
130133
defp example, do: @example
131134
```
132135

133-
> #### Attributes as constants {: .warning}
134-
>
135-
> You may want to use module attributes as constants for plain data types. However, functions themselves are sufficient for this role. For example, instead of defining:
136-
>
137-
> ```elixir
138-
> @hours_in_a_day 24
139-
> ```
140-
>
141-
> You should prefer:
142-
>
143-
> ```elixir
144-
> defp hours_in_a_day(), do: 24
145-
> ```
146-
>
147-
> You may even define a public function if it needs to be shared across modules. It is common in many projects to have a module called `MyApp.Constants` that defines all constants used throughout the codebase.
148-
>
149-
> This holds true for any data structure, as long as they don't have any expression in them. For example, you may specify a system configuration constant as follows:
150-
>
151-
> ```elixir
152-
> defp system_config(), do: %{timezone: "Etc/UTC", locale: "pt-BR"}
153-
> ```
154-
>
155-
> Given data structures in Elixir are immutable, only a single instance of the data structure above is allocated and shared across all functions calls, behaving precisely as a constant, as long as it doesn't have any executable expression.
156-
>
157-
> The use case for module attributes arise when you need to do some work at compile-time and then inject its results inside a function. They may also be useful for defining constants to be used in patterns or guards, as an alternative to `defguard/1`.
136+
## As compile-time constants
137+
138+
Module attributes may also be useful as compile-time constants. Generally speaking, functions themselves are sufficient for the role of constants in a codebase. For example, instead of defining:
139+
140+
```elixir
141+
@hours_in_a_day 24
142+
```
143+
144+
You should prefer:
145+
146+
```elixir
147+
defp hours_in_a_day(), do: 24
148+
```
149+
150+
You may even define a public function if it needs to be shared across modules. It is common in many projects to have a module called `MyApp.Constants` that defines all constants used throughout the codebase.
151+
152+
You can even have composite data structures as constants, as long as they are made exclusively of other data types (no function calls, no operators, and no other expressions). For example, you may specify a system configuration constant as follows:
153+
154+
```elixir
155+
defp system_config(), do: %{timezone: "Etc/UTC", locale: "pt-BR"}
156+
```
157+
158+
Given data structures in Elixir are immutable, only a single instance of the data structure above is allocated and shared across all functions calls, as long as it doesn't have any executable expression.
159+
160+
The use case for module attributes arise when you need to do some work at compile-time and then inject its results inside a function. A common scenario is module attributes inside patterns and guards (as an alternative to `defguard/1`), since they only support a limited set of expressions:
161+
162+
```elixir
163+
# Inside pattern
164+
@default_timezone "Etc/UTC"
165+
def shift(@default_timezone), do: ...
166+
167+
# Inside guards
168+
@time_periods [:am, :pm]
169+
def shift(time, period) when period in @time_periods, do: ...
170+
```
171+
172+
Module attributes as constants and as temporary storage are most often used together: the module attribute is used to compute and store an expensive value, and then exposed as constant from that module.
158173

159174
## Going further
160175

0 commit comments

Comments
 (0)