Strict and relaxed boolean operators

I know the difference between strict (and, or) and relaxed (&&, ||) operators, but when should I use them? Using && and || seems to be more versatile, but maybe it isn’t a good practice. What’s the convention?

docs state below:

As a rule of thumb, use and , or and not when you are expecting booleans. If any of the arguments are non-boolean, use && , || and ! .

One special trick which you can do with || is to assign default value when something is nil:

def id(opts) do
  id_prefix = opts[:id_prefix] || "default_prefix"
  #... do something.
end

|| becomes tricky for booleans when it is used for assigning default values :

def verify(id, opts) do 
  # this will result in a bug - validate_prefix? will not ever be false (for booleans)
  validate_prefix? = opts[:validate_prefix] || true # this will result in a bug - validate_prefix? cannot be false
  #  do something
end

Calling verify/2 with opts , opts2, opts3:

# works good - validate_prefix? will be true
opts = %{validate_prefix: true} 
verify("abc-123", opts)
# works good - true will be assigned (default value)
opts3 = %{} 
verify("abc-123", opts1)
# bug - disabling prefix validation is not possible due to use of ||
opts2 = %{validate_prefix: false} 
# this will be ignored and default value true will be assigned
# default value true will be assigned as false || true is always true
verify("abc-123", opts2)
2 Likes

One situation where and and or are required: && and || aren’t allowed in guard clauses.

4 Likes

Like || can be used to check for nil and assign default values - can && be used for anything else?

One thing it can be used for raise /throw.

true && raise(ArgumentError)
false || raise(ArgumentError)
nil || raise(ArgumentError)

you could possibly use it to perform an action that doesn’t require the value

value = get_some_value()
value && perform_some_action_only_if_value_is_not_nil()
value

something like this, I guess

1 Like

Thanks for all replies. So which one do you prefer or which one do you use more often?

As both (strict, relaxed) are not substitutes for one another - it depends on the restrictions they come with ?

Something I’ve done recently with :gen_tcp:

# The terminate callback in a GenServer
def terminate(_reason, socket) do
  socket && :gen_tcp.shutdown(socket, :read_write)
end
1 Like