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

Curious as to why this is not permitted. I suspect it has something to do with or only accepting a boolean as the first operand. But why does this matter with guard clauses?

because left || right actually compiles to something like

case left do
v when v == false or v == nil -> right
_ -> left
end

Which is not allowed in guards

2 Likes

Okay, so or is a more â€śprimitiveâ€ť boolean comparison operator? Is it natively implemented or does it have some other compiled form as well?

or is compiled to :erlang.or which is allowed in guards

Donâ€™t forget to mark the solution

Is it the same for all other boolean operators, such as && and and?

Only || and && are not allowed in guards. All other boolean operators are allowed

1 Like

Yeah && and || have what is called a â€śshort circuitâ€ť behavior where they will only evaluate the right hand side of the operand if they need to. This allows you to do things like:

value = params[:some_opt_key] || raise "this parameter is required!

If the left hand side is â€śtruthyâ€ť then || short circuits and doesnâ€™t evaluate the raise at all. This sort of branching behavior doesnâ€™t even make sense in guards.

1 Like

It makes sense, and it couldâ€™ve been implemented in guards:

For example, left || right could be translated to (left == nil or left == false) and right.

The short circuit behavior cannot be implemented in guards because it doesnâ€™t even make sense in guards. Guards donâ€™t have branches.

The truthy vs falsey could be yes, but personally the distinction between || and or is really nice because each behaves exactly the same way whether in a guard or not.

|| applies to any terms, so this fails on several fronts:

• if left isnâ€™t nil or false, this returns false instead of left
• if left is nil or false but right isnâ€™t a boolean, it crashes

Guards can have branches: You could in principle get it into a guard using orelse/andalso. I believe that elixir compiles down to orelse and andalso for its binary or/and operators anyways

Yeah, :erlang.orelse should do. Give me some time to think about the correct oneâ€¦

Yep. it is possible. Hereâ€™s the complete implementation

defmodule X do

defguard truthy(x) when x != false and x != nil
defguard a ||| b when
:erlang.andalso(
:erlang.orelse(
:erlang.andalso(truthy(a), true),
b
),
:erlang.orelse((not truthy(a) and b), a)
)

defmacrop check(left, right) do
result = left || right
quote do
must_be =
case 1 do
_ when unquote(result) -> 1
_ -> 2
end

got =
case 1 do
_ when unquote(left) ||| unquote(right) -> 1
_ -> 2
end

if must_be != got do
IO.inspect unquote(left), label: :left
IO.inspect unquote(right), label: :right
IO.inspect unquote(result), label: :results_in
IO.inspect must_be, label: :must_be
IO.inspect got, label: :got
raise "Stop"
end
end
end

def test do
check(nil, true)
check(false, true)
check(true, true)
check(1, true)

check(nil, false)
check(false, false)
check(true, false)
check(1, false)

check(nil, 123)
check(false, 123)
check(true, 123)
check(1, 123)

IO.puts "success"
end
end

X.test

@al2o3cr @ityonemo @benwilson512 , check this out

Worth noting that in guards, or is precisely the same as calling :erlang.orelse/2.

This might work for literals, but Iâ€™m not sure it works for arbitrary expressionsâ€”in general, nor successful at not evaluating the second branch.

In general thereâ€™s not much reason to want || in these cases, as we are not working with arbitrary expressionsâ€”we know that functions allowed in guards are always idempotent, and donâ€™t have side effects, so thereâ€™s little gain in suppressing them!

1 Like

This might work for literals, but Iâ€™m not sure it works for arbitrary expressionsâ€”in general, nor successful at not evaluating the second branch.

I believe this works correctly. There might be some reevaluations

In general thereâ€™s not much reason to want || in these cases

Yeah, thatâ€™s true. I did this just to show that porting || to guards is possible

Yeah, it works fine with the expressions:

case 1 do
_ when (1 > 2) ||| (3 < 4) -> 1
_ -> 2
end

Not necessarily, you might want to pipe a default value into a function, for example:

is_map_key(some_map, something_nillable ||| :erlang.map_get(:default_key, settings_map))

Of course that guards have branches and or also short circuitâ€¦