happy_path vs with (Elixir 1.3)

200 views
Skip to first unread message

OvermindDL1

unread,
Jun 20, 2016, 12:12:50 PM6/20/16
to elixir-lang-talk
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...

José Valim

unread,
Jun 20, 2016, 12:20:17 PM6/20/16
to elixir-l...@googlegroups.com
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
Skype: jv.ptec
Founder and Director of R&D

On Mon, Jun 20, 2016 at 6:12 PM, OvermindDL1 <overm...@gmail.com> 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 elixir-lang-ta...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-talk/3804944c-86eb-46a7-a581-7d3869ac6144%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

OvermindDL1

unread,
Jun 20, 2016, 1:29:58 PM6/20/16
to elixir-lang-talk, jose....@plataformatec.com.br
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)
    end
end
```
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} -> x
end
```
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 + 1
end
```


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)
    end
end
```

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)
end end 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. end end
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.  :-)

José Valim

unread,
Jun 20, 2016, 4:09:55 PM6/20/16
to OvermindDL1, elixir-lang-talk
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
Skype: jv.ptec
Founder and Director of R&D

OvermindDL1

unread,
Jun 20, 2016, 5:07:25 PM6/20/16
to elixir-lang-talk, overm...@gmail.com, jose....@plataformatec.com.br
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 + 1
end
```
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} -> t
end

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)
```

Peter Hamilton

unread,
Jun 20, 2016, 5:13:02 PM6/20/16
to elixir-l...@googlegroups.com, OvermindDL1
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.


OvermindDL1

unread,
Jun 20, 2016, 5:39:19 PM6/20/16
to elixir-lang-talk, overm...@gmail.com
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!  ;-)

Chris Keele

unread,
Jun 21, 2016, 9:52:30 AM6/21/16
to elixir-lang-talk
Of all the ideas in this discussion, the monad/tag helper seizes my imagination the most! It seems a nice compromise between simple tagging codified in erlang philosophy and fully-fledged monad types from a more formal language.

With different syntax I could see it becoming used similarly to keyword list two-tuple sugar: practical and the "happy path" while being small enough an abstraction to making dropping down to normal tagging fluid and natural.

It would be interesting to see if, given a dedicated syntax, what other functionality related to tagging it could/should take on. Like the pipe operator macro, or the keyword sugar, I think less is more here.

Reply all
Reply to author
Forward
0 new messages