upvote
You are mixing runtime and compile-time dependencies. Runtime dependencies (circular or not) have no impact on compilation performance and stability. Phoenix does include one circular dependency (the layout is rendered by your endpoint and it references your endpoint) but it is a runtime one.
reply
No I'm not. This is often brought up.

I spent 3 months analyzing failures caused by - what looked like - dirty builds but was caused by unstable compilation order. Which is quite obvious.

The solution is dynamic dependency resolution but this causes problem with macros.

The problem is easy to validate. Compile application multiple time and compare hashes. I'm not sure if it's sufficiently visible in bootstrapped Phoenix but I saw it in as small as <1000 LoC toy apps.

reply
Please file a bug report if you can indeed isolate/reproduce it (and please ping me on GitHub once you do)!
reply
I've made one for Phoenix (as per - it creates a cycle), though unstable compilation is a wider issue.

https://github.com/phoenixframework/phoenix/issues/6697

In case you want to see files affected I made extended writeup on my blog - for reference. https://xlii.space/eng/elixir-cycles/

reply
> ... circular dependencies ... compilation races ...

Does Dialyzer understand Elixir? Last I knew, it could only process Erlang source code and BEAM files. Looking around, it seems like folks running Dialyzer against Elixir code are using some "dialyxer" thing.

You talk about circular dependencies causing minor compilation troubles, so it doesn't sound like you're talking about types defined in terms of each other. I might be unaware of something important, given that I've never had the opportunity to do Erlang professionally [0]... but aren't the only "dependencies" of BEAM files the exported functions they call in other modules? If I'm not wrong about that, then what happens when you run Dialyzer against BEAM files compiled from Elixir that has circular dependencies? Do its reports become more reliable, or does the reliability of those reports become irrelevant because the transformations the Elixir build system makes to your code make the structure of the BEAM code difficult to trace back to the Elixir source code?

[0] ...and have written nearly zero Elixir in any context...

reply
Dialyzer (and Dialyxir) were written prior to compiler tracing, and also are based on Erlang's "Typespec" syntax which is a bit lacking.

I still use the Typespec syntax for its documentation benefits, and for catching "dumb" bugs, but as the Elixir compiler has improved I have found Dialyzer to be less relevant as the compiler usually catches things before Dialyzer would as it's not built into the compiler and isn't able to be.

reply
> Does Dialyzer understand Elixir? Last I knew, it could only process Erlang source code and BEAM files.

Once compiled, it boils down to BEAM files that Dialyzer can understand, yes. And the [Dialyxir](https://dialyxir.hexdocs.pm) wrapper helps translating error messages in Elixir. But, there is a significant limitation compared to plain Erlang: Elixir protocols (which are quite used in core parts of the language) are not an Erlang construct, so Dialyzer will be clueless about them, just accepting any term. Enum.map(nil, & &foo/1) or to_string(%{}) will be invisible to it.

reply
There's dialyxir which is wrapper to Dialyzer and I found it work fine on pure (non Phoenix) code.

As for how the problem manifests: even obvious contract violations stops being shown (making it feel like "Dialyzer is useless") but the second tell is very long check times (tens of seconds up to minutes).

reply
Cool, cool.

  [W]hat happens when you run Dialyzer against BEAM files compiled from Elixir that has circular dependencies? Do its reports become more reliable, or does the reliability of those reports become irrelevant because the transformations the Elixir build system makes to your code make the structure of the BEAM code difficult to trace back to the Elixir source code?
reply