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]> 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].
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.
For more options, visit https://groups.google.com/d/optout.

Reply via email to