julia icon indicating copy to clipboard operation
julia copied to clipboard

Arbitrary Infix Operators

Open jonas-schulze opened this issue 4 years ago • 5 comments

Sometimes it's useful to have more verbose infix operators (e.g. isa). I would like to suggest adding a way to declare arbitrary identifiers <op> to be infix operators. Please consider the following syntax as a placeholder for the underlying idea.

@infix <precedence> <function> <op>
  • <precedence> would correspond to the currently existing lists prec-comparison, prec-plus, prec-times, etc. inside julia-parser.scm. Hence, allowed values should be predefined/limited.
  • <function> is the function to be executed.
  • <op> is the identifier of the new infix operator. This might be considered optional.

Example: Divisibility

@infix :comparison divides

divides(a, b) = b % a == 0

# Usage: `a divides b`

This syntax would also allow creating new infix operators from Unicode symbols, such that things like #39350 could just be done by a package. To simplify that workflow, it would be nice to define "tab completions" in a similar fashion.

@completion <ascii> <symbol>

Example: Independence (probability theory)

@infix :comparison indep ⫫
@completion "indep" ⫫

function indep(A, B) end

# Usage: `A \indep<TAB> B` or `A ⫫ B`

Extension to n-ary operators

The distinction between <function> and <op> allows this syntax to be extended to custom ternary operators.

@infix <precedence> <function> <op1> <op2> ...

Example: Conditional Independence

@infix :n_ary cond_indep ⫫ |

function cond_indep(A, B, C) end

# Potential usage: `A ⫫ B | C` or `@(A ⫫ B | C)`

The latter usage is currently invalid syntax.

Discussions concerning ternary operators (e.g. how to deal with precedence) might be better located in #39353.

jonas-schulze avatar Jan 21 '21 17:01 jonas-schulze

Somewhat related (albeit with a broader scope) is: https://github.com/JuliaLang/julia/issues/16985

This would be a very large change as it would no longer make parsing canonical. Parsing itself would become stateful — and that's something that I know Jeff is very reticent to do.

mbauman avatar Jan 21 '21 18:01 mbauman

Just so; there is a lot of complexity here since not only does parsing become stateful but there are new scoping issues, i.e. there needs to be a way to import/export syntax from a package vs. keeping it local.

JeffBezanson avatar Jan 21 '21 19:01 JeffBezanson

there needs to be a way to import/export syntax from a package vs. keeping it local.

As it's already possible to use binary operators as identifiers,

julia> foo(_) = (_, _) -> true
cond_indep (generic function with 1 method)

julia> ⊕ = foo(42)
#1 (generic function with 1 method)

julia> 1 ⊕ 2
true

could this be handled the same as exporting functions?

module Foo

@infix :comparison compare comp 
compare(_, _) = 42

export compare       # only allows `compare(x, y)`
export comp          # only allows `x comp y`
export compare, comp # allows both

@infix :comparison _comp_
_comp_(_, _) = 21

export _comp_ # allows both `_comp_(x, y)` and `x _comp_ y`

end

jonas-schulze avatar Jan 26 '21 12:01 jonas-schulze

Rather than an @infix macro that turns a function into an infix operator, I think it'd be simpler to make @infix parse the current expression as infix, with every even-numbered top-level expression being treated as a function. So cmp(1, 2) could be written as @infix 1 cmp 2, and you could have e.g., @infix (1 + 2) (rand() < .5 ? cmp : +) (3 + 4), which would return -1 or 10. The @infix macro could also be placed before a begin ... end block to implicitly place @infix before every line.

The macro could also (somehow) take keyword arguments to convey the precedence and associativity of the infix-ed functions.

rben01 avatar Dec 05 '21 22:12 rben01

Any progress on this?

KwatMDPhD avatar Aug 10 '22 04:08 KwatMDPhD