defmodule Bonsai.LedgerController do
use Bonsai.Web, :controller
alias Bonsai.{Repo, Ledger, Income, Expense}
plug Guardian.Plug.EnsureAuthenticated, handler: Bonsai.SessionController
plug :scrub_params, "ledger" when action in [:create]
def index(conn, _params) do
ledgers = Ledger.all(conn.assigns[:organization_id])
render(conn, "index.json", ledgers: ledgers)
end
# Create an income
def create(conn, %{"ledger" => %{"type" => "income"}} = ledger_params) do
case Income.create(get_params(conn, ledger_params["ledger"]), user_id(conn)) do
{:ok, ledger} ->
conn
|> put_status(:created)
|> render("show.json", ledger: ledger)
{:error, changeset} ->
conn
|> put_status(:unprocessable_entity)
|> render("error.json", errors: changeset.errors)
end
end
end
Iâm using pattern match for the ledger_params but I have to use Income.create(get_params(conn, ledger_params["ledger"]) nested, in most cases when one does not pattern match the params would look %{"amount" => 10, "contact" => "Boris"}, but in this case it looks like %{"ledger" => %{"amount" => 10, "contact" => "Boris"}} is there a way to make it the params map wihout the nesting.
Took me a while to track that back to Yoda Conditions[1]. But it does bring up an interesting point: Why are both ways even allowed? In this particular case to me
seems clearer because elements pertinent to âthe patternâ seem to be neatly on the left side while what seems to be an alias/variable is out of the way on the right. In
def show(conn, %{"messenger" => messenger} = params) do
...
end
Itâs good to remember that the keys to the params map will always be strings, and that the equals sign does not represent assignment, but is instead a pattern match assertion.
A variable can only be assigned on the left side of =:
In the above examples the âvariableâ seems to be quite happy on the right side of the = match operator. So the rules seem to be far more nuanced:
Outside of a pattern, the pattern itself and any match variables have to be on the left side of =.
Inside of a pattern, match variables can occur of either side of the = match operator. For parameter level matching the pattern always extends over the entire parameter so that variables can be on either side of the = match operator.
I.e. both
def show(conn, %{"messenger" => messenger} = params) do
...
end
and
def show(conn, params = %{"messenger" => messenger}) do
...
end
seem to be valid.
Iâm just looking for some insight so that I can adjust my mental model to make sense of this again.
[1] Used the tactic a lot way before 2010 in C/C++ coding - only ran into the term later because of JS linters - and promptly forgot about it.
If you look at the projects under the elixir-lang org on GitHub and at Phoenix (as a fairly sizeable project), youâll see that putting the var on the right is the prevalent style.
I personally prefer it because the pattern is the more important part of the function head â it directly influences the choice of the function clause that is going to be invoked for a given call. The variable name becomes relevant inside the function body, i.e. after Iâve visually parsed the function head and decided that this is the clause I want to dive into.
In order to more easily remember the difference between the âassignmentâ-matching and the function clause matching, think about the former as matching from right to left, e.g.
x <- <pattern>
whereas when a function clause is matched, the arguments are coming from the top:
With this mental model it is clear that it doesnât matter whether it is <pattern> = params or params = <pattern>, the end result is going to be the same.
Do keep in mind though, this last rule also has effect in nested matches that are not necessarily part of a function head, e.g.
Both ways, pattern = var and var = pattern, are allowed because of what a = in a pattern really means. It is an alias and actually means that both sides much match, so the LHS of = must match and the RHS as well. You could actually write a pattern (x,y,z} = {a,b,c} which is perfectly legal but not really very useful.
The compiler does some checking that to ensure that it is possible for both sides to match, so if you try:
def foo({a,b,c} = {x,y} do ... end
then the compiler will complain. You can use = anywhere in a pattern not just at the top-level, for example [x|[y|z]=tail].
I personally prefer pattern = var in a pattern because that to me much more says a pattern then the other way around.
@alco: Thank you for your explanation on right-to-left vs top-down.
And @peerreynders: Very nice post!
It seems that putting the name at the right vs the left of the = is just a matter of personal preference.
Why to put it at the left:
It makes it âless hard to readâ because the bind order is similar as to what it is outside of matches.
As the names are to the left of the =, you never need to worry with the pin (^) operator (of course, In function clauses, this does not apply anyway)
It is more natural for people coming from languages like Haskell, which has the assigns-@ syntax: list@(head:tail) which would translate to list = [head | tail] in Elixir.
Why to put it at the right:
It makes the pattern itself more readable as it comes first. (As long as the pattern is no more than two levels deep, of course)
Multiple large projects, such as elixir-lang.org and Phoenix, have chosen to adopt this style.
In the end, I think this might be a lot like little-endian vs big-endian, or (dare I say it?) spaces vs tabs.
@rvirding: def foo(a = b = c = d = e = f = g = h), do: [a,b,c,d,e,f, g, h]
@alco, @rvirding, and @Qqwy - thank you for your great explanations and clarifications. It seems to boil down to:
The Elixirâs âGetting Startedâ documentation assertion that a âvariable can only be assigned on the left side of =â refers to the case where the match operator is used to initiate a pattern match. Inside the pattern the match operator works both ways.
The implicit top-down pattern match visualization for function parameters is a great reminder that the explicit portion of the argument is the pattern.
As long as âpersonal preferenceâ isnât synonymous to âcompletely arbitraryâ - but as you pointed out reasonable arguments can be made for either way depending on the circumstances.