It is just a little sugar in `happy_path`, unsure it should go in something
like `with`, all it does is transform something like:
```elixir
@foo {:ok, x} = y
```
Into something like:
```elixir
{:foo, {:ok, x}} = {:foo, y}
```
It is convenient and would be nice to have though, great for if you need to
differentiate a specific error without polluting the `happy_path` with tons
of braces! ;-)
On Monday, June 20, 2016 at 3:13:02 PM UTC-6, Peter Hamilton wrote:
>
> I'm intrigued by @tags. It reminds me of how I modularize my code in Elm,
> where I have an Update type in each module and then wrap it in a tag in my
> application Module.
>
>
> On Mon, Jun 20, 2016 at 1:09 PM José Valim <[email protected]
> <javascript:>> wrote:
>
>> Thank you Overmind, that's a great summary!
>>
>> Honestly, I am a bit puzzle because this code:
>>
>> happy_path do
>> {:ok, b} = a
>> {:ok, d} = b
>> c(d)
>> end
>>
>>
>> Is exactly the same as:
>>
>>
>> {:ok, b} = a
>> {:ok, d} = b
>> c(d)
>>
>>
>> i.e. Elixir developers already code the happy_path, so why wrap it in a
>> do block?
>>
>> I also don't like it changes the semantics of = based on the existence of
>> else. In particular, with "with", I can explicitly choose in the same
>> construct if I want the conditional (<-) or the usual matching semantics
>> (=).
>>
>> I do agree a common way of sharing the error handling code could be
>> welcome but the current syntax is a bit misleading (as handle_conn(conn)
>> will be called with 2 arguments and not 1).
>>
>> Further thoughts? :D
>>
>> *José Valim*
>> www.plataformatec.com.br
>> Skype: jv.ptec
>> Founder and Director of R&D
>>
>> On Mon, Jun 20, 2016 at 7:29 PM, OvermindDL1 <[email protected]
>> <javascript:>> wrote:
>>
>>> Yes, sorry, thought the documentation helped at the link but it could
>>> use more examples. Three sections then. :-)
>>>
>>> *General Use*
>>> First of all happy_path uses matching syntax instead of <- syntax, so it
>>> flows far more naturally:
>>> ```elixir
>>>
>>> happy_path do
>>> {:ok, b} = a
>>> {:ok, d} = b
>>> c(d)end
>>>
>>> ```
>>> The above happy_path macro translates the given code into:
>>> ```elixir
>>>
>>> case(a) do
>>> {:ok, b} ->
>>> case (b) do
>>> {:ok, d} -> c(d)
>>> endend
>>>
>>> ```
>>> And like the `with` in Elixir 1.3 it also has an else:
>>> ```elixir
>>>
>>> happy_path do
>>> {:ok, b} = a
>>> c(b)else
>>> {:error, x} -> xend
>>>
>>> ```
>>> The else is matched to anything that does not match the `happy_path`, if
>>> no match is there then a normal `MatchError` is thrown by the `case`'s
>>> themselves if `happy_path!` was used, else the unmatched value is returned
>>> (I usually use `happy_path!`, but a few cases of `happy_path` are useful).
>>> I know the 'with' syntax of `<-` will not be changed because of backwards
>>> compatibility, but the above functionality could be added to the `do`
>>> section, would be nice to get a returning/throwing version distinction too
>>> like `happy_path`/`happy_path!`.
>>>
>>> The `happy_path` also supports guards:
>>> ```elixir
>>>
>>> happy_path do
>>> x when not is_nil(x) = some(foo)
>>> x + 1end
>>>
>>> ```
>>>
>>>
>>> *Default Handler*
>>>
>>> From my own code an example (mentally replace `happy_path` with `with`
>>> for comparison):
>>>
>>> ```elixir
>>>
>>> def index(conn, _params) do
>>> happy_path! do # This is normally a hairy set of embedded case's or a
>>> dozen function calls
>>> {:ok, {uid, _obj}} = verify_uid(uid)
>>> true = conn |> can?(index(%Perms.Auth.Permission{uid: uid, perm_name:
>>> :_}))
>>> query = from p in Permission,
>>> where: p.uid == ^uid
>>> conn
>>> |> render(:index,
>>> uid: uid,
>>> permissions: Repo.all(query),
>>> newable_permissions: MyServer.PermsLoader.get_all_perms(),
>>> )
>>> else
>>> {:error, :not_a_number} -> bad_request(conn)
>>> {:error, :invalid_uid} -> unprocessable_entity(conn)
>>> {:error, :atom_invalid} -> bad_request(conn)
>>> {:error, :struct_invalid} -> unprocessable_entity(conn)
>>> {:error, err} -> internal_server_error(conn)
>>> false -> unauthorized(conn)
>>> endend
>>>
>>> ```
>>>
>>> Lot of error cases, I ended up having a lot more so I branched it off
>>> into a separate function:
>>>
>>> ```elixir
>>>
>>> def index(conn, _params) do
>>> happy_path! do
>>> {:ok, {uid, _obj}} = verify_uid(uid)
>>> true = conn |> can?(index(%Perms.Auth.Permission{uid: uid, perm_name:
>>> :_}))
>>> query = from p in Permission,
>>> where: p.uid == ^uid
>>> conn
>>> |> render(:index,
>>> uid: uid,
>>> permissions: Repo.all(query),
>>> newable_permissions: MyServer.PermsLoader.get_all_perms(),
>>> ) else # Maybe some other cases first before falling to the
>>> generic one for this controller err -> handle_errors(err, conn)
>>> endend
>>> defp handle_error(%{valid?: false}, conn) ,do: bad_request(conn)
>>> defp handle_error({:error, :not_a_number}, conn) ,do: bad_request(conn)
>>> defp handle_error({:error, :invalid_uid}, conn) ,do: not_found(conn)
>>> defp handle_error({:error, :invalid_pidx}, conn) ,do: not_found(conn)
>>> defp handle_error({:error, :no_item_at_pidx}, conn) ,do: not_found(conn)#
>>> Snip a few more that eventually falls down to a catch-all that returns a
>>> 500 error
>>>
>>> ```
>>> Which works and would with Elixir 1.3's with it looks like as well,
>>> however having that else at the bottom makes it easy to forget adding the
>>> default handler (over 30 of this style so far here, I've forgotten more
>>> than one a few times as I copy the happy_path header part around...).
>>> Happy recently added a method to allow setting a default handler that is
>>> called if there are no matches in the else branch:
>>>
>>> ```elixir
>>>
>>> def index(conn, _params) do
>>> happy_path(else: handle_errors(conn)) do
>>> {:ok, {uid, _obj}} = verify_uid(uid)
>>> true = conn |> can?(index(%Perms.Auth.Permission{uid: uid, perm_name:
>>> :_}))
>>> query = from p in Permission,
>>> where: p.uid == ^uid
>>> conn
>>> |> render(:index,
>>> uid: uid,
>>> permissions: Repo.all(query),
>>> newable_permissions: MyServer.PermsLoader.get_all_perms(),
>>> ) # else # # Maybe some other cases first before falling to
>>> the generic one for this controller
>>> # # In most cases this else block does not exist though, only for
>>> special cases, the
>>> # # default handles most. endend
>>>
>>> defp handle_error(%{valid?: false}, conn) ,do: bad_request(conn)
>>> defp handle_error({:error, :not_a_number}, conn) ,do: bad_request(conn)
>>> defp handle_error({:error, :invalid_uid}, conn) ,do: not_found(conn)
>>> defp handle_error({:error, :invalid_pidx}, conn) ,do: not_found(conn)
>>> defp handle_error({:error, :no_item_at_pidx}, conn) ,do: not_found(conn)#
>>> Snip a lot more that eventually falls down to a catch-all that returns a
>>> 500 error
>>>
>>> ```
>>> Any error that happens that is not already handled by the else will be
>>> piped into the else default handler, so the `handle_errors(conn)` is
>>> effectively turned into a last `else` case of: err -> err
>>> |> handle_errors(conn)
>>>
>>>
>>> *Tags*
>>> This is a nice-to-have but not really something necessary, or perhaps
>>> even wanted (I would not put it in the base language as-it-is, maybe only
>>> after some syntax modification if at all, probably not)
>>>
>>> From the docs:
>>> ```elixir
>>>
>>> happy_path do # using the `foo` tag
>>> @foo {:ok, x} = y
>>> # is exactly the same as
>>> {:foo, {:ok, x}} = {:foo, y}else
>>> {:foo, {:error, e}} -> "Foo error"end
>>>
>>> ```
>>>
>>> And lastly an example from production code of how `happy_path` has saved
>>> my sanity from case-hell and/or function call explosion:
>>> ```elixir
>>> def delete(conn, %{"uid" => param_uid, "name" => param_name, "pidx" =>
>>> param_pidx} = params) do happy_path! do {:ok, {uid, _obj}} =
>>> verify_uid(param_uid) {:ok, {perm_struct, struct_name, name}} =
>>> verify_perm_exists(param_name) true = conn |> can?(delete(%Perms.Auth.
>>> Permission{uid: uid, perm_name: name})) {:ok, permission} =
>>> verify_single_record(Repo.get_by(Permission, uid: uid, perm_name:
>>> name)) {:ok, idx, _perm} = verify_pidx(permission.perms, param_pidx) {
>>> :ok, redirect_to} = case Map.get(params, "redirect", nil) do "show" -> {
>>> :ok, permissions_path(conn, :show, uid, name)} _ -> {:ok,
>>> permissions_path(conn, :index, uid)} end case
>>> List.delete_at(permission.perms,
>>> idx) do [] -> Repo.delete!(permission) conn |> put_flash(:info, gettext
>>> "Permission deleted successfully.") |> redirect(to:
>>> permissions_path(conn, :index, uid)) perms ->
>>> Permission.changeset(permission,
>>> %{perms: perms}) |> Repo.update! conn |> put_flash(:info, gettext
>>> "Permission
>>> deleted successfully.") |> redirect(to: redirect_to) end else err ->
>>> handle_error(conn, err) end end
>>> ```
>>>
>>> Consequently if anyone has suggestions or improvements on my code and/or
>>> coding style I would be much appreciative. :-)
>>>
>>> On Monday, June 20, 2016 at 10:20:17 AM UTC-6, José Valim wrote:
>>>>
>>>> Hey OvermindDL1!
>>>>
>>>> Everyone is welcome to propose new features to Elixir but it is your
>>>> responsibility to explain why you think it is a good addition. :) So
>>>> please
>>>> do elaborate on the examples where you have used it and why you think they
>>>> are better than with. With more code samples and details, it is going to
>>>> be
>>>> much more productive discussion.
>>>>
>>>> Thank you.
>>>>
>>>>
>>>>
>>>> *José Valim*
>>>> www.plataformatec.com.br
>>>> Skype: jv.ptec
>>>> Founder and Director of R&D
>>>>
>>>> On Mon, Jun 20, 2016 at 6:12 PM, OvermindDL1 <[email protected]>
>>>> wrote:
>>>>
>>>>> The happy_path library ( https://github.com/vic/happy ) seems like it
>>>>> is `with` for Elixir 1.3, but with more features and runs on older Elixir
>>>>> versions; has it been thought about to add those features that it has
>>>>> over
>>>>> `with` to `with`? Specifically it handles tagging and setting a default
>>>>> error_handler for the unhandled error path are two that I find quite
>>>>> useful
>>>>> (especially in phoenix). I know it is likely too late for 1.3, but
>>>>> perhaps
>>>>> it would be a useful addition for 1.4? The `vic` repo in general seems
>>>>> to
>>>>> have a few useful little life-helper libraries for elixir that would be
>>>>> good to take a look at the design of to see if there is anything worth
>>>>> making available to the wider Elixir audience...
>>>>>
>>>>> --
>>>>> You received this message because you are subscribed to the Google
>>>>> Groups "elixir-lang-talk" group.
>>>>> To unsubscribe from this group and stop receiving emails from it, send
>>>>> an email to [email protected].
>>>>> To view this discussion on the web visit
>>>>> https://groups.google.com/d/msgid/elixir-lang-talk/3804944c-86eb-46a7-a581-7d3869ac6144%40googlegroups.com
>>>>>
>>>>> <https://groups.google.com/d/msgid/elixir-lang-talk/3804944c-86eb-46a7-a581-7d3869ac6144%40googlegroups.com?utm_medium=email&utm_source=footer>
>>>>> .
>>>>> For more options, visit https://groups.google.com/d/optout.
>>>>>
>>>>
>>>>
>> --
>> You received this message because you are subscribed to the Google Groups
>> "elixir-lang-talk" group.
>> To unsubscribe from this group and stop receiving emails from it, send an
>> email to [email protected] <javascript:>.
>> To view this discussion on the web visit
>> https://groups.google.com/d/msgid/elixir-lang-talk/CAGnRm4%2BE70%2BNmM0qCMHfVTmDSPhoBWoszXV3rURGL6CJJ9%2B%2BrA%40mail.gmail.com
>>
>> <https://groups.google.com/d/msgid/elixir-lang-talk/CAGnRm4%2BE70%2BNmM0qCMHfVTmDSPhoBWoszXV3rURGL6CJJ9%2B%2BrA%40mail.gmail.com?utm_medium=email&utm_source=footer>
>> .
>> For more options, visit https://groups.google.com/d/optout.
>>
>
--
You received this message because you are subscribed to the Google Groups
"elixir-lang-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/elixir-lang-talk/702ba925-294c-4e5b-acd6-c4dd3f3e94e6%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.