dogweather
Idea: Rust-like syntactic support for :ok/:error
Rust is too low-level for most of my work, but I’m envious of the strong fp influence in its design. E.g., the verbose way to react to an error — and bubble it up — looks like this. The gen2() function also returns this Result Union/Either type:
fn caller() -> Result<String, String> {
let r = gen2();
match r {
Ok(i) => Ok(i.to_string()),
Err(e) => Err(e),
}
}
To me, that looks a lot like the usual Elixir way of matching on {:ok, ...} and {:error, ...}. Rust, though, has this nice UX syntactic support with the ? operator. This code is equivalent to the above:
fn caller() -> Result<String, String> {
let n = gen2()?;
Ok(n.to_string())
}
(Source: https://betterprogramming.pub/rust-error-handling-84e7bd169e47)
Pretty damn nice, IMO. When I was doing a lot of Rust coding, error handling was a breezy, low-boilerplate experience — yet the code was as robust as ever. Invocations with ? like above can be chained together with the . operator.
I’m imagining syntactic support like this that’d work seamlessly with the existing :ok/:error convention, just like the Rust syntax works with Result.
Brainstorming and adapting the example from Writing Predictable Elixir Code with Reducers | AppSignal Blog … here’s the original code:
options = %{conn: conn}
with {:ok, payment} <- fetch_payment_information(params, options),
{:ok, user} <- fetch_user(conn),
{:ok, address} <- fetch_address(%{user: user, params: params}, options),
{:ok, order} <- create_order(%{user: user, address: address, payment: payment}, options)
do
conn
|> put_flash(:info, "Order completed!")
|> redirect(to: Routes.order_path(conn, order))
else
{:error, error_description} ->
conn
|> put_flash(:error, parse_error(error_description))
|> render("checkout.html")
end
Let’s say we want to refactor this, extracting the with clause to a new function, but use a new => operator:
def process_payment(params, options) do
payment <= fetch_payment_information(params, options),
user <= fetch_user(conn),
address <= fetch_address(%{user: user, params: params}, options),
order <= create_order(%{user: user, address: address, payment: payment}, options)
end
Like Rust’s ?, this <= would automatically “unwrap” the return values, directly giving access to the :ok result. But if any of these returns :error it’ll automatically return the first found :error result. In a nutshell, it’d de-sugar to a with/else-construct.
The refactored caller could be standard un-sugared Elixir since it’s top-level and wants to handle the error, not bubble it up. But now it’s more readable:
options = %{conn: conn}
case process_payment(params, options) do
{:ok, order} do
conn
|> put_flash(:info, "Order completed!")
|> redirect(to: Routes.order_path(conn, order))
{:error, error_description} ->
conn
|> put_flash(:error, parse_error(error_description))
|> render("checkout.html")
end
This has been vetted and used in Rust for years. So even though it hides some info and does some things automatically, I think the Rust experience has validated it as a positive addition to the language.
Along with <= I was thinking about an unwrapping version of the pipe |>: This new version would transparently handle functions that return the :ok/:error tuple, enabling them to be used in pipelines with functions that don’t. IMO this second syntax could make code even cleaner, e.g. if the new pipe is !>:
username
|> to_lower
!> get_user_from_database
|> format_address
Most Liked
al2o3cr
I don’t follow having “no control” - what about wrapping them in private functions?
Two thoughts:
- “cleaner” is subjective, IMO the
!vs|visual distinction is very small - you can already write functions like this if you want with plain
|>, but it means that downstream functions all grow a{:error, _}pass-through case
opsb
There’s a couple of libraries that provide macros towards this goal. Off the top of my head OK Elixir – ok v0.2.0 might be be appealing?
cmo
While with is a little more verbose, it has the benefit of being more explicit and more flexible, in that you can match on anything - not just an ok tuple.
F# has the result computation expression which I loved because I found working with the result monad to be a total PITA without it.
Popular in Discussions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








