import concerns (was Re: Historical language survey)

kris foo at bar.com
Fri Jul 7 18:42:53 PDT 2006


Walter;

I added compilable code at the top of the post here, in a genuine 
attempt to show you where the current design has a weakness. Yet, you 
ignored those clarifications entirely. Focusing instead on pre-clarified 
observations, which could be considered somewhat ambiguous, doesn't help 
much at all :(

The code provided illustrates the weakness quite well. I do hope you'll 
run the example provided, and address that instead?

Please see below:



Walter Bright wrote:
> kris wrote:
> 
>> to clarify, here's some examples:
>>
>> --------------
>> module foo;
>>
>> import bar;
>> import wumpus;
>>
>> extern (C) int printf (char*, ...);
>>
>> class Bar {char[] toString() {return "foo.Bar";}}
>>
>> void main()
>> {
>>         auto bar = new Bar;
>>         auto wumpus = new Wumpus;
>>
>>         printf ("%.*s\n", bar.toString);
>>         printf ("%.*s\n", wumpus.toString);
>> }
>> -------------
>>
>>
>> -------------
>> module bar;
>>
>> class Bar {char[] toString() {return "bar.Bar";}}
>> -------------
>>
>>
>> -------------
>> module wumpus;
>>
>> class Wumpus {char[] toString() {return "wumpus.Wumpus";}}
>> -------------
>>
>>
>> What's interesting here is the lack of conflict between bar.Bar and 
>> foo.Bar. The compiler ignores the conflicting names and uses foo.Bar 
>> within main().
>>
>> Now, assume both modules wumpus and bar are from different vendors. 
>> Module bar gets changed at some point to this:
>>
>> -------------
>> module bar;
>>
>> class Bar {char[] toString() {return "bar.Bar";}}
>>
>> class Wumpus {char[] toString() {return "bar.Wumpus";}}
>> -------------
>>
>>
>> The vendor added a Wumpus class to the module. Quite innocent. In this 
>> case, the program now fails to compile, and the user-code needs to be 
>> re-engineered. The amount of redundant work may be small, or it may be 
>> very large. This is simply redundant work ~ it should not be necessary 
>> at all.
>>
>> One way to avoid the re-engineering is to use
>> "import wumpus as ...."
>> "import bar as ...."
>>
>> In this case, the instances of Bar and Wumpus must be fully qualified 
>> -- you can't access them any other way. Thus it would be "auto bar = 
>> new bar.Bar;", or whatever.
>>
>> Another approach is to import explicitly, just like Modula-3 does:
>>
>> import Bar from bar;
>> import Wumpus from wumpus;
>>
>> In this case, it's pretty clear than any additions to modules from 
>> either vendor will not result in re-engineering work. It's also a bit 
>> closer to the current D model.
>>
>>
>> Another question is this: why is there no conflict between the two Bar 
>> declarations in the first case, while there is between the two Wumpus 
>> instances in the second case? I suspect this is down to where the decl 
>> actually resides (which module).
>>
>>
>>
>>
>>
>>
>> kris wrote:
>>
>>> Walter Bright wrote:
>>>
>>>> kris wrote:
>>>>
>>>>> D imports an entire module, into the current namespace (or some 
>>>>> variation upon that). This means that any additions to the original 
>>>>> module have to be aware of the namespace usage of *any* module that 
>>>>> imports the original. Otherwise, a namespace collision will occur 
>>>>> and the combination will fail to compile. M3 import explicitly from 
>>>>> each module instead ~ you can't have such a collision. The value of 
>>>>> that is just as solid today as it was in 1989.
>>>>>
>>>>> One might argue that with D, one should create new modules instead 
>>>>> of extending existing ones? That's a fair point until you consider 
>>>>> that the module namespace is limited to one file, and the 'friend' 
>>>>> aspect is limited to one module (private attributes being visible 
>>>>> within the one module). Thus, D suffers this problem in a notable 
>>>>> manner.
>>>>>
>>>>> I forget whether M3 supports importing into a distinct namespace or 
>>>>> not --- the "import x.y.z. as foo;" syntax -- but that can 
>>>>> alleviate related problems, and would help resolve the current D 
>>>>> namespace conflicts that are quite prevalant?
>>>>
>>>>
>>>>
>>>>
>>>> import namespaces are second class citizens in D - they are easily 
>>>> overridden by using aliases or fully qualified lookups:
>>>>
>>>> import a;    // defines foo()
>>>> import b;    // defines foo()
>>>>
>>>> foo();        // ambiguous
>>>> a.foo();    // doesn't matter if there's a b.foo
>>>> b.foo();    // works
>>>>
>>>> alias a.foo foo;
>>>> foo();        // works
>>>>
>>>> As for import x.y.z. as foo;, you can do:
>>>>
>>>> alias x.y.z foo;
>>>> foo.bar();
>>>>
>>>> alias x.y abc;
>>>> abc.x.bar();
>>>>
>>>> alias x def;
>>>> def.y.z.bar();
>>>>
>>>> The alias works at any level you choose to make it. Alias can be 
>>>> used to 'import' any name into the current namespace, making it 
>>>> first class.
>>>>
>>>> The second class lookup capability is to make it easier to write 
>>>> quick and dirty programs. Aliases or fully qualified names should be 
>>>> used when writing large, complex apps. Think of it like using 
>>>> private - you wouldn't bother with it for small or throwaway 
>>>> programs, but you wouldn't think of not using it for long lived or 
>>>> complex apps.
>>>
>>>
>>>
>>>
>>> Yes, I'm aware of those various workarounds, but none of them address 
>>> the issue. As I'm sure you're aware of, all of these need to be used 
>>> at the import site ... not in the importee code. This is where the 
>>> issues arise.
>>>
>>> What I was getting at is this:
>>>
>>> -------------
>>> module importee;
>>>
>>> class Foo {}
>>> -------------
>>>
>>> and
>>>
>>> -------------
>>> module importer;
>>>
>>> import importee;
>>>
>>> class Bar {}
>>>
>>> class Bazooka {}
>>> -------------
>>>
>>>
>>> now, suppose we later change module importee like so:
>>>
>>> -------------
>>> module importee;
>>>
>>> class Foo {}
>>>
>>> class Bazooka {}
>>> -------------
>>>
>>> Now, module importer will not compile.
> 
> 
> Yes, it will, because names in the module being compiled take priority 
> over imported names. A conflict only happens if two or more imports 
> define the same name.
> 
>>> The second aspect is the whole alias notion is just too weak to 
>>> handle large-scale development. In other languages, the syntax 
>>> "import x.y.z as foo;" actually does create a unique namespace, 
>>> achieving two things:
>>>
>>> a) there's no other way to refer to x.y.z content other than through 
>>> the "foo." prefix. This eliminates the potential for conflicting 
>>> names across multiple modules, regardless of long-term maintenance in 
>>> any of them. Relying on the D "alias" mechanism for such needs is 
>>> prone to abject failure.
> 
> 
> Why is it prone to abject failure? The only thing really necessary is 
> that the module names themselves be unique or be in a package with a 
> unique name.

Please see the examples at the top of this post, and those in Sean's 
post also.


> 
>>> b) alias simply provides an /additional/ means of referring to some 
>>> element. All of the original names are still there, from the entirety 
>>> of the imported module. The potential for name collisions, such as 
>>> two classes called 'Bazooka' is painfully obvious.
> 
> 
> Not if you use either fully qualified names when importing, or you use 
> an alias to pick which Bazooka you want.

The whole point of this particular thread is to avoid needless 
modifications to code which imports other modules; where those /other/ 
modules change over time. It appears that you're thinking purely in 
terms of "I own all the code, and I'll be responsible for all of the 
namespace issues". I sincerely hope we don't have to spell out the 
limitations with such a notion?

> 
>>> The whole concept of long-term and/or large-scale development using D 
>>> as a tool is marred by such problems -- it's not very hard to fix 
>>> either -- perhaps as simple as the "import x.y.z as foo;" syntax, 
>>> which is quite quite different from the concept of alias. I sincerely 
>>> hope you'll agree on that distinction?
> 
> 
> I'm not sure it's that different. But I also think (because of the 
> example above) there's a misunderstanding about how import name lookups 
> work.

That's exactly why I added the working code examples at the top of the 
post -- so you could try it out with the compiler, and observe for 
yourself what it does. It demonstrates that the current import handling 
is really quite brittle from a large-scale and/or long-term development 
aspect. Please read the clarifications toward the top of this post.



More information about the Digitalmars-d mailing list