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.

ityonemo

ityonemo

Cursed answer:

def validate(foo) do
  is_integer(foo) or throw :not_integer
  rem(foo,2) == 0 or throw :not_even
  foo > 50 or throw :too_big
catch
  :not_integer -> ...
  :not_even -> ...
  :too_big -> ...
end
brettbeatty

brettbeatty

If you’re just working with booleans, couldn’t you use cond?

cond do
  not is_integer(foo) ->
    :not_integer

  rem(foo, 2) != 0 ->
    :not_even

  foo <= 50 ->
    :too_small

  true ->
    :ok
end

Where Next?

Popular in Questions Top

lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
beno
I will often find my self writing things similar to: case some_value do nil -&gt; something() "" -&gt; something() _ -&gt; someth...
New
Codball
Mix format works fine if run from the cmd. I’ve followed this to facilitate the implementation into VSC which involves downloading an ext...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New

Other popular topics Top

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...
3268 119930 1237
New
peerreynders
Manning 2016 Halloween weekend sale via Deal of the Day Friday, October 28 - Half off all MEAPs - code WM102816LT Saturday, October 29 ...
326 29600 154
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
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 30048 115
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
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
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

We're in Beta

About us Mission Statement