TL;DR: yes and no
We need to take a step back to understand why the question is a bit strange and why programming languages do not behave like this. I'll still provide a Tyr implementation for Integer to sketch a solution.
First of all, there is no such thing as a logical operator. There is a truth type, e.g. bool
that might have operators. These operator symbols might be reused by other types. Usually, there is ||
and |
.
The first strange thing about the question is that ||
is usually lazy and implemented with an if then else. Most languages allowing to overload operators treat operators like functions, i.e. the right argument will always be evaluated preventing libraries to provide this behavior.
So, the remaining question is how can a statically typed language implement something like 3 or 4 in Python.
Step one, we do it just for Integer[_]
.
public def || (x : Block[Integer[Size]]) : Integer[Size]
<: operator.precedence[30], operator.rightAssociative =
if this.toBool() this else x.eval()
I'll assume that there is a toBool
function; it could be 0 == this
or similar. It does not matter. What matters here is the Block
type and eval()
call. Because this is what causes evaluation to happen in the correct branch of the CFG.
With this pattern, you can define your operator for every type where you consider this behavior reasonable. My personal taste is that it is never a good idea. Especially if you provide an implicit conversion to bool
.
If you are looking for the eager evaluation semantic, you could simply define it as
public def | (x : Integer[Size]) : Integer[Size]
<: operator.precedence[50], operator.rightAssociative =
if this.toBool() this else x
Step two, extension to arbitrary types.
Here, we have several options that all come with downsides.
One option would be to add an implicit conversion from any type to bool. This would result in C++'s solution if there wouldn't be an overload that results in a value.
We could define the operator in any as any || any : any
. This would require any to have a physical representation, which it usually does not have. Also, it would cause ambiguities with actual logical operations.
We could try to define an operator ∀T,U. T || U : union(T,U)
. This, again, would require that union(T,U)
is representable, which it is not in most languages. E.g. 0 || "hello"
would be either a pointer to string literal or a 32bit integer. To fix this, we'd need some sort of constraint requiring that T and U share the same physical representation. Even if there were a language that could express this relation, you'd still also need inference for all the types and means to provide sane error messages if this constraint is not fulfilled. My experience is that people who build such languages simply do not like implicit conversions from anything to bool. Hence, they won't have the operator you are looking for.
x or y
is a bit like whatis_truthy(x) ? x : y
would be in C(++), so that's one place to perhaps look at. e.g. en.cppreference.com/w/cpp/language/operator_other $\endgroup$