You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: lib/elixir/pages/anti-patterns/macro-anti-patterns.md
+108Lines changed: 108 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -287,3 +287,111 @@ For convenience, the markup notation to generate the admonition block above is t
287
287
> function, so your module can be used as a child
288
288
> in a supervision tree.
289
289
```
290
+
291
+
## Untracked compile-time dependencies
292
+
293
+
#### Problem
294
+
295
+
This anti-pattern is the opposite of ["Compile-time dependencies"](#compile-time-dependencies) and it happens when a compile-time dependency is accidentally bypassed, making the Elixir compiler is to track dependencies and recompile files correctly. This happens when building aliases (in other words, module names) dynamically, either within a module or within a macro.
296
+
297
+
#### Example
298
+
299
+
For example, imagine you invoke a module at compile-time, you could write it as such:
300
+
301
+
```elixir
302
+
defmoduleMyModuledo
303
+
SomeOtherModule.example()
304
+
end
305
+
```
306
+
307
+
In this case, Elixir knows `MyModule` is invoked `SomeOtherModule.example/0` outside of a function, and therefore at compile-time.
308
+
309
+
Elixir can also track module names even during dynamic calls:
310
+
311
+
```elixir
312
+
defmoduleMyModuledo
313
+
mods = [OtherModule.Foo, OtherModule.Bar]
314
+
315
+
for mod <- mods do
316
+
mod.example()
317
+
end
318
+
end
319
+
```
320
+
321
+
In the previous example, even though Elixir does not know which modules the function `example/0` was invoked on, it knows the modules `OtherModule.Foo` and `OtherModule.Bar` are referred outside of a function and therefore they become compile-time dependencies. If any of them change, Elixir will recompile `MyModule` itself.
322
+
323
+
However, you should not programatically generate the module names themselves, as that would make it impossible for Elixir to track them. More precisely, do not do this:
324
+
325
+
```elixir
326
+
defmoduleMyModuledo
327
+
parts = [:Foo, :Bar]
328
+
329
+
for part <- parts do
330
+
Module.concat(OtherModule, part).example()
331
+
end
332
+
end
333
+
```
334
+
335
+
In this case, because the whole module was generated, Elixir sees a dependency only to `OtherModule`, never to `OtherModule.Foo` and `OtherModule.Bar`, potentially leading to inconsistencies when recompiling projects.
336
+
337
+
A similar bug can happen when abusing the property that aliases are simply atoms, defining the atoms directly. In the case below, Elixir never sees the aliases, leading to untracked compile-time dependencies:
To address this anti-pattern, you should avoid defining module names programatically. For example, if you need to dispatch to multiple modules, do so by using full module names.
352
+
353
+
Instead of:
354
+
355
+
```elixir
356
+
defmoduleMyModuledo
357
+
parts = [:Foo, :Bar]
358
+
359
+
for part <- parts do
360
+
Module.concat(OtherModule, part).example()
361
+
end
362
+
end
363
+
```
364
+
365
+
Do:
366
+
367
+
```elixir
368
+
defmoduleMyModuledo
369
+
mods = [OtherModule.Foo, OtherModule.Bar]
370
+
371
+
for mod <- mods do
372
+
mod.example()
373
+
end
374
+
end
375
+
```
376
+
377
+
If you really need to define modules dynamically, you can do so via meta-programming, building the whole module name at compile-time:
378
+
379
+
```elixir
380
+
defmoduleMyMacrodo
381
+
defmacrocall_examples(parts) do
382
+
for part <- parts do
383
+
quotedo
384
+
# This builds OtherModule.Foo at compile-time
385
+
OtherModule.unquote(part).example()
386
+
end
387
+
end
388
+
end
389
+
end
390
+
391
+
defmoduleMyModuledo
392
+
importMyMacro
393
+
call_examples [:Foo, :Bar]
394
+
end
395
+
```
396
+
397
+
In actual projects, developers may use `mix xref trace path/to/file.ex` to execute a file and have it print information about which modules it depends on, and if those modules are compile-time, runtime, or export dependencies. This can help you debug if the dependencies are being properly tracked in relation to external modules. See `mix xref` for more information.
0 commit comments