How to overload operators and their doc bindings

julia> module Foo
           struct Point
               x::Int
               y::Int
           end
           
           """
               <(a::Point, b::Point) -> Bool
               
           Compare two `Point`s object.
           """
           function <(a::Point, b::Point)
               a.x < b.x || a.y < b.y || (a.x == b.x) && (a.y < b.y)
           end
       end
Main.Foo

help?> Foo.<
search:

Couldn't find Foo.<
Perhaps you meant Foo, Bool or floor
  No documentation found.

  Binding Foo.< does not exist.

help?> Foo.:<
  <(a::Point, b::Point) -> Bool

  Compare two Points object.

Isn’t there a way to define this, so its called with Foo.< as the normal method would have been and not Foo.<?

Note that the help does not work even with the standard <:

help?> Main.<
search:

Couldn't find Main.<
Perhaps you meant Main or Matrix
  No documentation found.

  Binding Main.< does not exist.

Thus, not answering exactly that question, there are two things: Using Foo.< as as that would be very inconvenient if you do not export < from your module.

Yet, if you want to export it, you are probably wanting to overload the Base.<, and in that case you should that explicitly:

julia> module Foo
          
           struct Point
               x::Int
               y::Int
           end
           
           import Base: <  # import here
           """
               <(a::Point, b::Point) -> Bool
               
           Compare two `Point`s object.
           """
           function <(a::Point, b::Point)
               a.x < b.x || a.y < b.y || (a.x == b.x) && (a.y < b.y)
           end
       end
WARNING: replacing module Foo.
Main.Foo

with which < works for your points:

julia> x = Foo.Point(1,1); y = Foo.Point(2,2);

julia> x < y
true

and the help entry appears when typing ? < (among the other definitions for <):


  <(a::Point, b::Point) -> Bool

  Compare two Points object.

Thank you.

But the issue here is that I don’t want other definitions for < showing except the one I defined. That’s what I’m trying to solve.

Yeah, I do not think you can do that right now. It seems a very specific parsing issue the fact that the help> prompt is not able to show Base.< or similar entries.

But be careful that, in your case, you didn’t overload the Base.< operator, you defined a new one, and inside your module < won’t work anymore for any other type except the ones you defined (note that your example does not work, but you don’t have < defined anymore for integers, thus your Foo.< function errors).

Yeah I’m aware. My main issue is the docs bindings, I just wrote a dummy function to test it

I’m experimenting with some stuffs and I don’t want the other docs to show up in the help section

You often need to quote (as :symbol) special symbols like < when qualifying them:

help?> Main.:<
  <(x, y)

  Less-than comparison operator. Falls back to isless. Because of the behavior
  of floating-point NaN values, this operator implements a partial order.
2 Likes

So there’s virtually no way to solve the problem posed at hand?

Works fine for me with quoting:

help?> Foo.:<
  <(a::Point, b::Point) -> Bool

  Compare two Points object.
1 Like

But then how you quote it when it has to do with :: operators.

This is the problem. Operators or functionality with the : in their makeup won’t work well or it will mess up things.

:: is not an ordinary operator and cannot be overloaded; it is a syntactic keyword.

But in general you can use parentheses along with quoting to disambiguate the parsing:

help?> Main.:(:)
  (:)(start, [step], stop)

  Range operator. a:b constructs a range from a to b with a step size of 1 (a
  UnitRange) , and a:s:b is similar but uses a step size of s (a StepRange).

This is not good, but sure it works for now.

But why does the help section works this way?

It’s not the help section, it’s the Julia parser in general. You’ll find that Main.< never works in Julia.

The basic reason is that .< is also a binary operator in Julia. So, Main.< is parsed as the beginning of a comparison Main .< x … the parser then starts looking for the next expression x.

Because of this, Julia provides a way around the ambiguity: you can specify a symbol in a module — or indeed any property of any data structure — as object.:property or object.:(property). e.g.

julia> Main.:<
< (generic function with 74 methods)

julia> z = 3+4im
3 + 4im

julia> z.:re
3

This is useful in lots of contexts, not just for help. Most commonly it is used if you want to add new methods to a base operator, like:

Base.:<(x::String, y::Number) = NaN
4 Likes

Thank you.

But I still count it a limitation that the help section works that way.

Package developers will then have to do mypackage.:operator, just so they can see how a specific operator they defined is used - that’s verbose.

Why not just do mypackage.operator or even possible, set this as a flag, so it can be enabled and disabled🙃

A bracket should have been used or allowed to use:

so Main.:operator can be Main.(operator); at least this makes sense and the syntax will still be clean
for cases where : will be part of the operator

The help section is for Julia, so it should use Julia syntax. What other syntax could it use?

Main.(::) is very concise and sensible than Main.:(::)

I don’t see where Main.(::) clashes with anything in the help section of the REPL for now. Or does it?

No, because Main.(x) denotes a “dot call”, equivalent to broadcast(Main, x).

It’s certainly true that there are many different syntactic styles Julia could have chosen. We could have chosen to be more like Python, or more like Rust, or more like Lisp. But the fact of the matter is we have chosen a syntax, and it’s all inter-related — you can’t just change one aspect without changing everything else. Nor is Julia at the stage of development where we are changing basic syntactic structures (see PSA: Julia is not at that stage of development anymore), and any new syntax must be added in a carefully nonbreaking way and to solve a real problem, not just because we want a slightly different spelling.

(You may think Main.:foo is not “sensible”, but in fact this is very consistent with Julia’s metaprogramming facilities, which require a way of “quoting” symbols.)

2 Likes

Main.:foo is sensible but the fact that Main.:(:) and also the REPL won’t allow Foo.> on the help section isn’t great.

help?> .>
search:

  x .> y is akin to broadcast(>, x, y). See broadcast.

The above is great, but it should allow Foo.> since Foo here is a package or whatever.

  • Foo.> can’t be a broadcast operation as you’ve said. Its a binary operator and it requires a second argument.

  • Foo.> can’t be a broadcast operation as you’ve said. Because operators are not allowed in POSTFIX NOTATION.

  • Operators that are POSTFIX isn’t broadcasted as arg.' but rather as '.arg or arg1 .' arg2.

My point is its isn’t the best style that the help section considers Foo.> a broadcasting operation as you’ve outlined. It just can’t be.

Hence it’s parsed as an incomplete expression:

julia> Meta.parse("Foo.>")
:($(Expr(:incomplete, "incomplete: premature end of input")))

To have a sensible language, whether something counts a complete expression, a single syntactic “unit”, shouldn’t generally depend on what comes after it. We don’t want to parse x .> y as a binary operation but then parse x .> as something completely different.

Of course, you are free to implement your own language with your own syntax and whatever context-dependent grammar strikes your fancy, maybe even as a front-end for Julia if you want.