July 07, 2006
kris wrote:
> Just for fun, how many folks here have hands-on experience with any of the following languages?
> 
> Algol
> Pascal
> BCPL
> Ada
> Modula
> Simula

Turbo Pascal in high school.
Simula at the University of Oslo, where it was created.
July 07, 2006
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 foo and bar are from different vendors.

You meant modules wumpus and bar :-)

> 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.

This would be the alternate interpretation of the "issue of whether the contents of x.y.z are visible without the 'foo' qualifier" in my last post.  Personally, I think either interpretation would work as I believe collisions only occur when a lookup is being processed, not simply because the symbols happen to exist in the various modules, but your method would help the user avoid accidental non-qualified use of imported symbols.

> 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.

I like this idea irrespective of the other issues being discussed.  For example, one irritating issue for implementing Posix headers is that they are all inter-dependent, and each header is often required to expose particular symbols from other headers.  The only way to do this now in D is to publicly import one module entirely into the other module, and finer-grained control would be far nicer.  Unfortunately, alias can't be used to resolve this issue:

    module A;

    alias foo bar;

----------

    module B;

    private import A;

    alias A.bar bar;

because it causes multiple definition errors.

> 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).

It's because of what Walter said about import namespaces being second-class citizens in D.  Any symbol in the current module automatically has priority over a same-named symbol in an import module.  If you want to use the import symbol instead you must either fully qualify it or alias it locally to put it on equal footing with the other local symbols.


Sean
July 07, 2006
kris wrote:
> Just for fun, how many folks here have hands-on experience with any of the following languages?
> 
> Pascal

To the point that I became a beta tester for Borland for many years.. Pascal 6.0 through Borland Pascal 3 (I think), into the delphi series up through delphi 3, if I remember right.  Somewhere along the way I also entered the c++ compiler beta program, but left the dos/windows world shortly thereafter and never looked back.

Borland Pascal was a _very_ usable language, imho.  I only moved on to c and later c++ due to the companies I worked for.

Later,
Brad
July 07, 2006
On Sat, 08 Jul 2006 06:56:47 +1000, Walter Bright <newshound@digitalmars.com> wrote:

> 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.

Even names that are declared 'private' in the imported module? Is that how you want it to work Walter? If so, why do we bother with 'private'? What's the point?

-- 
Derek Parnell
Melbourne, Australia
July 08, 2006
Sean Kelly 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 foo and bar are from different vendors.
> 
> 
> You meant modules wumpus and bar :-)


Yes, I did ~ thank you



>> 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.
> 
> 
> This would be the alternate interpretation of the "issue of whether the contents of x.y.z are visible without the 'foo' qualifier" in my last post.  Personally, I think either interpretation would work as I believe collisions only occur when a lookup is being processed, not simply because the symbols happen to exist in the various modules, but your method would help the user avoid accidental non-qualified use of imported symbols.
> 
>> 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.
> 
> 
> I like this idea irrespective of the other issues being discussed.  


Me too. It appears to be fairly immune to the kind of issues discussed here, and could be thought of as purely a refinement (or focusing) of the current approach to import.


> For example, one irritating issue for implementing Posix headers is that they are all inter-dependent, and each header is often required to expose particular symbols from other headers.  The only way to do this now in D is to publicly import one module entirely into the other module, and finer-grained control would be far nicer.  Unfortunately, alias can't be used to resolve this issue:
> 
>     module A;
> 
>     alias foo bar;
> 
> ----------
> 
>     module B;
> 
>     private import A;
> 
>     alias A.bar bar;
> 
> because it causes multiple definition errors.
> 
>> 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).
> 
> 
> It's because of what Walter said about import namespaces being second-class citizens in D.  Any symbol in the current module automatically has priority over a same-named symbol in an import module.  If you want to use the import symbol instead you must either fully qualify it or alias it locally to put it on equal footing with the other local symbols.


OK; thanks.
July 08, 2006
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 foo 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.

>> 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 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.
July 08, 2006
Sean Kelly wrote:
> This would be the alternate interpretation of the "issue of whether the contents of x.y.z are visible without the 'foo' qualifier" in my last post.  Personally, I think either interpretation would work as I believe collisions only occur when a lookup is being processed, not simply because the symbols happen to exist in the various modules,

That is correct.
July 08, 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.
July 08, 2006
kris wrote:
> 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?

I want to make sure first that all understand how it currently works, and that the way it works is neither a bug nor erratic.

The following two suggestions:

>>> One way to avoid the re-engineering is to use
>>> "import wumpus as ...."
>>> "import bar as ...."
>>>
>>> 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.

suggest the onus is on the importer, not the importee.

>>>> 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.

But the proposed improvements involve changing the importer code.


>> 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.

But the suggestions involve changing the importer code, too. I'm not seeing the advantage of that over fully qualifying the references or using an alias, both of which will ensure that no future imports will cause name collisions.


> 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.

I know exactly how it works, and it's working as designed (I wish to emphasize the way it works is as intended - it's not a bug and alias is not a workaround). The current module scope overrides any imported symbols, and if a symbol is not in the current module scope but is in more than one import, an ambiguity error occurs (because all imports have equal standing, none is favored over another).

What can be done is something like add a warning whenever a name is found using the second-class import lookup, rather than using an alias or a fully qualified lookup. Then, you'll be able to easily purge your code of any such, and be confident that adding other modules will not break your existing code.

What can also be done is extend the import declaration to allow the .'s to continue so that specific symbols can be imported.
July 08, 2006
Derek Parnell wrote:
> On Sat, 08 Jul 2006 06:56:47 +1000, Walter Bright <newshound@digitalmars.com> wrote:
> 
>> 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.
> 
> Even names that are declared 'private' in the imported module? Is that how you want it to work Walter? If so, why do we bother with 'private'? What's the point?

In class scope, access control is done *after* name lookup, not before. I'm concerned about confusion by reversing the order of that for module scope.