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.
