JuliaLang / julia

The Julia Programming Language
https://julialang.org/
MIT License
45.54k stars 5.47k forks source link

Feature request: allow multiple dispatch on short-circuit operators && and || #52368

Open RaulDurand opened 10 months ago

RaulDurand commented 10 months ago

I am currently working with custom mathematical expressions in Julia. One limitation I've encountered is the inability to use multiple dispatch with the short-circuit logical operators && and ||. While alternatives such as defining functions like and() and or() or using Unicode operators are viable, they don't quite offer the intuitive and natural usage that && and || provide for most users.

Proposal:

I propose extending Julia's functionality to allow multiple dispatch on && and || operators while preserving their short-circuit behavior. This enhancement would not only maintain the intuitive use of these operators but also add flexibility for custom types and expressions.

Implementation Concept:

The core idea is to encapsulate the right-hand side (rhs) of the operator into an anonymous function at compile time (or any other stage). This encapsulation allows the short-circuit logic to be preserved. For example:

function Base.:(&&) (lhs_val::Bool, rhs_fun::Function)
    if lhs_val
        rhs_fun()
    else
        false
    end     
end

This approach maintains the current behavior when dealing with Boolean values. For custom types, such as CustomExpr, the operator can be redefined to suit specific needs:

function Base.:(&&) (lhs_val::CustomExpr, rhs_fun::Function)
    rhs_val = rhs_fun()
    custom_logical_expr(:&&, lhs_val, rhs_val)
end

Benefits:

Intuitive Syntax: Users can continue using && and || in a manner they are familiar with. Customizability: This feature opens up avenues for more flexible and powerful expressions in custom mathematical and symbolic computation. Backward Compatibility: The proposed change is backward compatible as it preserves the existing behavior for standard Boolean types.

I believe this feature would be a valuable addition to Julia, enhancing its capabilities in symbolic mathematics and potentially other domains where custom types benefit from logical operations. I am hopeful that this feature aligns with the interests and needs of most Julia programmers. Thank you for considering this proposal

PallHaraldsson commented 10 months ago

allow multiple dispatch on && and || operators

Does this make sense? They are not (math) operators, unlike e.g. (bitwise) & and |, that are defined by functions, and they can sometimes be used as substitutions for working (faster even) on Bools (and support multiple dispatch?.

They are Boolean short-circuting operators, or operations... not defined by functions.

I admit I didn't read carefully what you proposed, you want some extension. Those are the only short-circuting operations, i.e. control flow. I.e. none of normal operators are.

fingolfin commented 10 months ago

To me the benefit also remains unclear. Perhaps a concrete example or two would help illustrate what usecases you have in mind?

As an alternative, you might consider using macros to rewrite expressions containing && or ||, substituting those with suitable code (e.g. a && b could be transformed into myand(a, ()->b)).

PallHaraldsson commented 10 months ago

Backward Compatibility: The proposed change is backward compatible as it preserves the existing behavior for standard Boolean types.

Short-circuiting is what people expect, and if that doesn't apply to what you propose (it doesn't for functions/other operators), then that would be surprising. Are you asking for that in the extension? I'm not sure it's easily doable.

With a macro you can do anything, as proposed, then then people may not expect it, but might also be surprising to miss it.

RaulDurand commented 10 months ago

Does this make sense? They are not (math) operators, unlike e.g. (bitwise) & and |, that are defined by functions, and they can sometimes be used as substitutions for working (faster even) on Bools (and support multiple dispatch?.

They are Boolean short-circuting operators, or operations... not defined by functions.

Well, && and || are logical operators. Although in Julia they are not defined by functions.

I admit I didn't read carefully what you proposed, you want some extension. Those are the only short-circuting operations, i.e. control flow. I.e. none of normal operators are.

The idea is to extend && and || to be used as operators. In the past, other operators (such as the dot operator) were exposed for multiple dispatch.

RaulDurand commented 10 months ago

To me the benefit also remains unclear. Perhaps a concrete example or two would help illustrate what usecases you have in mind?

The use case is more common in symbolic calculus, equations and boolean expressions. Take as an example the definition of a piecewise function using SymPy (from Python). In that case, this package uses the & operator, but additional parentheses are required due to the higher precedence of &.

from sympy import symbols, Piecewise
x = symbols('x')

# Define the piecewise function
f = Piecewise(
     (x**2, x <= 0),          # x^2 for x <= 0
     (x + 1, (x > 0) & (x < 2)),  # x + 1 for 0 < x < 2
     (3, x >= 2)              # 3 for x >= 2
 )

There are some Julia packages that use a similar approach.

As an alternative, you might consider using macros to rewrite expressions containing && or ||, substituting those with suitable code (e.g. a && b could be transformed into myand(a, ()->b)).

Yes, macros can help but when used frequently within the same expression they can compromise readability.

mbauman commented 10 months ago

Quite related is https://github.com/JuliaLang/julia/issues/39104. That's more specifically about 0 < x < 2, but that lowers to the short-circuiting 0 < x && x < 2, just like you have written here.

RaulDurand commented 10 months ago

Short-circuiting is what people expect, and if that doesn't apply to what you propose (it doesn't for functions/other operators), then that would be surprising. Are you asking for that in the extension? I'm not sure it's easily doable.

In my proposal, short-circuit will still work as expected provided that the first argument is a boolean. For other types, such as symbolic expressions, it is reasonable to expect the outcome to be a corresponding symbolic expression.

RaulDurand commented 10 months ago

Quite related is #39104. That's more specifically about 0 < x < 2, but that lowers to the short-circuiting 0 < x && x < 2, just like you have written here.

Thus, the proposed feature could prove useful in extending the functionality of chained inequalities.