Why cond raises an error without truthy value?

From the docs,

Raises an error if all conditions evaluate to nil or false .

But conceptually a cond is a more elegant way to write nested if/else:

assignment = 
  if first_condition do
    first_result
  else 
    if second_condition do
      second_result
    else
      default_result
    end
  end
assignment = 
  cond do
    first_condition -> first_result
    second_condition -> second_result
    true -> default result
  end

But if is very much ok without else:

variable = if condition, do: value

variable is nil if condition is falsy.

But if I want the same with cond, I must add an ugly and visually polluting true -> nil in the end

variable = 
  cond do
    condition -> value
    true -> nil
  end 

Since everything is value transformation in Elixir, I love that variable = if condition, do: value works, and I don’t understand why cond wouldn’t have the same behavior.

Same goes for case, though one could argue that in case variable do, variable does have a value so it makes sense to want to patterne match it, even with the general case.

Let’s take this from the other side. If you want to raise if a condition doesn’t match you don’t need if to do so. You can just do true = condition and it’ll raise if the condition isn’t true. For case you might have a case where for a system to work correctly one of the given conditions needs to be true. Implicitly adding a true -> nil case would break this behavior.

Also for if there’s a fixed number of cases. The condition being true or not a.k.a. the do block and the else block. A missing else block is easy to detect and to fall back to a default behavior. In a case expression there’s no way to know if existing clauses are exhaustive or not.

3 Likes

…conceptually a cond is a more elegant way to write nested if/else

Actually cond is like nested case because it is compiled to such structure.

Couple of examples, with their erlang representation obtained by Michał Muskała’s decompile

defmodule ClausesCase do
  @doc """
  The function compiles to erlang like this:

    'one?'(_x@1) ->
      case _x@1 of
          1 ->
              <<"one">>;
          _ ->
              <<"nope">>
      end.
  """
  def one?(x) do
    case x do
      1 -> "one"
      _ -> "nope"
    end
  end
end
defmodule ClausesCond do
  @doc """
  The function compiles to erlang like this:

    'one?'(_x@1) ->
      case _x@1 == 1 of
          true ->
              <<"one">>;
          false ->
              case true of
                  true ->
                      <<"nope">>;
                  false ->
                      error(cond_clause)
              end
      end.
  """
  def one?(x) do
    cond do
      x == 1 -> "one"
      true -> "nope"
    end
  end
end
defmodule ClausesIfElse do
  @doc """
  The function compiles to erlang like this:

    'one?'(_x@1) ->
      case _x@1 == 1 of
          false ->
              <<"nope">>;
          true ->
              <<"one">>
      end.
  """
  def one?(x) do
    if x == 1, do: "one", else: "nope"
  end
end
defmodule ClausesIf do
  @doc """
  The function compiles to erlang like this:

    'one?'(_x@1) ->
      case _x@1 == 1 of
          false ->
              nil;
          true ->
              <<"one">>
      end.
  """
  def one?(x) do
    if x == 1, do: "one"
  end
end

To answer the question, if and cond are syntactic sugar on top of case in Elixir, they have different use cases, so they behave slighly differently. Most notably, if has an implicit else clause that defaults to nil.

1 Like

Interesting pattern the true = variable, never thought to use it.

That said, I’m still unsure of the whole logic behind, but I reckon this might be related to this comment and the difference between “defensive coding” instead of “let it crash”.

For me a “nil-case” can definitely be under control, it shouldn’t be explicit all-the-time.

But it relatives to deep language philosophy, so we might not find an issue to the question.

Thanks for your replies.