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

Reply via email to