Function name conflict: ADL / function merging?

This is exactly the point I am at as well…I still don’t quite get this. Just because two functions have the same name, why can’t we dispatch based on completely unrelated types? Moreover, why is this different than defining inline operators like *? Aren’t those defined all the time for different types in different libraries?

Without the ability to call the same function from different packages, isn’t this an impending disaster? All of the sample julia code suggests using A and many of the packages export all sorts of functions. If any of the the pacakges are lacking discipline and export a common verb as a function, doesn’t this mean confusing incompatibility between packages? Won’t users will be perplexed?

Merging methods doesn’t seem like it is throwing namespaces away entirely as it is opt-in whether you do using A or import A? Even if a method overwrite or two is confusing to users, the inability to have a workaround (i.e., a user can’t convince two packages to form a common base) is much worse?

I do not think that it is equivalent to do away with namespaces, but I see the problems.
I would then fall back to my idea of “adding names to base”. The idea would be
that whenever a package defines a name which could make sense in another package
(like my degree example) it is written as Base.degree.

At the same time, users have the possibility to add names to base (perhaps a special command in .juliarc). This would be a way to use the same name in several packages.

I find the current situation very undemocratic, in that for example I could use the name trace
in several packages (trace on a field extension, trace in a Hecke algebra) because trace is in base and I could define it by extending Base.trace. I had no such luck with degree
because degree is not in Base. Very undemocratic.

1 Like

How do you coordinate with other packages to decide what degree means? Does it relate to temperature, angle, academic qualification, graph vertices, parameters of a mathematical model, or perhaps something else?

trace works because base has decided that it relates to linear algebra and not to, say, tracing the outline of an image; so Base.trace of an image array is unambiguous. If someone wanted to implement the latter, they should make a new, separate function for it.

If you decide what kind of degree you want you can always put it into a small interface package that other packages can coordinate to depend on; then it’s as if the function were in base to begin with.

6 Likes

I think “arbitrary” or “accidental” is better here than “undemocratic”. It doesn’t feel right that general implementation norms should depend on which names happen to be in Base. I mean, that you should in general export fewer names, qualify them, etc. But, if they happen to already be defined in Base you can always get around this by extending the Base function.

At the same time, users have the possibility to add names to base (perhaps a special command in .juliarc). This would be a way to use the same name in several packages.

I’m not sure if you are asking here if there now exists a possibility for users to add names in Base, or whether you are acknowledging that it is in fact possible. In case you mean the former

@eval Base function degree end

will do what you want.

1 Like

@eval Base function degree end

Yeah! I will try this in my packages

I disagree completely with that. ‘Same meaning’ is a meaningless notion in a programming language. Maybe someone who writes a ray-tracing package will find that taking the trace
can be interpreted in some mathematical context as the trace of an operator. Even if that
is not the case, I do not see why you would force him to use a different word than ‘trace’ to talk about a trace.

We can speak English even though the same English word can have
several meanings. I do not see why the same would not be true of a programming language.
Despite trying in Volapuck and Esperanto, no one ever managed to make a natural language were there is a bijection between word and meanings. I do not see why this should be any
more desirable for programming languages.

This does not seem to be reasonable. If I’m writing an image tracer then I would want to name my function trace that takes my custom type. Again, there is no ambiguity when I use the linear algebra one or the image tracer one because they take different arguments.

The only counter argument I have seen so far refers to the risk of type piracy. But it’s not like that we can prevent it altogether anyways. Sometimes we should trust package developers for doing the right thing, or someone could point out the issue and get that resolved.

Some of us are pushing hard because we see this as a usability issue and as the Julia ecosystem grows it can become a bigger mess.

1 Like

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. Also, I’m not sure that the current situation is really that “undemocratic” given that you have multiple different ways to mitigate name collisions with Base. First, you can explicitly import the name you want to use:

julia> module A
           export sin
           sin = 666
       end
Main.A

julia> module B
           import ..A: sin
           👿 = sin
       end
Main.B

julia> B.👿
666

The B module gets A.sin instead of Base.sin. If you want none of Base’s bindings, then you can use a bare module:

julia> baremodule B
           using ..A
           👿 = sin
       end
Main.B

julia> B.👿
666

In general if you really want to future-proof your code, you should use explicit imports of all the names you use from the packages you depend on since otherwise names added to other packages could cause a name ambiguity. Function merging would only mitigate this up to a point since signature collisions could still cause the same thing to happen, which to me is one of the major arguments against it—it doesn’t even fully solve the problem that it aims to solve, the only real solution is full qualification of imported names either by explicit import or qualified usage.

1 Like

Would it be possible for Julia to implicitly qualify function names when method signature is unambiguous, and error when there is method ambiguity?

3 Likes

Meaning is absolutely essential in Julia because it is so very polymorphic. Multiple dispatch forces you to pay attention to the meanings of functions because that’s the only thing that’s consistent when you can both extend generic functions to new types and add new types to existing functions. I think that rejecting meaning as a guiding principle will make Julia a much harder language to use effectively than embracing the significance of meaning.

10 Likes

What’s so sticky about this issue? The problem is that the first person who uses the word gets the precedence and lock in to the meaning of that word. There are 30 other meanings of trace that people want to use but they’re unable to do so in a clean manner. One of the reasons why Julia code looks so much cleaner than Python is that it doesn’t have to prefix every function with an object. Having to qualify functions unnecessarily is just not cool.

I don’t disagree importing specific functions explicitly from a package could be the best practice after all. Even so, why not let people import functions having the same name but different signatures? @greg_plowman 's suggestion above sounds like a good compromise.

2 Likes

Isn’t meaning related to the type? update! or solve or gradient or trace or mean may mean something completely different for different types. The semantics of operators need to be ruled with an iron-fist, but I don’t see how there can be a single “meaning” for every verb.

With both single-dispatch languages, and those with function overloading, isn’t that how it works? As long as a function isn’t ambiguous (i.e., you can’t define the same function on the same type). Coming from that sort of programming it didn’t even cross my mind that you couldn’t have two functions of the same name. I still keep thinking that I am fundamentally misunderstanding the issue.

Users are going to be very very confused, and I still don’t see what the gain is in making their lives difficult and the code more verbose. If it extremely difficult to implement? The package developers you are protecting from breaking changes between major versions of Julia are precisely the ones most capable of handling the nuances of function resolution.

It’s unclear to me if people legitimately don’t know how to do this or if this is just hyperbole. You can easily choose which meaning of a word with multiple meanings you want by explicitly importing the one you want (as I showed in my earlier post). In many other languages you’re required to explicitly import every name you use from another namespace. In Julia you only have to do it if there’s an ambiguity. The only problem case is when there are multiple meanings of a name and you want to use one or more without indicating which one you mean… Which, ok, I get it, but it just doesn’t seem like that big of a problem and it really does not prevent people from using names. Base defines parse yet there are dozens of distinct X.parse functions in various packages. People call them qualified when that’s clearer or they import X.parse and then just call parse.

2 Likes

As far as I can tell, the current approach (i.e., all sample code with using, lots of exported functions to make things look clean, an increasing number of packages, and telling users to put their code in a package/namespace) is untenable in the medium-term. The less technical users are going to be extremely confused, and there will be a huge number of incompatible packages. Following through on the idea that packages with a common function name should form a Base, does this mean that we will have to have those Base use a higher level base? i.e., will an optimizationbase and a solverbase library need to use a common base? Where does that end? A single root base with a list of all function names? Are users supposed to lobby the library writers to coordinate on refactoring into a base library?

It seems there are only 3 choices to resolve the issue:

  1. Go to an equilbrium where everyone writes code with namespaces. That is:
    • Tell package developers to immediately stop exporting function names
    • Tell everyone writing examples for tutorials to stop with the using
    • Train users to qualify everything with a namespace
  2. Create some sort of JuliaVerbs package which basically exports all common function names, and anyone writing a package can use the JuliaVerbs package in order to merge methods. Then you don’t need to manually coordinate all of the packages… Although this sure looks like a workaround to implement method merging…
  3. Allow method merging @greg_plowman example. Focus on the usability, eliminate the confusion of people coming from a single-dispatch world, and keep all of the using ... instead of forcing people to qualify everything. Package developers are the most skilled, and will be able to write the import etc. code which makes it less likely to break in major versions.
1 Like

This is not hyperbole. A simple example:

julia> using Distributions

julia> pdf
pdf (generic function with 72 methods)

julia> mean
mean (generic function with 79 methods)

julia> struct mytype
       end

julia> mean(mt::mytype) = 1
ERROR: error in method definition: function Base.mean must be explicitly imported to be extended

I am confused… why can’t I write a mean function for my own type? Without reading through the documents, I am not even sure what ERROR: error in method definition: function Base.mean must be explicitly imported to be extended even means, or how to fix it. And what is so special about Base there?

A generic function should correspond to only one meaning. There should be one trace function that computes the trace of a matrix and a different trace function that, for example, calls a function and traces everything it does. There can be many different methods for each trace function but they should all be implementing the same conceptual operation. There can be methods of the matrix trace function for different kind of exotic matrix types: dense, sparse, diagonal, bidiagonal, tridiagonal; for matrix factorization objects, for linear operators, etc. But it should not have any methods that trace a function call—that belongs to the other trace function.

1 Like

That seems like a question of the design of an inter-related set of methods, not something intrinsic to the function name trace or mean.

What is so different about Julia? Why does it work so well to allow different meanings with the same function name in pretty much every single-dispatch language?

You can, just do what the error message tells you to do:

julia> import Distributions: mean

julia> struct T end

julia> mean(::T) = 1
mean (generic function with 80 methods)

julia> mean(T())
1

Or do this:

julia> using Distributions

julia> struct T end

julia> Distributions.mean(::T) = 1

julia> mean(T())
1

And what is so special about Base there?

Base.mean is just the full name of the mean function. That’s all. There’s nothing special about Base. If you try to do the same thing with pdf you get an error message about importing Distributions.pdf because that’s the full name of the pdf function:

julia> pdf(::T) = 2
ERROR: error in method definition: function Distributions.pdf must be explicitly imported to be extended

Right now, all of the sample code I see has using. And I love it. There is absolutely no ambiguity about what method I mean to use, and I don’t have to annotate a bunch of redundant namespaces.

Now imagine I am a user writing some code with 20 different libraries (completely reasonable). What if two of them happen to export the same function name? I am screwed, and either need to start putting namespace annotations on every function in them, or lobby the package developers to get their act together.

And if I write my own function, I don’t even know what functions they may have exported! I would be very confused that I can’t write my own function in my own package, and need to insert it into Distributions or something else? Why can’t I write it in my own namespace?

… 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

The names trace and mean aren’t special, they’re just examples. But the principle of a single meaning per function should apply everywhere. Of course, you can do whatever you want to in your own code, but mixing unrelated meanings into a single function is not recommended.

What is so different about Julia? Why does it work so well to allow different meanings with the same function name in pretty much every single-dispatch language?

In class-based languages you’re truly stuck if someone else has already used a name. If a type already uses a method name to mean something and you wanted to use that name, there’s nothing you can do. Suppose you wanted to have x.mean() method that tells you if some object is a mean one or not. If d is a distribution and d.mean() already returns the mean of the distribution, then there’s nothing you can do. You cannot use the method name mean to do something different without breaking the object. In Julia, this is no problem since Distributions.mean and Personality.mean are separate functions and Distributions.mean(d) and Personality.mean(d) can do entirely different things.

4 Likes