michalmuskala

michalmuskala

I wrote why I dislike hiding an if behind && and else behind || in this article.

When you think about teaching programming, what’s the first thing you want someone to learn? It’s if! It’s the most basic construct in nearly every programming language. Every programmer knows if, every programmer wrote thousands upon thousands of ifs. Every programmer knows what if means and what was the intention of the coder who wrote it.
[…]

1
Post #31
net

net

I disagree with your article but that’s not the point. The point is that if you rewrote your befores using if they would be verbose too.

0
Post #32
michalmuskala

michalmuskala

The thing is, I didn’t have to. I feel completely comfortable with a single && operator. I feel completely not comfortable with a combination of && and || to emulate an if.

1
Post #33
Qqwy

Qqwy

TypeCheck Core Team

I often find myself writing multiline statements that depend on boolean values, which however do not warrant their own named function. For this, if/else is very useful.

(For the min example of @michalmuskala’s blog post, I think I’d be most happy with this form:

def min(a, b) when a < b, do: a
def min(_a, b), do: b

)

1
Post #34
net

net

Also, I don’t think {:ok, _} and :error are very appropriate in this situation. It’s not an error if the element doesn’t match what you’re looking for. Something more descriptive would be better, like :notfound.

It’s unconventional (as far as I know), but I think an idiom where a single element tuple holds a value in cases where only a single value must be returned could be introduced to reduce complexity.

Enum.search(list, fn(item) -> ... && {item} || :notfound end)
0
Post #35
net

net

Your arguments against boolean operators seem to be a) that they don’t make long conditionals like

  if (vehicle.type == :car)
    drive(vehicle)
  elsif (vehicle.type == :plane)
    fly(vehicle)
  else
    sail(vehicle)
  end

any better, and b) that there’s nothing wrong with if in the first place. However, wouldn’t you agree that the complexity in your after examples is cause by the ifs? The boolean operators in my example are not long conditionals either. From your article, I don’t see any reasons not to choose && || over if in this situation.

0
Post #36
michalmuskala

michalmuskala

I don’t know about others, but whenever I see foo? && bar || baz I have to “parse” it to an if before I can understand the code. That’s why I call it “emulation” - for me it looks like a fancy way to make your code “if-free” (that seems to be a popular metric lately) without actually giving any real benefits.

0
Post #37
Eiji

Eiji

Firstly I would like to remove bugs :smiley:

1
Post #38
gregvaughn

gregvaughn

I only do that if I’m doing ElixirGolf and trying to save a few characters for fun. Do you really see that in production code?!? :scream:

1
Post #39
OvermindDL1

OvermindDL1

Indeed it would, but that is only because of the current patterns that are used. Following your example but with a monadic style (from this thread) it would change from:

content =
  Enum.find_value(sources, fn source ->
    File.exists?(source) && File.read!(source)
  end) || raise "could not find #{source_file_path} in any of the sources"

To if Enum.find_value’s callback an option tuple ({:ok, blah}|:error) (using module helpers as stated should be made in the above linked thread):

{:ok, content} =
  Enum.find_value(sources, &Option.from_result(File.read(&1)))
  ~> fn _ -> raise "could not find #{source_file_path} in any of the sources" end
  # Or do `|> unwrap()` here and replace the binding above with just `content =`

And for the other example of:

Enum.find_value(headers, fn({k, v}) -> k =~ ~r/^origin$/i && v end)

To if Enum.find_value’s callback an option tuple ({:ok, blah}|:error) (using module helpers as stated should be made in the above linked thread):

Enum.find_value(headers, fn({k, v}) -> Option.wrap_if(k =~ ~r/^origin$/i, v) end

And the third example of:

defp check_operations_valid(operations) do
  Enum.find_value(operations, &invalid_operation/1) || {:ok, operations}
end

defp invalid_operation({name, {:changeset, %{valid?: false} = changeset, _}}),
  do: {:error, {name, changeset, %{}}}
defp invalid_operation(_operation),
  do: nil

To if Enum.find_value’s callback an option tuple ({:ok, blah}|:error) (using module helpers as stated should be made in the above linked thread):

defp check_operations_valid(operations) do
  Enum.find_value(operations, &invalid_operation/1)
  |> Result.from_option(operations) # Takes the optional error value, only evaluated if an `:error`
  |> Result.flip() # Flip the `:error` and `:ok` cases
end

defp invalid_operation({name, {:changeset, %{valid?: false} = changeset, _}}),
  do: {:ok, {name, changeset, %{}}}
defp invalid_operation(_operation),
  do: :error

Which although a bit longer I find more readable rather than the easily missed (since I did originally miss it) trailing || {:ok, operations} bit.

Tagged Tuples are far superior, especially with conditional piping as well.

Entirely true, and gets back to what I say should be properly named return values. Although if we could drop the erlang heritage and big erlang style I’d say the option type should be {:some, value}/:none and the result type should be {:ok, value}/{:error, result} or something along those lines, but {:ok, value}/:error is definitely more erlang’y and there is a lot of history and code there.

I’ve also been for that too, doing the option as {value}/:error (or :none or whatever) would make sense, but there is no precedence for that either.

That example would be significantly better with cond, yet another reason I encourage either purging if as-it-stands from the language, or forcing an else clause, always. I really do not like this magically appearing nil from invisible else’s.

Agreed yep, hence why I want some construct that forces the else to exist, no magically appearing nil’s and such.

Which gets back to my wanting to fix the scoping brokenness. :-V

1
Post #40