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
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
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.
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’tnil
orfalse
, this returnsfalse
instead ofleft
- if
left
isnil
orfalse
butright
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
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!
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…