christopheradams / elixir_style_guide

A community driven style guide for Elixir

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

I'd love to see a conventional format for the "with" keyword

laserlemon opened this issue ยท comments

I've seen:

with {:ok, user} <- User.get(id),
     {:ok, posts} <- Post.by_author(user)
do
  {:ok, posts}
else
  error -> error
end
with \
  {:ok, user} <- User.get(id),
  {:ok, posts} <- Post.by_author(user)
do
  {:ok, posts}
else
  error -> error
end

amongst others including using the do: form of the block, although I'm not sure if that format works with an else clause as well.

I love the second form if it weren't for that pesky backslash. For that reason, I lean toward the first version.

Given that in the first version, the indentation depth will not change with the code (as opposed to, like I've seen in many styles

some_variable = foo
                .bar # same with |>, point being renaming the variable means reindenting a lot

the only point speaking for the \ version is what editors will tolerate or support.

Howdy. You might find this with syntax gist I did awhile back to try to get to a with convention that allows for (optional) comments before each step. I try to write self-documenting code where comments aren't necessary, but sometimes it is very useful to be able to do this.

Because of this, the first version you have will have the comment before the entire with statement, which I dislike because it looks like you're commenting on the with statement as a whole as opposed to the first clause.

# 1. This comment is to the left and looks like it's for the `with` statement
with {:ok, user} <- User.get(id),
     # 2. This comment is to the right, etc. 
     {:ok, posts} <- Post.by_author(user)
do
  {:ok, posts}
else
  error -> error
end

So I posted a suggested tweak on elixir forum, wherein I was pointed to the \ syntax. I thought that was great! ...only the \ operator doesn't allow for the immediately following line to be a comment.

But thankfully there was another alternative mentioned that uses parentheses:

with(
  {:ok, user} <- User.get(id),
  {:ok, posts} <- Post.by_author(user)
) do
  {:ok, posts}
else
  error -> error
end

This has several benefits:

  1. It allows for comments like I mentioned.
  2. It is less asymmetrical than the \ version
  3. It always has the same indentation.
  4. The indentation allows for the maximum usage of the width of the line.
  5. The do block is cleanly and clearly separated from the preceding clauses and else clause.

This is now how I do all of my with statements. I thought you might like to hear about some of the reasoning behind the \ version and consequences of it and the parentheses version.

Interesting. I haven't seen the need yet for a comment in that space. Otherwise I would prefer the backslash version for being a tiny bit less noisy.

@bill-mybiz from this reply it seems the no comment after / is a bug in the compiler. Did you ever follow up on that?

@schnittchen Yes, at first I also liked the \ version which is why I went with that when it was suggested. But I prefer the paren version now, because it not only allows for the comments, but also it cleans up and resolves the do issue - , do: vs do...end.

@christopheradams Deja yep. ๐Ÿ˜„

OK, so not a bug then if I read that right @bill-mybiz. thanks for the link.

@christopheradams Yes, it was a great insight to compare the behavior against Ruby and bash. I'm really enjoying the decision making going on with Elixir core stuff!

This is the style presented in the docs, although it may be hard to see since it's shown in an IEX session:

# with just do (a)
with :ok <- :ok,
     :ok <- :ok,
  do: :ok

# with else (b)
with :ok <- :ok,
     :ok <- :ok do
  :ok
else
  :error ->
    :error
end

(This indentation style is supported by Emacs elixir-mode. What does everyone else's editor do with this?)

issue refs: 348, 349, 350

Here's my summary so far:

  1. indenting successive with arguments is the preferred style
  2. do vs. do: if you have more than one line to do, or an else, then use do
  3. do on a line by itself or not? docs and emacs prefer not. some of you prefer yes. (no other style rules have do on a line by itself.)
  4. if you must comment above each with argument, use parentheses

What to you think?

I opened PR #124 for this. I didn't add a rule about commenting above with clauses, since it's relatively complicated and uncommon. But if there were a rule it might read something like this (not sure how to describe what to do with do):

  • If you need to add a comment above each with clause, surround them with
    parentheses. Put each clause on its own line, indented two spaces.

    with(
      # look up foo
      {:ok, foo} <- fetch(opts, :foo),
      # look up bar
      {:ok, bar} <- fetch(opts, :bar)
    ) do
      {:ok, foo, bar}
    end

@christopheradams I think overall your changes look like they reflect the majority of core developers. ๐Ÿ‘

I mainly pointed out the paren version to show the reasoning behind the \ version, which I personally had mentioned both on the Elixir Forum and in the compiler issue I linked to (so perhaps I was responsible for their confusion). I tried to explain the dynamics of the paren version from my personal experience, because I use with a lot, i.e. almost every primary function. I used to do it the ways you are listing, but for me, they were insufficient. โ›” ๐Ÿ˜‰

As an aside, I found your observation that no other rules have the do keyword on its own line very interesting. I'd say the reason that the do keyword is on the same line in other rules is that the bulk of code is usually inside the do block. The with construct is the only one that has the arguments be the bulk of the code with the do block being most commonly a simple return value.

An alternative indentation for do: is shown in this rule from a different guide:

with {:ok, date} <- Calendar.ISO.date(year, month, day),
     {:ok, time} <- Time.new(hour, minute, second, microsecond),
     do: new(date, time)

If that style catches on we can consider it.