Elixir Notes: Module Names and File Names Mismatch
Short version: module names and file names don’t have to match! There is a convention, but mix
doesn’t enforce it.
Background
I became fully aware of this after I renamed a directory under lib/
, but everything kept working…
This wasn’t what I expected. I ran my tests expecting to be told what I had broken.
At first, I thought it was a caching problem. I removed the
_build
and deps
directories and … everything still worked!
Why believe that names are important?
It was partly my experience with other languages. Coming from Go (or Java), we expect moving files around to break the build.
But, mostly, it was BECAUSE most Elixir projects and examples are so
well-behaved! Inside a file named lib/some/thing.ex
, we expect to find a module named Some.Thing
.
This naming is used consistently in all the Elixir and Phoenix books I read.
It’s a convention…
If you’re feeling unsatisfied/surprised by this behavior, you’re not alone. I found a thread by Joe Armstrong (no less!) discussing this. And here’s the answer, from José Valim himself:
Here’s another thread on the topic, and José’s answer:
Finally, here’s the text from the Naming Convention reference:
Detour: a naming recipe
How to convert from a full path to a module name?
e.g. lib/text_client/prompter.ex -> TextClient.Prompter
- start with a full path (dir + file name)
- remove the leading
lib/
ortest/
directory - remove the extension
- capitalize each word
- replace
/
with.
- remove all
_
Implications
I read “it doesn’t matter” many times, but never understanding EXACTLY what that meant in practice.
I ran a few experiments to convince myself of the behavior. Here’s my new mental model:
- mix reads all
.ex
files underlib/
, anywhere - all these files could be one big concatenated file
- all defined modules get compiled into their own
.beam
file
What does that imply?
The name of a module and the file name where it is defined don’t need to be related:
Dog
module defined incat.ex
The directory structure doesn’t matter:
Animal.Mammal.Dog
module defined indog.ex
Dog
module defined inanimal/mammal/dog.ex
Multiple modules can be defined in one file:
Dog
andCat
modules both defined inanimals.ex
Combinations of the above:
Animal.Mammal.Dog
andCat
modules both defined insome/thing/unrelated.ex
Is this a good thing or a bad thing?
I was looking for understanding: what is allowed and not allowed. I had been working with Elixir for months before REALLY wondering how this worked… I had been following the naming convention and everything was great.
But with great power comes great responsibility: the naming convention exists so that we don’t have to grep around all the time – use the convention, please. The alternative is complete chaos…
(that being said, there are times when it’s useful to bend the rules)
Back to my original problem – it would have been useful to “detect” that I had moved some files to a different directory and that some renaming would be necessary to, once again, comply with the convention.
I wondered if there were tools to help catch this (and briefly considered writing my own). In the end, I found that credo_naming will detect exactly these types of mismatches.