I keep getting accused of insisting that every name have only one meaning. Not at all. When you extend a function there are no restrictions. The `connect` methods for GlobalDB and SQLDB could absolutely belong to the same generic function. From there, it's *nice* if they implement compatible interfaces, but nobody will force them to.
Scott, I think you're overstating the damage done by a name collision error. You can't expect to change package versions underneath your code and have everything keep working. A clear, fairly early error is one of the *better* outcomes in that case. In your design, there are *also* situations where an update to a package causes an error or warning in client code. I'll grant you that those situations might be rarer, but they're also subtler. The user might see Warning: modules A and B conflict over method foo( #= some huge signature =# ) What are you supposed to do about that? It's worth pointing out that merging functions is currently very possible; we just don't do it automatically. You can do it manually: using GlobalDB using SQLDB connect(c::GlobalDBMgr, args...) = GlobalDB.connect(c, args...) connect(c::SQLDBMgr, args...) = SQLDB.connect(c, args...) This will perform well since such small definitions will usually be inlined. If people want to experiment, I'd encourage somebody to implement a function merger using reflection. You could write const connect = merge(GlobalDB.connect, SQLDB.connect, conflicts_favor=SQLDB.connect) On Sun, Apr 26, 2015 at 12:10 PM, David Gold <[email protected]> wrote: > You, Jeff and Stefan seem to be concerned with different kinds of > "ambiguity." Suppose I import `foo(T1, T2)` from module `A` and `foo(T2, > T1)` from module `B`. I take you to claim that if I call `foo(x, y)` then, > as long as there is no ambiguity which method is appropriate, the compiler > should just choose which of `A.foo()` and `B.foo()` has the proper > signature. I take you to be concerned with potential ambiguity about which > method should apply to a given argument. > > On the other hand, I take Jeff and Stefan to be concerned with the ambiguity > of to which object the name '`foo()`' refers (though they should chime in if > this claim or any of the following are wayward). Suppose we're the compiler > and we come across a use of the name '`foo()`' while running through the > code. Our first instinct when we see a name like '`foo()`' is to look up the > object to which the name refers. But what object does this name refer to? > You (Scott) seem to want the compiler to be able to say to itself before > looking up the referent, "If the argument types in this use of '`foo()`' > match the signature of `A.foo()`, then this instance of '`foo()`' refers to > `A.foo()`. If the argument types match the signature of '`B.foo()`', then > this instance of '`foo()`' refers to '`B.foo()`'." But then '`foo()`' isn't > really a name, for the referent of a name cannot be a disjunction of > objects. Indeed, the notion of a "disjunctive name" is an oxymoron. If you > use my name, but your reference could be to either me or some other object > depending on context, then you haven't really used my name, which refers to > me regardless of context. > > I suspect that many problems await if you try to adopt this "disjunctive > reference" scheme. In particular, you'd need to develop a general way for > the compiler to recognize not only that your use of '`foo()`' isn't intended > as a name but rather as a disjunctive reference but also that every other > name-like object is actually a name. I strongly suspect that there is no > general way to do this. After all, what sorts of contexts could possibly > determine with sufficient generality whether or not a given name-like object > is actually a name or instead a disjunctive reference. The most obvious > approach seems to be to let the compiler try to determine the referent of > '`foo()`' and, if there is no referent, then to see whether or not there > exist imported functions with the same name. If such imported functions > exist, then perhaps the compiler decides that '`foo()`' is a disjunctive > reference and tries to find an unambiguously matching method. But do we > really want the compiler to assume, just because you used a "name" that has > no referent but matches the name of one or more imported functions, that you > really intend to use that "name" as a disjunctive reference? This is not > even to mention the performance costs associated with trying to look up a > referent-less "name," deciding that the "name" is actually intended as a > disjunctive reference, and then trying to find a matching method and > determine a "true" reference. > > Now, if you're not going to try to implement '`foo()`' as a disjunctive > reference, then it must be a name. But to which object does the name refer? > The obvious choice would be some "merged" function that aggregates all the > unambiguously distinct methods of `A.foo()` and `B.foo()`. But where does > this object live? Do we make a new object, i.e. a function also named > `foo()` whose methods are copies of all the unambiguously distinct methods > of `A.foo()` and `B.foo()` and which belongs to the scope in which `A.foo()` > and `B.foo()` were imported? But then if one imports `A.foo()` and `B.foo()` > in a global scope one thereby creates a global object as the referent of > '`foo()`'. I suppose this isn't so bad for functions, but it seems perverse > that just importing a name from a module should create global object. > > Okay, so maybe we shouldn't create a new object. The alternative is to have > to have our compiler decide upon import to let '`foo()`' refer once and for > all either to `A.foo()` or `B.foo()`, and whichever one it does refer to > will "absorb" all unambiguously distinct methods from the other. The first > problem here is that it is entirely arbitrary which of the two functions > should be the "true" referent, and which should hand over its methods. That > this decision would be entirely arbitrary suggests (to me, at least) that it > is not the best one to make. And regardless of which function is chosen to > be the "true" referent, we still end up with the similarly perverse > byproduct that just importing a name from module `A` results in a change to > an object from module `B`. This seems to defeat the purpose of a module. > > Again, this is just what I understand Stefan and Jeff to be arguing. I hope > that either/both will point out any misinterpretations or errors on my part. > > > > On Sunday, April 26, 2015 at 6:24:15 AM UTC-4, Scott Jones wrote: >> >> Yes, precisely... and I *do* want Julia to protect the user *in that >> case*. >> If a module has functions that are potentially ambiguous, then 1) if the >> module writer intends to extend something, they should do it *explicitly*, >> exactly as now, and 2) Julia *should* warn when you have "using" package, >> not just at run-time, IMO. >> I have *only* been talking about the case where you have functions that >> the compiler can tell in advance, just by looking locally at your module, by >> a very simple rule, that they cannot be ambiguous. >> >> Scott >> >> On Sunday, April 26, 2015 at 5:46:15 AM UTC-4, [email protected] wrote: >>> >>> >>> >>> On Sunday, April 26, 2015 at 7:23:02 PM UTC+10, Scott Jones wrote: >>>> >>>> Ah, but that is NOT the situation I've been talking about... If the >>>> writer of a module wants to have a function that takes ::Any, and is not >>>> using any other types that are local to that package, then, from the rules >>>> I'd like to see, they *would* have to explicitly import from Base (or >>>> whichever module they intended to extend). >>> >>> >>> Neither module is extending anything, they are separate. Its the user >>> that wants to use them both in the same program, and its the user that Julia >>> is protecting. >>> >>> Cheers >>> Lex >>> >>> >>>> >>>> >>>> Scott >>>> >>>> On Sunday, April 26, 2015 at 12:25:28 AM UTC-4, [email protected] wrote: >>>>> >>>>> The situation I was describing is that there is: >>>>> >>>>> module A >>>>> type Foo end >>>>> f(a::Any) ... >>>>> f(a::Foo) ... >>>>> >>>>> which expects f(a) to dispatch to its ::Any version for all calls where >>>>> a is not a Foo, and there is: >>>>> >>>>> module B >>>>> type Bar end >>>>> f(a::Bar) ... >>>>> >>>>> so a user program (assuming the f() functions combined): >>>>> >>>>> using A >>>>> using B >>>>> >>>>> b = Bar() >>>>> f(b) >>>>> >>>>> now module A is written expecting this to dispatch to A.f(::Any) and >>>>> module B is written expecting this to dispatch to B.f(::Bar) so there is >>>>> an >>>>> ambiguity which only the user can resolve, nothing tells the compiler >>>>> which >>>>> the user meant. >>>>> >>>>> Cheers >>>>> Lex >>>>> >>>>> >>>>> On Sunday, April 26, 2015 at 12:00:50 PM UTC+10, Michael Francis wrote: >>>>>> >>>>>> I don't think Any in the same position is a conflict. This would be >>>>>> more of an issue if Julia did not support strong typing, but it does and >>>>>> is >>>>>> a requirement of dynamic dispatch. Consider >>>>>> >>>>>> function foo( x::Any ) >>>>>> >>>>>> Will never be chosen over >>>>>> >>>>>> Type Foo end >>>>>> >>>>>> function foo( x::Foo ) >>>>>> >>>>>> As such I don't get the argument that if I define functions against >>>>>> types I define they cause conflicts. >>>>>> >>>>>> Being in the position of having implemented a good number of modules >>>>>> and being bitten in this way both by my own dev and by changes to other >>>>>> modules I'm very concerned with the direction being taken. >>>>>> >>>>>> I do think formalization of interfaces to modules ( and behaviors) >>>>>> would go a long way but expecting a diverse group of people to >>>>>> coordinate is >>>>>> not going to happen without strong and enforced constructs. >>>>>> >>>>>> As an example I have implemented a document store database interface, >>>>>> this is well represented by an associative collection. It also has a few >>>>>> specific methods which would apply to many databases. It would be nice >>>>>> to be >>>>>> able to share the definition of these common interfaces. I don't advocate >>>>>> adding these to base so how should it be done? >>>>>> >
