Allowing the object.method(args...) syntax as an alias for method(object, args ...)

(Taking the example from above:)

mutable struct IIRFilter
    state::Float64
    alpha::Float64
end

julia> f = IIRFilter(0.5, 0.7)
IIRFilter(0.5, 0.7)           

# This could of course be a much more complicated function, making use of `f.state`
julia> state(f::IIRFilter) = f.state                         
state (generic function with 1 method)                       
                                                             
julia> f.state()                                             
ERROR: MethodError: objects of type Float64 are not callable 
Maybe you forgot to use an operator such as *, ^, %, / etc. ?
Stacktrace:                                                  
 [1] top-level scope                                         
   @ REPL[20]:1                                              

The problem arises when f.state is callable - which call do you choose?

julia> struct Metafilter                                  
         filter::IIRFilter                                
       end                                                
                                                          
julia> filter(m::Metafilter) = m.filter                   
filter (generic function with 1 method)                   
                                                          
julia> (filter::IIRFilter)() = filter.state * filter.alpha
                                                          
julia> m = Metafilter(f)                                  
Metafilter(IIRFilter(0.5, 0.7))                           

# would you have expected this here? It's basically the same as (m.filter)(), which is (::IIRFilter)()
julia> m.filter()                                         
0.35                                                      
                                                          
julia> filter(m)                                          
IIRFilter(0.5, 0.7)                                       
4 Likes

This is what I was referring to when I mentioned “only one method”. I should have been more specific.

In Julia any object can be called like that.

julia> struct Callable end

julia> (::Callable)(x) = 2x

julia> obj = (a=Callable(), b=Callable())
(a = Callable(), b = Callable())

julia> obj.b(5)
10

So there is so syntactical distinction between fields and functions… anything can be called as a function.

6 Likes

One recent thread Is it reasonable to mimic a Python class with mutable structs?

This is the first I’ve heard of such efforts, do you have more information to share about them?

Good completion is probably the holy grail of editor tooling support in Julia land (alongside an integrated hoogle/@which/methodswith search). One avenue that might be interesting to explore is Intellij-style postfix completion, which would allow one to type something like x.[ctrl+space]foo but actually write out foo(x). I noticed that there are extensions that do this for VS Code, but I’m not sure if it’s possible to hook them into the language server or not.

1 Like

Probably this is implicit (or explicit) in the answers above, and I’ve missed it, but consider this example:

julia> struct A
         x::Vector{Int}
       end

julia> f(x) = x[1]
f (generic function with 1 method)

function f clearly does not work for type A, thus I suspect one would not want that given a=A([1,2,3]), typing a.f(... returned anything, or that f was listed in any list of methods that can be applied to type A.

Yet, if we do:

julia> Base.getindex(a::A,i) = a.x[i]

suddenly f is a function that can perfectly work for type a:

julia> f(a)
1

Thus, my impression is that while some subset of the functionality you propose can be obtained (methods that explicitly annotate type A for example), in general that will not be very useful, in particular if developers follow the more or less sensible guidelines that functions should be type-annotated as flexible as possible (see Over-constraining argument types).

2 Likes

No problem, sorry if I ranted at you. I definitely agree we should be stealing good features! However, I think what I’ve been trying to argue and what others have been arguing is that the costs are greater than the benefits.

One other cost that hasn’t really been brought up yet is that julia already has a lot of special syntactic forms that are hard for beginners to learn. We need to be conservative about adding more. I think it’s important to try and come up with powerful syntax extensions that can be generalized to many situations, rather than adding more single purpose syntax.

Fair point. I guess though that as others have already pointed out, there’s a lot of potential for conflicts here between that names of properties and the names of methods laying around in the namespace that makes me very wary of this.

To my eye, I guess I’m just so used to julia that I don’t see what’s so nice about

x_f = f.filter(x)

and why I wouldn’t want to just write

x_f = filter(f,x)

which I think is visually much cleaner.

One other thing I should mention is that there are various proposals for a new anonymous function syntax so that writing filter(_, x) is equivalent to f -> filter(f, x). That way, you could write

x_f = f |> filter(_, x)

if desired. I’m not sure this is a great win, but it’s being considered.

2 Likes

Just because I’m bored. Don’t do this :slight_smile:

macro bless(T, fs...)
    fswitch = map(fs) do f
        :(s === nameof($f) && return (args...;kwargs...) -> $f(t, args...; kwargs...))
    end

    quote
        @eval Main begin
            function Base.getproperty(t::$T, s::Symbol)
                $(fswitch...)
                return getfield(t, s)
            end

            Base.propertynames(t::$T, private::Bool=false) = private ? ($fs..., fieldnames($T)...) : $fs         
        end
    end
end

julia> struct AA
       x::Int
       end

julia> f1(a::AA, x) = a.x + x
f1 (generic function with 1 method)

julia> f2(a::AA, x) = a.x * x
f2 (generic function with 1 method)

julia> f3(a::AA, x, y;z) = z*(a.x - x^y)
f3 (generic function with 1 method)

julia> @bless AA f1 f2 f3

julia> aa = AA(2)
AA(2)

julia> aa.f # type aa. and press tab
f1 f2 f3
julia> aa.f1(6)
8

julia> aa.f2(6)
12

julia> aa.f3(2,3;z=4)
-24

Yes, the name of the macro is a reference to a language which tucked on OO though it probably shouldn’t.

Anyways, I agree that one drawback with functions first is discoverability.

9 Likes

We should really add this it the FAQ
https://docs.julialang.org/en/v1/manual/faq

It would be good if someone could summarised this thread including the downsides and upsides, and make a PR to the docs.

11 Likes

Thank you. Yes this was also apparent from @mcabbott example.
Still C++ has the same issue but that didn’t seem to be a sufficient reason to immediately reject the idea.

( PS One think I appreciate about Bjarne Stroustrup from the few talks I saw and the linked documents is that he is always very pragmatic and he seems to try to always look for the greater good. I’m not claiming I know what the greater good is, I just felt like complimenting Stroustrup :smiley: )

This seems to apply to any auto-completion effort including the one linked earlier in this thread (?(x, y)TAB completes methods accepting x, y by timholy · Pull Request #38791 · JuliaLang/julia · GitHub). But I don’t think its a valid reason to give up on discoverability for the cases where it’s possible to have it. Unless I misunderstood your comment. In fact I would add the discoverability should be “smart” and whenever possible not too verbose.

Nim describes its disambiguation procedure.

https://nim-lang.org/docs/manual.html#templates-limitations-of-the-method-call-syntax

Sometimes people travel halfway around the world to visit an exotic country, then spend the first afternoon asking the locals where they can find a decent hamburger restaurant. It’s understandable because it’s a safe choice (who knows what those crazy foreigners are cooking) but it’s also understandable that the locals are a bit saddened and try to convince the visitor to sample the local cuisine.

So please, have a taste, you may find it delicious. We have a vibrant culture with many master chefs who’ve developed some unique dishes over the years. Some visitors are so taken that they end up moving here permanently. And even if you’re not one of them, you may find when you get home that your tastes have broadened and you can cook burgers in exciting new ways.

19 Likes

See this PR, linked to earlier in the thread.

2 Likes

What I meant is that in principle all functions defined everywhere which do not have type annotations excluding the new type could be applied to a new type. This is what it makes possible I being able to define a new type of Matrix and use everything that is defined in base or other packages that define methods to work with matrices.

Some of them will not make sense and error, of course, but that possibility is one of the strength of multiple dispatch and general programming.

I am inclined to think that a package + macro that provided a list of methods the author of the type would like to be listed is a reasonable choice to improve the experience of the user in the sense OO programers are used to.

2 Likes

@NiclasMattsson , I know it can be fun trying to come up “catchy” analogies, but I won’t take it personally since I’m the farthest thing you can find from the guy the eats hamburger in an exotic country :wink:

To play your game, now that I think about it: there is a recurring discussion in Italy of how come Italy is not the first touristic destination in the world, and gets “beaten” by Spain, France, you name it… when on paper it could “win” against anybody by a huge margin (history, food, weather, music, friendly locals…). The reason is that they rely too much on their “intrinsic” value and ignore completely all organizational aspects that make a vacation pleasant and not stressful. Sad, but true.

(PS just playing a game here, this comment doesn’t really matter for the overall informative discussion)

5 Likes

People here also seem generally pragmatic to me. If you see lack of pragmatism, hopefully you can imagine difficulties and tradeoffs that you dont yet see that are constraining the possibilities, rather than stubbornness!

Basically, we all want autocompletion, but its a hard problem. Largely because we also expect that thousands of loosely typed functions defined in packages we dont even know about (or are not written yet) will work on our objects. That’s a good problem to have, and C++ does not have it. It has the expression problem instead. But it clearly has some drawbacks, like autocompleting them all in a sane way.

I also write C++ and like autocompletion. But knowing both contexts, they are not equivalent and the problem is really more difficult in Julia. Im not sure what else there is to say.

5 Likes

Glad you didn’t take my over-the-top analogy personally. We’re not resisting your suggestion because we’re against syntactic sugar or easier onboarding for pythonistas. The problem (as other have explained above) is that your proposed syntax would likely increase confusion for Julia beginners. The object.method() syntax explicitly indicates a hierarchy that simply does not exist in Julia. Better to make a clean break with syntax from other languages up front and get newbies using multiple dispatch ASAP. Its benefits are subtle and take time to grasp - a beginner may immediately note that it’s more powerful and expressive, but its long-term advantages for the package ecosystem are not obvious.

EDIT: Initially got a different url from what I intended.

6 Likes

To stretch that analogy a bit, a lot of suggestions from newcomers or outsiders on how Julia should be redesigned (in a major or minor way) sounds like me going to Italy and telling them how to make pizza (disclaimer: I only have a vague idea about it; presumably it involves flour at some point).

Its not like Julia is perfect — far from it. But making meaningful suggestions about improving it at this point requires quite a bit of investment. This is how I understand @NiclasMattsson’s comment about “having a taste”: it may make sense to use Julia as is for your first 50–100k LOC, and then see what you are really missing. Chances are that the obj.method syntax won’t feel natural any more, but if it does, you will be in a much better position to argue for it.

12 Likes

Guys, I appreciate the enthusiasm, the feedback and the suggestions, but going back to a more “academic” style of discussion, I would say I would be pretty impressed if the only thing that came out of it initially was a nice documentation page.
Let’s say titled “discovery and auto-completion in Julia” with:

  • problem statement
  • list of possible approaches, not necessarily mutually exclusive. From all-encompassing to minimalistic, with pros and cons: verbosity, ease of use, learning curve, etc and I would include stuff like the Uniform Function Call Syntax
  • current attempts and lessons learned
  • comparison with other languages and their solutions/approaches
  • thesis proposal :slight_smile: “discovery and auto-completion in a generic programming language with multiple-dispatch”
2 Likes