I think you're probably being overly optimistic about how infrequently
there will be dispatch ambiguities between unrelated functions that happen
to have the same name. I would guess that if you try to merge two unrelated
generic functions, ambiguities will exist more often than not. If you were
to automatically merge generic functions from different modules, there are
two sane ways you could handle ambiguities:

   - warn about ambiguities when merging happens;
   - raise an error when ambiguous calls actually occur.

Warning when the ambiguity is caused is how we currently deal with
ambiguities in individual generic functions. This seems like a good idea,
but it turns out to be extremely annoying. In practice, there are fairly
legitimate cases where you can have ambiguous intersections between very
generic definitions and you just don't care because the ambiguous case
makes no sense. This is especially true when loosely related modules extend
shared generic functions. As a result, #6190
<https://github.com/JuliaLang/julia/issues/6190> has gained a lot of
support.

If warning about ambiguities in a single generic function is annoying,
warning about ambiguities when merging different generic functions that
happen share a name would be a nightmare. Imagine popular packages A and B
both export a function `foo`. Initially there are no ambiguities, so things
are fine. Then B adds some methods to its `foo` that introduce ambiguities
with A's `foo`. In isolation A and B are both fine – so neither package
author sees any warnings or problems. But suddenly every package in the
ecosystem that uses both A and B – which is a lot since they're both very
popular – is spewing warnings upon loading. Who is responsible? Package A
didn't even change anything. Package B just added some methods to its own
function and has no issues in isolation. How would someone using both A and
B avoid getting these warnings? They would have to stop writing `using A`
or `using B` and instead explicitly import all the names they need from
either A or B. To avoid inflicting this on their users, A and B would have
to carefully coordinate to avoid any ambiguities between all of their
generic functions. Except that it's not just A and B – it's all packages.
At that point, why have namespaces with exports at all?

What if we only raise an error when *making calls* to `foo` that are
ambiguous between `A.foo` and `B.foo`? This eliminates the warning
annoyance, which is nice. But it makes code that uses A and B that calls
`foo` brittle in dangerous ways. Suppose, for example, you call `foo(x,y)`
somewhere and initially this can only mean `A.foo` so things are fine. But
then you upgrade B, which adds a method to `B.foo` that also matches the
call to `foo(x,y)`. Now your code that used to work will fail *at run time* –
and only when invoked with ambiguous arguments. This case may be possible
but rare and not covered by your tests. It's a ticking time bomb introduced
into your code just by upgrading dependencies.

The way this issue has actually been resolved, if you were using A and B
and call `foo`, initially only is exported by A, as soon as package B
starts exporting `foo`, you'll get an error and be forced to explicitly
disambiguate `foo`. This is a bit annoying, but after you've done that,
your code will no longer be affected by any changes to `A.foo` or `B.foo` –
it's safe and permanently unambiguous. This still isn't 100% bulletproof.
When `B.foo` is initially introduced, your code that used `foo`, expecting
to call `A.foo`, will break when `foo` is called – but you may not have
tests to catch this, so it could happen at an inconvenient time. But
introducing new exports is *far* less common than adding methods to
existing exports and you are much more likely to have tests that use `foo`
in *some* way than you are to have tests that exercise a specific ambiguous
case. In particular, it would be fairly straightforward to check if the
tests use every name that is referred to anywhere in some code – this would
be a simple coverage measure. It is completely intractable, on the other
hand, to determine whether your tests cover all possible ambiguities
between functions with the same name in all your dependencies.

Anyway, I hope that's somewhat convincing. I think that the way this has
been resolved is a good balance between convenient usage and "programming
in the large".

On Fri, Apr 24, 2015 at 10:55 PM, Michael Francis <[email protected]>
wrote:

> the resolution of that issue seems odd -  If I have two completely
> unrelated libraries. Say DataFrames and one of my own. I export value(
> ::MyType) I'm happily using it. Some time later I Pkg.update(), unbeknownst
> to me the DataFrames dev team have added an export of value( ::DataFrame,
> ...) suddenly all my code which imports both breaks and I have to go
> through the entire stack qualifying the calls, as do other users of my
> module? That doesn't seem right, there is no ambiguity I can see and the
> multiple dispatch should continue to work correctly.
>
> Fundamentally I want the two value() functions to collapse and not have to
> qualify them. If there is a dispatch ambiguity then game over, but if there
> isn't I don't see any advantage (and lots of negatives) to preventing the
> import.
>
> I'd argue the same is true with overloading methods in Base. Why would we
> locally mask get if there is no dispatch ambiguity even if I don't
> importall Base.
>
> Qualifying names seems like an anti pattern in a multiple dispatch world.
> Except for those edge cases where there is an ambiguity of dispatch.
>
> Am I missing something? Perhaps I don't understand multiple dispatch well
> enough?

Reply via email to