josefrichter

josefrichter

Proper way of handling simple booleans in `with` statement

Say I have a bunch of simple boolean conditions I want to check in a with statement, something like

with true <- is_integer(foo),
     true <- rem(foo,2) == 0,
     true <- foo > 50 do
# do something
else
# distinguish with one failed
end

^ when I want to always distinguish which one failed, even if they are super simple conditions, do I always need to wrap then in a function that returns {:ok, "greater than 50"} vs {:error, "not greater than 50"}? or is there a more elegant way of using these super simple conditions in with statement? The conditions may be simple, but I do need to know which one exactly failed do deal with it.

Most Liked

sasajuric

sasajuric

Author of Elixir In Action

My preferred approach for this is to have a generic validate helper which converts boolean into :ok | {:error, reason}:

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

And now with can be expressed as:

with :ok <- validate(is_integer(foo), :not_an_integer),
     :ok <- validate(...),
     ...,
     do: ...

I briefly discussed this in this blog post.

kokolegorille

kokolegorille

or something like this…

with {:a, true} <- {:a, is_integer(foo)},
     {:b, true} <- {:b, rem(foo,2) == 0},
     {:c, true} <- {:c, foo > 50} do
# do something
else
  # distinguish with one failed
  {:a, false} -> ...
  {:b, false} -> ...
  {:c, false} -> ...
end
19
Post #4
soup

soup

Just to throw some grenades, Jose on tagged with on the ThinkingElixir Podcast and relatedly keathleys Good Bad Elixir.

See also the beware section in the with docs, and a similar question I directed at Saša Jurić.

Jose’s argument (and basically every other “anti-tagger” I have read/heard) is that with is explicitly made to handle the case [sic] where your failure states are pretty unified. “If the error matters, use case” sticks in my head.

My take away is that the else is actually almost an after thought for some edge cases, and that most uses of with probably shouldn’t need/have one.

Do I agree with this? Not really sure. I dont really like nested case statements:

case is_integer(foo) do
  true ->
    case rem(foo,2) == 0 do
      true ->
          case 
          ...  # blergh :vomit:

You could wrap each step in its own ensure_integer, ensure_rem2 function, etc that return :ok | {:error, :x} but that feels like a lot of work for some simple checks.

You could write

def validate_foo(foo) when is_integer(foo) and rem(foo,2) == 0 and foo > 50 do
:ok # or act_foo(foo), whatever
end

def valiate_foo(foo) when is_integer(foo) and rem(foo, 2) == 0 do
  {:error, :under_50}
end


def valiate_foo(foo) when is_integer(foo) do
  {:error, :not_rem_2}
end

but that’s not great either IMO because the logic becomes pretty complicated to keep track of.

I think some of the problem is we reduce these problems down to talk about them when really that abstracts any ability to reason on why we should or shouldn’t use a form. In this case does it matter if your foo is bad? can you recover? is passing {:error, reason} useful?

It’s trite, but “know the rules to break the rules” is a valid idiom.

In some cases, tagging the tuple is just the most ergonomic way to do it. In other cases you may want to wrap it in a data structure (such as using Ecto changesets, which are great outside of Ecto) or use a more structured set of functions or case statements.

I would posit that if you want to return a reasonable error back to the user (i.e. they provided a foo of bad quality), then building a changeset or similarly robust structure around it isn’t a bad idea, because you’re now talking about a business rule which can probably stand to be codified more concretely.

Otherwise perhaps you really only need to return {:error, :invalid_foo} and the UI should just be explicitly stating the qualifiers around foo (must be even, must be over 50, etc).

I do like both eksperimentals x || :error and kartheeks error_* styles.

Where Next?

Popular in Questions Top

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
Lily
In templates/appointment/index.html.eex: &lt;%= for appointment &lt;- @appointments do %&gt; &lt;tr&gt; &lt;td&gt;&lt;%= appoi...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: The documentation above suggests that while ...
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call t...
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" =&gt; #BSON.ObjectId&lt;58eb1a7a9ad169198c3dXXXX&gt;, "email" =&gt; ...
New

Other popular topics Top

axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 48475 226
New
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 29703 241
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
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
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
AstonJ
We’ve put together this wiki for Phoenix LiveView - please feel free to add any info you feel is worth including. What is Phoenix LiveV...
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New
dogweather
I wrote this comment on r/haskell, and it’s not popular there. :wink: But I think I’m on to something… Haskell reminds me of Java, and e...
New

We're in Beta

About us Mission Statement