Scott, the behavior you're trying to get sounds to me like "IF this
function exists in Base then I want to extend it, otherwise just make
my own version of the function". That strikes me as a hack. What we've
tended to do is let everybody define whatever they want. Then if we
see the same name appearing in multiple packages, we decide if there
is indeed a common interface, and if so move the packages to using it,
e.g. by creating something like StatsBase or maybe adding something to
Base. But we don't want Base to grow much more, if at all.

Getting an error for using both Base and your package seems annoying,
but alternatives that involve doing "something" silently surely must
be considered worse. If a colliding name gets added to Base, the
default behavior should not be to assume that you meant to interfere
with its behavior.

On Sat, Apr 25, 2015 at 1:57 PM, Jeff Bezanson <[email protected]> wrote:
> 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