elt547

elt547

Obsession with line length and hideous formatting (am I writing elixir wrong?)

What do y’all think about the defaults of the mix formatter? I often find long lines being being turned into multiple breaks with weird indentation. The output is so often hideous and completely unreadable.

This happens almost exclusively in long function definitions with pattern matching. Since pattern matching function parameters is a core part of elixir and incidentally very wordy, it doesn’t make sense for formatters to make such a fuss over line length. It’s making me doubt whether I’m writing elixir in the intended manner.

Look at the example below. It’s almost impossible to visually separate the function definition from the first line of the function.

This version is objectively easier to read, but apparently wrong according to the flagship formatter. Is 103 columns really that sinful for a function definition?

First 10 of 28 Posts Switch mode

LostKobrakai

LostKobrakai

Line length is one of the few things you can configure on the formatter. If you feel the default is to short increase it.

elt547

elt547 OP

Thanks, that’s not actually in the hex docs so I wasn’t aware. That should do for now!

sorentwo

sorentwo

Oban Core Team

Personally, 9 out of 10 times I see a function clause that spans multiple lines as a code smell. Ask yourself:

  • Do you need all of that pattern matching in the function head?
  • Can you match some in the body instead?
  • Can you write a private guard to reduce matching or guard clauses?
  • Can you use a case within a function (or all compiles down to a case anyhow)?
sodapopcan

sodapopcan

Agreed. I’ve come across “over-matching” to the point where it takes a second to figure out which matches are required for flow control.

handle_event is pretty ubiquitous so it’s not as big a deal, but I always write it like this:

def handle_event("save", %{"profile" => profile_params}, socket) do
  profile = socket.assigns.profile
  # ...
end

I feel that setting profile in the body is a lot less noisy than nested destructuring, especially when combined with capturing the whole value. My other reasoning is that the event name and param arguments are specific to the function itself whereas profile already exists in state, so destructuring it the head isn’t telling me anything new—I already know that I always have access to profile if I have a socket.

It’s all a matter of taste, of course, and totally up to you!

elt547

elt547 OP

After playing with it a while, I really want to configure the formatter to never breakdown a long function head. I like how it formats other lines, so changing the line length breaks that preference. Is this possible?

elt547

elt547 OP

Are you able to give examples of the last two in alleviating this issue? Thanks!

elt547

elt547 OP

Thanks for the kick, I think this might be a case of me being new to the language. I find myself doing the same thing with “over-piping” the socket struct instead of making a regular function call with the socket as first param.

{:noreply, socket |> assign(:other, "other")}

# instead of

{:noreply, assign(socket, :other, "other")}
baldwindavid

baldwindavid

Think you mean subjective :slight_smile: I actually find the formatted version easier to read. It’s easier for me to scan vertically than horizontally and I don’t like long lines. I tend to think one can get used to just about any formatting if you look at it enough, but also agree with others that too many matches in a function head can be hard to parse.

sorentwo

sorentwo

Oban Core Team

Sure thing!

  1. Here’s an example of writing a private guard from Oban: oban/lib at main · oban-bg/oban · GitHub
  2. Here’s an example of reducing the number of function clauses and minimizing the head size by matching with a case in the function body (it is from Oban Pro, so no link)
  def job_to_key(%{worker: worker, args: args}, partition) do
    case partition do
      %{fields: ["worker"], keys: []} ->
        with_hash({worker, nil})

      %{fields: ["args"], keys: keys} ->
        with_hash({nil, Map.take(args, keys)})

      %{fields: [_, _], keys: keys} ->
        with_hash({worker, Map.take(args, keys)})

      _ ->
        with_hash({nil, nil})
    end
  end
thiagomajesk

thiagomajesk

The formatter is something that I have a bittersweet feeling about and it’s perhaps the tool that I least like in the ecosystem. It’s great to keep a codebase consistent across teams if you don’t really want to think about it.

I hope for more configuration options in the future while keeping some opinionated default because I really care how my code reads. I also got my code butchered by the formatter a lot in the past because it doesn’t care about nuances as I do.

TLDR: It’s great for standardization, but the idea that a tool would decide how your code reads better than yourself is nuts to me.

There’s some value in those considerations overall if you want to increase readability in some cases, but I disagree with the premise of considering it as code smells. Mainly because there’s no real underlying issue in your code if you don’t follow this. It’s all a matter of preference on how you want to read your code. BTW, extracting code elsewhere is not always preferred for legibility.

That’s exactly my point, thanks @baldwindavid! Legibility is something really subjective and I think the main gripe some people might have with the formatter is that it’s not really flexible in the first place. It actually enforces very opinionated defaults which are also very subjective.

Where Next?

Trending in Discussions Top

AstonJ
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
2977 91561 914
New
byu
@chrismccord : I just saw the Extract AGENTS.md from Phoenix.new into phx.new generator commit to the phoenix project. My initial shotgu...
New
arcanemachine
I was working on an Ecto migration and I needed a timestamp. So, for the nth time, I looked up the different data types for timestamps, a...
New
AstonJ
Just a general thread to post chat/news/info relating to AI/ML stuff that may be relevant for Nx now or in the future. Got anything to sh...
New
type1fool
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
alexslade
Fly’s CEO posted this recently - Turn And Face The Strange · The Fly Blog It says that Fly is going all-in on sprites, which is a worry ...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
akoutmos
@hugobarauna and I (Alex Koutmos) have been hard at work on writing a book on Nerves that takes you from simply blinking LEDs to building...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New

We're in Beta

About us Mission Statement