I am trying to understand why transitive compile-time dependency is there, and what’s a good strategy to remove it. The mix app for example below is available here.
In a nutshell, I have these 3 modules:
defmodule A do
import B, only: [valid_string: 1]
def check(string) when valid_string(string), do: "OK"
defmodule B do
defguard valid_string(string) when string in ~w(read write)
def by_the_way do
defmodule C do
def something do
IO.puts("Just passing by")
Dependencies from this module form a what’s known as transitive compile-time dependency. This can be confirmed by running mix href command:
mix xref graph --label compile-connected
└── lib/b.ex (compile)
If I remove by_the_way function from module B, mix xref no longer reports a transitive compile-time dependency. Could someone explain to me why A transitively depends on C, even though A is only interested in importing the macro from B, and doesn’t really care about the function by_the_way or its implementation?
What’s a good strategy to remove such transitive compile time dependency? Is it like separating macros and functions in different modules?
That’s a quite a few assumptions about the knowledge of the compiler. In this specific case the imported functionality is a guard, which indeed cannot depend on by_the_way due to limitations of guards. But it could also be a normal macro, which might call by_the_way at some point during execution.
Elixir tracks dependencies at the module level. One common approach to address this is to define constants (which you may access at compile-time, such as invoice_states) and guards that are used across multiple modules into a separate module that only defines constants/guards (and does not call anything else).