Michael, that's not a bad summary. I would make a couple edits. You
don't really need to qualify *all* uses. If you want to use `foo` from
module `A`, you can put `import A.foo` at the top and then use `foo`
in your code. That will have no surprises and no breakage.

Also I think calling it "SuperSecretBase" makes it sound worse than it
is. You can have modules that describe a certain named interface, and
then other modules extend it. Which reminds me that I need to
implement #8283, so you can introduce functions without adding methods
yet.

On Sat, Apr 25, 2015 at 12:31 PM, Stefan Karpinski <[email protected]> wrote:
> Scott, I'm not really understanding your problem. Can you give an example?
>
>
> On Sat, Apr 25, 2015 at 11:53 AM, Scott Jones <[email protected]>
> wrote:
>>
>> A problem I'm running into is the following (maybe the best practice for
>> this is documented, and I just to stupid to find it!):
>> I have created a set of functions, which use my own type, so they should
>> never be ambiguous.
>> I would like to export them all, but I have to import any names that
>> already exist...
>> Then tomorrow, somebody adds that name to Base, and my code no longer
>> works...
>> I dislike having to explicitly import names to extend something, how am I
>> supposed to know in advance all the other names that could be used?
>>
>> What am I doing wrong?
>>
>> On Saturday, April 25, 2015 at 11:20:14 AM UTC-4, Stefan Karpinski wrote:
>>>
>>> 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 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