On Monday, June 20, 2016 at 2:09:55 PM UTC-6, José Valim 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?
>
That it is indeed yes! In Elixir we already try for the 'happy path'. :-)
The point of happy_path is more about being able to handle the (as it puts
it) 'unhappy' path in a singular controlled location, whether by returning
any errors:
```elixir
ret = happy_path do
{:ok, result} = do_something()
{:ok, another} = {:error, :blah}
end
# ret == {:error, :blah} # Assuming that do_something() returned a {:ok,
any} tuplespec
```
Raising any/all errors:
```elixir
ret = happy_path! do
{:ok, result} = {:error, :blah}
end
# ret is not set because the happy_path! (note the !) raised a MatchError,
so this process probably died unless that was caught somewhere.
```
Handling some errors and returning any that are unhandled:
```elixir
ret = happy_path do
{:ok, result} = do_something()
{:ok, another} = {:error, :blah}
else
{:error, :blah} -> {:ok, :barely} # Assuming that do_something() returned
a {:ok, any} tuplespec
end
# ret == {:ok, :barely} # Assuming that do_something() returned a {:ok,
any} tuplespec, else ret would have got whatever else it returned
```
Handling some errors and raising on any unhandled:
```elixir
ret = happy_path! do # Note the !
{:ok, result} = do_something()
{:ok, another} = {:error, :blah}
else
{:error, :blah} -> {:ok, :barely} # Assuming that do_something() returned
a {:ok, any} tuplespec
end
# ret == {:ok, :barely} # Assuming that do_something() returned a {:ok,
any} tuplespec, else ret would have got whatever else it returned
```
Deferring to a default handler:
```elixir
ret = happy_path(else: my_handler) do
{:ok, result} = do_something_bad()
{:ok, another} = {:error, :blah}
else
{:error, :blah} -> {:ok, :barely} # Assuming that do_something_bad()
returned a {:ok, any} tuplespec, which this one doesn't
end
# ret == {:error, :something_happened}# Lets say do_something_bad()
returned this tuple, since it did not match the
# happy_path else then `err |> my_handler` was called, it is inlined
so it is whatever my_handler is in this scope, this
# is also why you can pass arguments to it, like the ever useful
`conn` in phoenix, but you could of course just make
# it an anonymous function too, though that would be more wordy and
annoying, but would make the &1 placement
# explicit (any point? we know what |> means, maybe instead of
`else: my_handler` it could instead be something like
# `else |> my_handler` if the elixir syntax could allow something
like that?)
```
Etc... you get the gist of it. :-)
But yeah, for normal pattern matching with raising on MatchError then there
is no point to use happy_path, it is for when you want to handle them more
easily, like in my case if a record is missing then I send back a generic
404, or if they are not allowed then I send back a not authorized, etc...
On Monday, June 20, 2016 at 2:09:55 PM UTC-6, José Valim wrote:
> 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
> (=).
>
Ah, that is because the `do` part is a function body, program like normal
(I am a proponent of a defhappy/defhappyp function definition personally,
but its not been added yet), the else is a case-like matcher to handle
errors, and this else case is exceptional (I.E. rare) in general now that
the `else: default_handler` part has been added. In the happy_path the
'do' is always normal elixir, = for matching, <- is not used at all. From
the docs you can see:
```elixir
happy_path do
x when not is_nil(x) = some(foo)
x + 1end
```
That is still a match, but it includes (something that I have always wanted
in both erlang and elixir for over ten years!) guards on matches, but it is
still just a match. The `else` clause, if you have it at all (I prefer
only the default handler in 95% of cases) can be thought of like a `rescue`
in a `try/rescue/end` clause, it uses -> to 'catch' errors so you can
handle them (else they get returned if happy_path or raised via MatchError
if happy_path! with the !). To be honest, I use happy_path in more cases
than it is really warranted (like the above mentioned
elixir-standard-happy-path) just because of the guards on normal matching
(would love that in normal elixir ^.^).
On Monday, June 20, 2016 at 2:09:55 PM UTC-6, José Valim wrote:
> 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).
>
I can see that, could do something like `&handle_conn(&1, conn)`, more
verbose to type out and a bit messy but it is perfectly explicit then. I
do admit that I like and prefer the fact that it pipes in the error and
just puts whatever I put there as straight inlined code, it has a few nice
abilities, but I do see from a language design aspect that it could
potentially be a touch confusing the first time it is used (though
admittedly a lot of things in elixir are like that, I initially excepted
something like |> to 'append' the passed through value to the end to the
argument list instead of prepending it to the beginning, appending makes
more sense to me, but I've gotten used to it).
On Monday, June 20, 2016 at 2:09:55 PM UTC-6, José Valim wrote:
> Further thoughts? :D
>
I love this discussion, it forces me to think through the patterns. :-D
And yes, Elixir is awesome, I only picked it up about 6 months ago, been
using Erlang for almost ten years before that though. Never used Ruby (I'm
more of a C++/Scala/Haskall/Lisp/Python person, depending on the need of
the task, yes I've even written web servers in C++, yes it works
fantastically, more so than people would think), but the syntax makes sense
to me, although that may be because I practically memorized a lot of the
code that makes Elixir, including the directory of Erlang code that is its
base over the past 6 months. ^.^
Side-Note: Guards on matching `Kernel.=\2` like you can do in a
`happy_path` would be awesome! A normal match could be as is now, direct
down to beam, but it would be great if it were possible to have guards on
matching `Kernel.=\2` calls that instead compiled down to a single case
`case` or so if not already within a matching context (only top level
`Kernel.=\2`, no function definitions or within an existing matching
context like `{:ok, %{}=m} = from_something` where the `Kernel.=\2` on the
`m` would not be allowed to have guards at that level, though it could be
tested on the outer-most level. Would be backwards compatible, efficient,
highly convenient, and easy to actually add it in. :-)
Oh, and `Happy` has test cases (and additional constructs like happy_pipe,
vic's ok_jose library is very nice too), those test cases do not test
'code', but test the macro generation, they are all assertions of
comparison to verify the code generated by a happy_path and the
equivalently generated case tree are identical, so you can see precisely
how it works starting
at https://github.com/vic/happy/blob/master/test/happy_path_test.exs#L7 .
And as a quick example, the `ok_jose` (named as an homage after you he
states) could convert that above slightly-modified example of a useless
happy block of:
```elixir
{:ok, r0} = do_something()
{:ok, r1} = do_something_else(r0)
{:ok, result} = do_another(r1)
# Use `result` knowing it is useful since the whole line matched
```
Into this:
```elixir
result = do_something
|> do_something_else
|> do_another
|> ok!
# Use `result` knowing it is useful since the whole line succeeded
```
Or you could even do this if you want the error returned if it errored at
any point on the path:
```elixir
res = do_something
|> do_something_else
|> do_another
|> ok
# `result` will either be `{:ok, result}` or `any` usually `{:error,
something}` if it fails since the lack of ! on `ok` means the error is
returned
```
There is also `error` and `error!` as well. They are all macros that
transform the path into a set of cases that ensure that :ok is done and
'piping' the second tuple value into the next function call, else returning
the error straight. It has a few other features, like one being able to
define your own types of pipes like:
```elixir
defpipe ok_kitten do
k = %Kitten{} -> k
t = %Tiger{domesticated: true} -> tend
def purr(%Kitten{}), do: "purr"def purr(%Tiger{}), do: "PuRRR"
%Kitten{} |> purr |> ok_kitten #=> "purr"
ok_kitten( %Doggie{} |> purr ) #=> %Doggie{} # Alternate style, can wrap
everything, same thing though
# using do/end syntax# block executed only for matching kittens
%Kitten{} |> (ok_kitten do
k -> purr(k)end)
```
*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].
To view this discussion on the web visit
https://groups.google.com/d/msgid/elixir-lang-talk/75dca62f-eec8-4a7d-bd3b-4726f15153ad%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.