Why can or be used in guard clauses but not ||?

Elixir’s or is Erlang’s orelse. Erlang’s or do not have Elixir equivalent.

Elixir’s or is both erlang’s or (kinda) and orelse. Take a look

1 or 2 in normal context generates this code

  case 1 do
    false -> 2
    true -> true
    underscore -> :erlang.error({:badbool, :or, underscore})
  end

And only in guards it translates to

:erlang.orelse(1, 2)

This is because orelse will just err with badarg, whereas in Elixir we want more accurate errors. But besides that this is the same behaviour as orelse : only booleans on the left.

1 Like

I would need to check, but I am pretty sure it will behave differently if one of the sides is not an boolean. As for example even with that code you cannot do something like foo == (a || b) in guard.

Yeah, it has a problem with second and third case here. However, the first one works correctly. I think this is general problem of :erlang.orelse which returns false for :erlang.orelse(1, 2) in guards.

    case 1 do
      x when x == (1 ||| 2) -> true
      _ -> false
    end
    |> IO.inspect()

    case 1 do
      x when x == (nil ||| 1) -> true
      _ -> false
    end
    |> IO.inspect()

    case 1 do
      x when x == (false ||| 1) -> true
      _ -> false
    end
    |> IO.inspect()

It does not return false for :erlang.orelse(1, 2). It fails. There is huge difference between these two:

defmodule Foo do
  def f(a) when (a or false) or true, do: :matched
  def f(_), do: :not_matched

  def g(a)
    when a or false
    when true, do: :matched
  def g(_), do: :not_matched
end

Foo.f(nil) # => :not_matched
Foo.f(false) # => :matched

Foo.g(nil) # => :matched
Foo.g(false) # => :matched

Yeah, you’re right