Elixir vs Ruby: Naming conventions

SeriesPhoenix on Rails clock5 min read

How does naming files, modules, variables and functions in Elixir compare to Ruby and its convention-driven world?

First of all, I highly recommend two resources for basic information about Elixir naming conventions. Number one is a dedicated chapter in the official documentation. Even though I've browsed through Elixir docs so many times, it took a while until I've noticed this excellent, information-rich article. Also, there's an extensive, opinionated community driven style guide.

Files and modules

You can see in official naming guide that files should be named by underscored module name, just like in Ruby. But as opposed to Rails coupled with autoload, it's a convention instead of a hard rule. That's because there may come a time when such a convention must be broken for reasons hard to anticipate up front when creating it. Here's one example why: acronyms.

As Rubyist, you may feel hesitant and insecure about putting acronyms in module names. Well, you really shouldn't anymore. Without inference of module names from file names (or table names in case of Ecto), you can name your modules (and models) like REST, API or HTMLParser all day long. Elixir won't make you regret it.

Nested modules

Here's what you may see in some Ruby projects or gems:

lang:ruby
module CodeAnalyzer
  module Engine
    module Parser
      module Ruby
        # code indented way too much
      end
    end
  end
end

Here's how some developers used to bypass this issue in the following way:

lang:ruby
module CodeAnalyzer
module Engine
module Parser
module Ruby
  # code indented a bit weird 
end
end
end
end

In Elixir you won't have to come up with such inventions as here modules can be nested without weird or deep indentation. Here's what will suffice:

lang:elixir
defmodule CodeAnalyzer.Engine.Parser.Ruby do
  # nested module code
end

In Ruby something like this will also work, but only if parent module was already defined. But sometimes, there's just no obvious place to define that parent module. You don't have to worry about such things in Elixir as here the whole module name is just an atom.

App namespacing

In Elixir, every single OTP app prefixes all of its modules with its own name. You won't find any top-level ApplicationHelper or User here. This results in a much more organized code structure throughout all parts of the app. Combine that with explicit module aliasing via the alias directive and you'll always know what you're referring to in the code, even if only the last part of the module is used. 

This convention also makes it so much easier to switch your growing project to multi-app architecture (called umbrella in Elixir/OTP dictionary).

Unused _variables

You'll find a convention for marking unused variables with underscore prefix in both Ruby and JavaScript these days. But Elixir takes it to the next level. First, this convention is intuitively integrated into pattern matching. It's also checked by the compiler itself, instead of making you rely on external tools such as Rubocop or ESLint. That's great because this check is so crucial for finding bugs and typos in the code that in my opinion its place is compiler, not linter.

It turns out you can also use it when naming module functions for authoring selective imports. Basically, functions such as _this_one won't get imported by the import directive, unless invoked with such intent in the following way: 

lang:elixir
import MyModule, only: [_this_one: 0]

def immutability!

In Ruby, there are at least two major conventions for naming methods with trailing bangs:

  1. Standard library uses it to mark methods that change the object in-place instead of returning new one. Someone has really tried to tame the mutability here.
  2. Ruby on Rails and ActiveRecord (and many others) use it to mark methods that throw instead of returning false when the operation fails.

It's really unfortunate that such ambiguity was born in Ruby, to say the least. As a result, not only you have the mutability that on its own makes it so hard to reason about anything but the simplest Ruby/Rails codebase, but now you don't even know what really to expect from those damn bang functions. It's such a mess!

It would be a little hard for Elixir to go with the first pattern, so having no other choice it went with the second... But seriously, trailing bang comes with a really clear meaning in Elixir: do you want to handle exception? It fits perfectly into the whole OTP philosophy. Basically, you should just let exceptions be exceptions and throw and let OTP restart things for you. Then, you can remove bangs and pattern match on each spot that is to be a supported negative scenario.

There's just one concern that I have. According to official docs, there's also a second convention for bangs in Elixir: the one for macros. It's basically about declaring scope-unsafe variables in macros. While writing macros is a whole separate domain and on its own this convention seems OK, I hope it won't open a door for more and more conventions that will make Elixir universe as messy as the Ruby one already is. But I guess that's a responsibility for a community to take.

Summary

Naming conventions is yet another example of how Elixir inherits (no pun intended) much of what made everyday coding in Ruby nice and pleasant. Of course, it does so while trying to improve on limitations of Ruby, even when it comes to seemingly little things, such as setting proper, fool-proof rules for naming things consistently and beautifully.

We, web developers, do have an opposite example at hand: JavaScript. I know it's not that much of a challenge to bring JS up as an antagonist in a programming language story of any kind, but it's a fact that after writing JS for many years I still don't know how to name the damn files...

That's why I believe that at the end of the day it's those little things that make you smile at the code that you've written. And they make others understand it and so the community grows.