ImNotAVirus

ImNotAVirus

std_result - A way to standardize function returns

Hi everyone,

Published a new library: StdResult!

StdResult is a library designed to standardize function returns.
Highly inspired by Rust’s std::result, this library provides a way of simplifying the management of :ok and :error tuples by providing functions for manipulating them.

The problem:
One problem I come across quite often is the lack of consistency between certain functions. In the same module, some functions will sometimes return :ok while others will return {:ok, result} and others just result. The same goes for errors. It can quickly become complicated to manipulate these results.

That’s where StdResult comes in.

Usage:
Here is a simple example: let’s say we need to retrieve an environment variable, convert it to an integer and check that it’s positive. Our function should return {:ok, value} or {:error, reason}.

Here’s an example of what it might look like with StdResult.

import StdResult

System.fetch_env("PORT")
# This will transform `:error` into a `:error` tuple
|> normalize_result()
# If there is an error, explicit the message
|> or_result(err("PORT env required"))
# If no error, parse the string as an integer
# We could also have used `Integer.parse/1` but for simplicity's sake we won't.
|> map(&String.to_integer/1)
# Test if the number is positive
|> and_then(&(if &1 >= 0, do: ok(&1), else: err("PORT must be a positive number, got: #{&1}")))

# The result will be either:
# - `{:ok, port}`
# - `{:error, "PORT env required"}`
# - `{:error, "PORT must be a positive number, got: <value>"}`

Check out the documentation for more details on existing functions.
Any issues, suggestions or contributions are welcome.
Cheers


Links:

Most Liked

sasajuric

sasajuric

Author of Elixir In Action

To reduce the amount of function splits and these micro-functions, I use a helper function called validate, which transforms a boolean into :ok | {:error, reason}:

def validate(true, _reason), do: :ok
def validate(false, reason), do: {:error, reason}

And now you can write

with :ok <- validate(User.exists?(updated_user.id), :not_found), ...

It doesn’t solve all the problems you mention, but it can often help avoiding micro-functions and keep the logic in a single place, for the price of a small function whose semantics are easy to grasp. I’ve introduced it to multiple teams and programmers with different experience levels, and it worked quite well, especially in combination with Repo.transact mentioned in another thread.

dimitarvp

dimitarvp

I think you all are being too harsh on OP here, he spotted a problem and offered his take on it. Let the guy live, he doesn’t deserve the death sentence you are giving him. :smiley:

@ImNotAVirus I do understand the motivation for your library but I’d be personally against using it in its current form because to me it introduces ambiguity and conditions in pipes, and overall just increases code size without offering clarity of intent in return. Furthermore, the problem statement is to me minor; I never found it problematic to have my guard up and properly catch singular :ok / :error atoms. :person_shrugging: It’s part of the job, plus we have Dialyzer, plus we have “Go to definition” and “Go back” in our IDEs, so… I don’t know. The problem does not seem major to me.

IMO if you want to push the library ahead then you should aim for ultra mega hyper terseness; not just the to_result renaming that you said you’ll adopt, but also you should have only 2-3 functions in the library that do practically everything that your current separate functions are doing right now.

My points:

  • You want to call functions that are not guaranteed to return proper {:ok, value} / {:error, reason} tuples? Use a singular library function e.g. ok_err (or to_result) that absolutely always will return the proper tuple regardless of what is fed to it.

  • You want to have custom validation / error reporting functionality? Well, IMO that is another library. Your post (and likely the library as well) seems to be mixing concerns.

  • The Elixir community usually resists usage of such libraries because (a) most teams feel that doing what the library does is trivial enough and (b) nobody wants to adopt new idioms without a very clear value proposition.

cmo

cmo

Another solution is to use your editor to inspect the function (hover, keyboard shortcut, etc) to see the spec. This doesn’t introduce cognitive load, you learn the standard library, you don’t pay a performance penalty and are more likely to write idiomatic code.

It is convention that functions/macros ending with a ! raise exceptions.

Where Next?

Popular in Announcing Top

danschultzer
In short Plug n’ play OAuth 2.0 provider library. Just set up a resource owner schema with Ecto (your user schema), install the dependen...
New
mischov
import Meeseeks.CSS html = HTTPoison.get!("https://news.ycombinator.com/").body for story &lt;- Meeseeks.all(html, css("tr.athing")) do...
New
brainlid
LangChain is short for Language Chain. An LLM, or Large Language Model, is the “Language” part. This library makes it easier for Elixir a...
New
MRdotB
I needed to reuse React components from my Chrome extension in my Phoenix/LiveView backend. I noticed that for Svelte/Vue, there are live...
New
wojtekmach
Hey everyone! Req is an HTTP client for Elixir that I’ve been working on for quite some time. There is already a lot of HTTP clients out...
New
aditya7iyengar
Rummage.Ecto and Rummage.Phoenix provide ways to perform Searching, Sorting and Pagination over Ecto queries and Phoenix collections. Fo...
New
Flo0807
Hello everyone! I am excited to share our heart project Backpex with you. After building several Phoenix applications, we realized that...
New
anshuman23
Hello all, I have been working on my proposed project called Tensorflex as part of Google Summer of Code 2018.. Tensorflex can be used f...
New
zoltanszogyenyi
Hey everyone :waving_hand: Excited to join this forum - I am one of the founders and current project maintainers of a popular and open-s...
New
wfgilman
I’ve cleaned up and open sourced three financial libraries I was using for my company. They are bindings for the APIs of these three comp...
New

Other popular topics Top

danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 29377 241
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 30877 112
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 43622 214
New
hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a &gt; b) do {:ok, "a"} end if (a &lt; b) do {:ok, b} end if (a == b) do {:ok, "equa...
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 39297 209
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" =&gt; #BSON.ObjectId&lt;58eb1a7a9ad169198c3dXXXX&gt;, "email" =&gt; ...
New

We're in Beta

About us Mission Statement