Runtime dependency on module atom value

55 views
Skip to first unread message

Victor Rodrigues

unread,
Oct 9, 2020, 3:44:38 PM10/9/20
to elixir-lang-core
Hi, I’m working towards reducing the recompilation dependencies on a project with hundreds of modules.

Digging through this, I’ve stumbled into something that I didn’t expect:

When a function returns modules as values, that creates a runtime dependency to these modules.

Taking the example below:

```elixir
defmodule ImplSelector do
  def resolve(impl_type) do
    case impl_type do
      :foo ->
        ImplA

      :bar ->
        ImplB

      :omg ->
        :"Elixir.ImplC"

      :wut ->
        NonExistingMod
    end
  end
end
```

```shell
➜ mix xref graph --source lib/impl_selector.ex
lib/impl_selector.ex
├── lib/impl_a.ex
│   └── lib/impl_behaviour.ex (compile)
└── lib/impl_b.ex
    └── lib/impl_behaviour.ex (compile)
```

It creates runtime deps from ImplSelector to Impls A and B. C is left because I’ve used :”Elixir.ImplC”, and as a non existing module doesn’t raise any warnings (just an atom after all right), I wonder if we could remove these dependencies too, or if it’s really desired.

We rely a lot on behaviours, and figuring these from instance configurations. I think that would have a good impact on the dependency graph overall, if it’s something that could go.

Thanks!

----

A bit off-topic, but wanted to share that when I started this saga this week, Elixir 1.11 only reduced ~10% the modules on recompilation, but now, as I’ve fixed a few important dependencies (still a lot to go), I can see already ~50% less modules recompiling in 1.11 as compared with same code in 1.10.4! 🌈

José Valim

unread,
Oct 9, 2020, 4:09:50 PM10/9/20
to elixir-l...@googlegroups.com
Unfortunately we cannot remove those, precisely because they may be invoked when passed to another function.

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/216ce42b-9fe0-4e28-a5b9-acdde5414c41n%40googlegroups.com.

Victor Rodrigues

unread,
Oct 9, 2020, 4:41:15 PM10/9/20
to elixir-lang-core
Thanks, now I get it! 👍

It seems this kind of function might not be the best strategy dependency-wise to resolve behaviours at runtime, since the consumer of the `ImplSelector.resolve/1` function ends up with all the cruft from all implementations, transitively.

Thinking now perhaps processes would be the only way when we’d really want to isolate these calls from propagating impl deps.

Adam Lancaster

unread,
Oct 9, 2020, 4:55:09 PM10/9/20
to elixir-l...@googlegroups.com
Knowing nothing about your app, and therefore nothing about whether it’s a good idea or not, I’m curious if the dependancies change if you use protocols instead of the ImplSelector?

Victor Rodrigues

unread,
Oct 9, 2020, 5:38:09 PM10/9/20
to elixir-lang-core
Thanks for the idea, I'm not sure if that would help improve much Adam, I've played a bit and it seems it would be just slightly worse:

```shell
lib/usage.ex
└── lib/impl_selector.ex
    ├── lib/impl_a.ex
    │   └── lib/impl_behaviour.ex (compile)
    └── lib/impl_b.ex
        └── lib/impl_behaviour.ex (compile)
```

```shell
lib/usage.ex
└── lib/proto.ex
    └── lib/proto_impl.ex
        ├── lib/impl_a.ex
        │   └── lib/impl_behaviour.ex (compile)
        ├── lib/impl_b.ex
        │   └── lib/impl_behaviour.ex (compile)
        └── lib/proto.ex (compile)
```

In the end, from what I get, if the dependency chain is clean of compile deps in between (ideally `(compile)` should just be in the leaves, as in the tree above), this shouldn't increase recompilations, I'll try to attack all of these in our codebase, if I manage to get there, then these runtime deps shouldn't be a problem 👍

Reply all
Reply to author
Forward
0 new messages