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] 
> <javascript:>> 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] <javascript:>.
>> 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/9f15dd15-bfe9-423f-8055-4a344d07e876%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to