Newbie question regarding Pattern Matching (in Programming Phoenix 1.4)

Hi,

I’m new to Elixir\Phoenix, and am currently working my way through the Programming Phoenix book.

Could someone please help me understand the following line of code: ‘{:error, %Ecto.Changeset{} = changeset} ->’, the complete function is below.

I get that the Accounts.create_user(…) is returning a tuple, and the CASE is matching the tuple, to either {:ok, user} or {:error, %Ecto.Changeset{} = changeset}.

But I don’t get why the empty structure ‘%Ecto.Changeset{}’, part of the second tuple. Could we have written: ‘{:error, changes}’. Most examples I’ve seen have a var in left hand side, i.e. %Ecto.Changeset{var: var} = changeset, and so I understand we are able to use assigned ‘var’ in our function, but I don’t know why the empty struct. Is it to ensure the second arg is an %Ecto.Changeset{}, or is there more?

def create(conn, %{"user" => user_params}) do
    case Accounts.create_user(user_params) do
      {:ok, user} ->
        conn
        |> put_flash(:info, "User: #{user.name} successfully created")
        |> redirect(to: Routes.user_path(conn, :index))

      **{:error, %Ecto.Changeset{} = changeset} ->**
        render conn, "new.html", changeset: changeset
    end
  end

Kind Regards,

Jas

Yep. Nope. Here’s an example:

defmodule Dog do
  defstruct [:name, :age] 
end

defmodule My do
  def go(%Dog{}), do: IO.puts "matched a Dog struct"
  def go(%{}), do: IO.puts "matched a map"
  def go(_), do: IO.puts "matched something else"

  def test do
    My.go(%{a: 1, b: 2})
    My.go(%Dog{name: "Ralph", age: 3})
    My.go(10)
  end
end

In iex:

~/elixir_programs$ iex a.exs
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)

iex(1)> My.test()
matched a map
matched a Dog struct
matched something else
:ok

iex(2)>

You should be aware that a struct will also match %{}, i.e. a map, so the order of the go() clauses is important.

Could we have written: ‘{:error, changes}’.

Yes, but that is pattern allows a more permissive match because changes is a variable and as such it will match anything.

Most examples I’ve seen have a var in left hand side, i.e. %Ecto.Changeset{var: var} = changeset,

The %Ecto.Changeset{var: var} pattern requires two things to be true for a match:

  1. The thing on the right of the match operator, =, (that’s right it’s not an assignment operator like in other languages–it’s more powerful in elixir) has to be an Ecto.Changeset struct.

AND

  1. The struct has to have the key :var.

If there is a match, then the var variable:

         :var key (an atom)
                 |
                 V       
%Ecto.Changeset{var: var}
                      ^
                      |
                var variable 

will be assigned the value corresponding to the :var key in the struct. That type of pattern is used to extract values from a struct. That is not the intent/need of the original pattern.

2 Likes

Accounts.create_user(user_params) is a context function that could return an {:error, reason} tuple for any number of reasons. In {:error, %Ecto.Changeset{}} %Ecto.Changeset{} is only one possible reason but that is the particular reason the case clause is intended to handle.

%Ecto.Changeset{} = changeset simply binds that empty structure to the name changeset for reuse.

1 Like

Precisely that. In pattern matching, an “empty map” %{} pattern represents any map. An “empty struct” %StructName{} pattern checks if the struct is of this type (this pattern is the same as %{__struct__: StructName}).

So if there’s another {:error, value} value than a Ecto.Changeset, this code will raise. If you don’t pattern match on %Ecto.Changeset{} here, there’s a chance you’d pass something else to render.

Check out the Keyword lists and maps chapter and the Structs chapter in the Getting Started guide. It’s also mentioned in Map documentation.

2 Likes

Yes, the reason is as you suspect so you can guarantee the second element in the tuple is of a specific type.

The only extra bit of context you might find helpful is that other functions may still return an error tuple without a changeset but this code only cares if it’s a changeset error.

Many thanks, these replies are really helpful.

Esp for someone coming from a Java\OO background, these concepts are quite new, and your replies have given my limited understanding some further depth.

Much appreciated

1 Like