Setting variables while pattern matching in with special form

Hopefully it’s not an anti-pattern but I am using the with special form in at least half my non-trivial functions (especially in “context” modules), it’s awesome and might be the biggest thing I miss when having to work in other languages.

Anyway, I’ve been writing code like this:

def update_password(%User{} = user, params) do
    with %Changeset{valid?: true} = chset <- User.update_password_changeset(user, params),
         attrs = Changeset.apply_changes(chset),
         :ok <- Credentials.set_credential(:password_v1, user.username, password: attrs.password) do
      {:ok, user}
    else
      chset = %Changeset{valid?: false} -> {:error, chset}
      {:error, reason} -> {:error, reason}
    end
  end

My question is… when setting variables while pattern matching should the variable name be before or after the pattern? In other words, should line 2 instead be: with chset = %Changeset{valid?: true} <- User.update_password_changeset(user, params) and should line 7 instead be: %Changeset{valid?: false} = chset -> {:error, chset}. Does it matter or is it a question of style? I have been testing both variants and they both seem to work as expected but I can’t find documentation on it.

There’s no difference.

This on line 2:

%Changeset{valid?: true} = chset <- User.update_password_changeset(user, params)

is just like a pattern match, where <- is replaced with =.

%Changeset{valid?: true} = chset = User.update_password_changeset(user, params)

That said, what I’ve seen the most in the Elixir community is most specific to least specific. So, what you have in line 2 rather than line 7. My best guess as to why is that it looks more like a pattern match that can fail than the other does. So, it’s more consistent with a standalone pattern match.

with %Changeset{valid?: true} = chset <- User.update_password_changeset(user, params), do: chset

# looks like this, which would fail if it didn't match
%Changeset{valid?: true} = chset

# However, this
with chset = %Changeset{valid?: true} <- User.update_password_changeset(user, params), do: chset

# looks like this, which would not fail because it's a very general match
chset = %Changeset{valid?: true}
3 Likes

Thanks that makes sense. I guess you are right maybe the reason for the convention is it looks more like a pattern match that can fail versus just binding a variable.

Also I’ve noticed that same order is consistent with matching and destructuring in a function parameter:

def some_func(%{username: username} = user) do