Function name conflict: ADL / function merging?

You can and it doesn’t matter what the packages you’re using exported:

julia> using Distributions

julia> pdf() = println("saving PDF file...")
pdf (generic function with 1 method)

julia> pdf()
saving PDF file...

… and all of this is to what end? I don’t see any benefit whatsoever to this artificial need to choose a common namespace for functions

This is the opposite of there being a single common namespace for all functions. This is what allows you to have the same name mean different things in different namespaces.

OK, so the benefit is that this approach lets someone write the same function name on the same types.

But why is that mutually exclusive with the simple approach @greg_plowman suggested? Throw an “ambiguous” error if someone tries to call a function on types without any namespaces. If they want to use Personality.mean(d) and Distributions.mean(d) then they can put in the namespace annotation because it is ambiguous. But it doesn’t force people to annotate with namespaces in order to prevent name conflicts.

1 Like

It’s not and I already said that’s a possible design to explore. And then brought up some issues that make it more complicated than it naively seems. For example, if A.f and B.f both exist and you do using A; using B and then someone adds methods to A.f, what happens? Does the change to A.f affect the merged f? Or does the merged f stay the same, detached from A.f and B.f? What if someone extends the merged f? What effect does that have on the original A.f and B.f functions? These are just two of many problems that such a “simple approach” raises.

Apropos of the approach

@eval Base function degree end

StefanKarpinski says

Note that if you do this, your code may break when you upgrade to future versions of Julia 1.x if, for example, Base decides to define a degree function.

Not at all! The code of my package will be exactly the same (extending Base.degree).
What will happen is that I can delete (must delete) the line above from the “glue code” in my
.juliarc

1 Like

I don’t understand why this works, and my mean example above didn’t… It shows how surprising this behavior is.

Sorry, I was unclear. My point is that in order to have a common function name for packages, they end up having to coordinate on choosing a common namespace (even if the types the function operates on are different). If they don’t, then users are forced to put redundant namespace qualifications on functions that should be perfectly clear

The proliferation of Base packages suggests to me that this is the tip of the iceberg. Why not just put all of the valid names in a JuliaVerbs package, which they can opt-in to using for method merging? Makes things easier…

The only thing you cannot do is use a function to mean one thing and then try to change it to mean a different thing, which is what you example does:

julia> using Distributions

Evaluating pdf causes it to be resolved to mean Distributions.pdf because there’s nothing else it could mean at this point:

julia> pdf
pdf (generic function with 72 methods)

This tries to define pdf as new function local to this namespace, which can’t happen because pdf is already bound to Distributions.pdf and that binding is constant:

julia> pdf() = println("saving PDF file...")
ERROR: error in method definition: function Distributions.pdf must be explicitly imported to be extended

OK. Great. Please, please, please consider this for v0.7 prior to the release. The only viable alternative for new users (training tutorials to do import, avoid using, and annotate everything with namespaces) is a usability nightmare.

This is the single most confusing thing I have run into with Julia. If this sounds like hyperbole, it is not. All of the other quirks I have seen about the Julia environment have a clear path to resolution.

1 Like

I’m not sure how to politely say “no” here. There are no additional features going into 0.7 at this point, let alone really fundamental language changes like this. It’s vaguely plausible that this could be added in a 1.x release in a non-breaking way but even that is a bit questionable since it fairly deep change. Julia has worked the way it currently does for about five years now and is quite usable and easy to reason about once you understand it. While some confusion for new users is understandable, calling it a “usability nightmare” seems rather overstated. There does seem to be quite a bit confusion in this thread about how things actually work, so it seems like some documentation improvements are in order.

6 Likes

Documentation is not going to help prevent package conflicts with using.

In the meantime, is a viable alternative to try to convince package developers to coordinate on a package which lists common function names that don’t happen to be in base, or would that not work? Then they could opt-in to a global method namespace to prevent package conflicts. It wouldn’t be perfect, but at least clashes with using would be eliminated, and it wouldn’t lead to a proliferation of Base libraries? Maybe users would still get confused when trying to create functions with the same names as imported ones, but it is at least a step forward.

It seems to me that the alternative @eval Base function degree end approach doesn’t quite work because it requires users to add that sort of thing in their local file, and adding to Base is a bad idea, even if it works.

I realize that this is really bothering you right now, but I would suggest living with it for a while before deciding that really drastic measures are required. I don’t think this is as big of an issue as it’s being made out to be here. I would have to actively discourage you from telling developers to extend a single function in cases where they only happen to share the same name but do not mean the same thing. As I’ve said, that’s a bad idea and not something people should be doing. The bottom line is that if you’re using Distributions and Personalities and you want to use one of their mean functions, you have to tell Julia which one you want.

2 Likes

I would have to actively discourage you from telling developers to extend a single function in cases where they only happen to share the same name but do not mean the same thing

This notion of meaning is completely subjective and thus should play no role in the design of the programming language. You cannot make people agree with you or between themselves on the meaning of something.

The policy should not be based on this badly defined notion, this will lead nowhere.

I trust that this will not be done without a very thorough discussion in an issue (as it usually happens with other features). I find the current approach to namespaces very transparent, while I am suspicious of solutions which would try to guess what the user meant — that is very difficult in general.

5 Likes

I wonder if you have used other languages with multiple dispatch (not OO) before. Julia’s solution is for namespaces is the standard one in that family. Having programmed in Common Lisp and R (the latter of which attempts to hide quite a bit from the user), I find it very natural, but I understand how it is novel for people who have not used languages where the user is forced to deal with namespaces. Perhaps one can think of this as the price of multiple dispatch, which is how I interpret what Stefan suggested.

Julia is full of features which are novel to people coming from other languages. I think the best approach is to keep an open mind, start with the assumption that these are well-designed and there for a reason, even if that reason may not become apparent for a while.

5 Likes

We have a specific complaint that has nothing to do with newness. Myself I have used about 40 different
programming languages including one based on multiple dispatch (GAP4). What is new for me is to base design on an ill-defined concept like “meaning”.

It’s how freeness of generic programming works. You have to ascribe meaning in order to have something generic, otherwise if you put your “Number” into the differential equation solver and your * means something totally different then you’ll get the wrong answer out. How do you expect that to be resolved? At some point generic codes need to say “this is the operation of _____ on your data type”, and Julia does that through functions and dispatching.

If you’re not dealing with generic programming though, then the discussion about meaning is meaningless because you can ascribe whatever meaning to the function in your own codes of course.

4 Likes

I’m surprised with this. If a package exports f = 3 and another exports f = 2 do you find it very confusing that this cannot be resolved cleanly? You have the same name for two different variables in the same scope.

Now, if package A instead exported, say a dictionary b = Dict(Int => 3) and the other package did import A.b; b[Float64] = 2 then everything is fine because there is only one b which we have extended by adding new keys to it.

4 Likes

It is nice to write readable terse code using natural language. But, natural language is very context sensitive. So context is everything. Of course if you’re reading examples of how to use the Base library, the context is the Base library and there is no ambiguity.

In real domain specific code you will run into conflicts. But resolving them is simple. There is no need to end up with a mess of ever-growing fully qualified names. Just define the names to have the meaning you want them to in the context of your local module. In some contexts it makes most sense to write HTTP.get everywhere, in others having a local definition get(url::String) = HTTP.get(url) makes code more readable. In practice this is quite manageable.

module PersonSimulator

    export Person, helpful, argumentative, mean, troll

    struct Person
        attributes
        Person() = new("😐")
        Person(x) = new(x)
    end

    """
    Simulation based on `p` with added helpfulness.
    """
    helpful(p::Person)::Person = Person(p.attributes * "😇")

    """
    Simulation based on `p` but more prone to argue.
    """
    argumentative(p::Person)::Person = Person(p.attributes * "🤔")

    """
    Simulation based on `p` but with +`x` meanness.
    """
    mean(p::Person; x=5)::Person = Person(p.attributes * repeat("👿", x))

    """
    Simulation based on `p` but more troll like.
    """
    troll(p::Person)::Person = mean(argumentative(p; level=2))
end

Is a mean person average? What does that even mean?
Does this mean that Julia won’t scale to complex systems with domain specific meanings?

No. In practice all you have to do is define what you mean in your local module:

module MyModule

    using ..PersonSimulator

    mean(x) = Base.mean(x)
    mean(p::Person) = PersonSimulator.mean(p)

    @show mean(Person())
    @show mean([1,2,3])
end
5 Likes

I’ve not read the whole thread so someone might have already mentioned this.
I asked this question on S) about 18month back.
And I made a function to do the explicit imports require to go over a clash.

In anycase the point to understand I think about why this is the way it is is that namespaces are actually part of the name.
So a function func declared in namespace Foo has the true name Foo.func.
And a function func declared in namespace Bar has the true name Bar.func.

Even ones you do using Foo or using Bar, that does not itself create a new Main.func and merge those methods in.

3 Likes

What will happen is that I can delete (must delete) the line above from the “glue code”

This is probably what Stefan meant by “code may break” in 1.x. The fix is, as you said, to delete the line in your juliarc. But, if a lot of code does this kind of function declaration, say in packages, then 1.x, 1.y, will often be breaking existing packages. You’ll have to worry about various point versions of Julia used with various versions of packages, etc.

btw. you can also check whether a function is defined, like this isdefined(Base,:degree). Then you can declare a function conditionally.

What is new for me is to base design on an ill-defined concept like “meaning”.

Have you ever encountered a language whose design, or suggested best practice, is based in part on the not-perfectly defined concept of “semantics” ? :wink:

1 Like