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.

Reply via email to