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.
happy_path do {:ok, b} = a {:ok, d} = b c(d) end
case(a) do {:ok, b} -> case (b) do {:ok, d} -> c(d) end end
happy_path do {:ok, b} = a c(b) else {:error, x} -> x end
happy_path do x when not is_nil(x) = some(foo) x + 1 end
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
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
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
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
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 |
happy_path do{:ok, b} = a{:ok, d} = bc(d)end
{:ok, b} = a{:ok, d} = bc(d)
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} = bc(d)endIs exactly the same as:{:ok, b} = a{:ok, d} = bc(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 (=).
happy_path do x when not is_nil(x) = some(foo) x + 1 end
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
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)
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.
@foo {:ok, x} = y
{:foo, {:ok, x}} = {:foo, y}
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.