Ooh, fun question!
So, personally, I don’t really agree with that part of the docs:
- I’ve seen code bases with tiny files which were still vulnerable to the types of mutation bugs described there. And, in these cases, I also had a harder time coming up to speed on the codebase because I had to jump around between files so much!
- I’ve also worked in codebases with God objects, and despite being in very long files I would not say they were easier to work with, or had more or less mutation bugs.
If you can get mutation similar bugs regardless of file length, file length is not the problem, mutation is. And besides, the kinds of mutation bugs described in this paragraph are not possible in Elm because Elm does not allow mutation, not because Elm encourages a particular file length.
So to answer your question directly, no, I wouldn’t say that Elm files should be longer. They may be longer as a consequence of the things I’m gonna say below, but that’s a consequence only—not a goal!
Now, with that out of the way, we can get to the more interesting question: what is the right file length? I think to answer that you’ve gotta answer “what is a file for?” The basic answer is encapsulation: we can choose to expose or hide things in modules, put different functions in different modules, et cetera. The second answer is namespacing—we can have a toString
in both Foo
and Bar
—but we can’t have namespacing without encapsulation so that’s secondary.
I think that means the right file length is the one where your encapsulation works to prevent bugs, either by preventing encapsulation-violating behavior or making your code easy enough to understand that you can verify it’s doing the right thing. That doesn’t mean long files or short files, but correct-for-the-situation files.
I realize that this basically boils down to “it depends”, which I know is a bit unsatisfying. Sorry! Maybe it will help if I share some assorted things I use to know if a module needs to be broken up (or not):
-
Yes, split if: there’s a function that should not be able to access internals of a data structure, but which can because it lives in the same module. (Of course, the opposite is true too… if there’s a function that needs to be able to access internals but can’t the module boundary may be in the wrong place!)
-
Yes, split if: there are two independent data structures in the same module. One smell here is if I have to write
fooToString
for the main one and barToString
as well, and foo
and bar
are independently valuable, it may be time to split them into their own modules.
-
No, don’t split if: there is just “too much code” in a file. I’ve split apps into
Model.elm
, Update.elm
, View.elm
before and almost always regretted it. Even if it’s a little tricky to navigate a long file, it’s way better than having to perform module gymnastics to prevent import loops etc.
All that said: I would gently encourage people to write long files and then break them up instead of making tiny files before it’s actually necessary. I’ve been using Elm for something like half a decade now and the only reliable way I’ve found to put module boundaries in the right places is to wait and see what the right place is. I suppose that means I am, in effect, advocating for long files over short ones but again: that’s a consequence, not a goal!
Finally, you mention The Life of a File. Good talk, and I’d recommend also watching Make Data Structures by Richard Feldman.