dogweather

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

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

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

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.

Where Next?

Popular in Discussions Top

andre1sk
A big advantage to Elixir is all the distributed goodness but for many applications running on multiple nodes having integrated Etcd, Zoo...
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
Fl4m3Ph03n1x
Background This question comes mainly from my ignorance. Today is Black Friday, one of my favorite days of the year to buy books. One boo...
New
lucaong
Hello Elixir and Nerves community, I have been working for a while on an open-source embedded key-value database for Elixir, that I call...
230 13924 124
New
rower687
Hi all, I’ve been reading a lot about the “let it crash” term and how supervising processes and the whole messaging passing make an elixi...
New
eteeselink
Hi all, In the last days, two things happened: A blog post titled “They might never tell you it’s broken” made the rounds. It’s about ...
New
griffinbyatt
Sobelow Sobelow is a security-focused static analysis tool for the Phoenix framework. For security researchers, it is a useful tool for g...
New
paulanthonywilson
I like Umbrella projects and pretty much always use them for personal Elixir stuff, especially Nerves things. But I don’t think this is ...
New
Markusxmr
Since Drab has been developed for a while in the open, introducing the Liveview functionality in a way it happend appears to undermine th...
New
slashdotdash
Phoenix Live View is now publicly available on GitHub. Here’s Chris McCord’s tweet announcing making it public.
New

Other popular topics Top

shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
marick
I had some trouble figuring out how to make many-to-many associations work. Once I got it working, I wrote a blog post. Because I’m a nov...
New
Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 126479 1222
New

We're in Beta

About us Mission Statement