I've been following with interest many of the threads started
by James Harris over the year and frequently the topic of
namespaces and modularity come up. Most recently in the
"Power operator and replacement..." thread.
And I tend to find myself on James' side through unfamiliarity
with the other option. What's the big deal about namespaces
and modules? What do they cost and what do they facilitate?
I've been following with interest many of the threads started
by James Harris over the year and frequently the topic of
namespaces and modularity come up. Most recently in the
"Power operator and replacement..." thread.
And I tend to find myself on James' side through unfamiliarity
with the other option. What's the big deal about namespaces
and modules? What do they cost and what do they facilitate?
On 16/11/2021 16:16, luserdroog wrote:
I've been following with interest many of the threads started
by James Harris over the year and frequently the topic of
namespaces and modularity come up. Most recently in the
"Power operator and replacement..." thread.
And I tend to find myself on James' side through unfamiliarity
with the other option. What's the big deal about namespaces
and modules? What do they cost and what do they facilitate?
namespace timers {
void init();
void enable();
int get_current();
void set_frequency(double);
}
You can use them as "timers::init();" or by pulling them into the
current scope with "using timers; enable();".
The cost is that the identifiers (functions and objects) no longer match
the source code name and the assembly level name - you need some kind of mangling (it could be as simple as $timers$init, if you don't need
mangling for overloads or other purposes).
On 16/11/2021 16:11, David Brown wrote:
namespace timers {
void init();
void enable();
int get_current();
void set_frequency(double);
}
You can use them as "timers::init();" or by pulling them into the
current scope with "using timers; enable();".
As this is about namespaces, I'd suggest some problems with that "using timers" example:
* AIUI it mixes the names from within 'timers' into the current
namespace. If that's right then a programmer taking such an approach
would have to avoid having his own versions of those names. (I.e. no
'enable' or 'init' names in the local scope.)
* If multiple namespaces are imported in the same way then there's
another potential for name conflicts. It's especially bad for names
imported from external files.
* There could be a stable situation with no name conflicts, but then an upgrade to an imported module which is not directly under the
programmer's control could introduce a new name which /does/ conflict.
Therefore namespace support needs to be more extensive and more
restrictive, IMO. For example,
namespace T = lib.org.timers
then
T.enable()
In that, T would be a name in the local scope that the programmer could control and ensure it doesn't conflict with any other names. The import
could be
namespace timers = lib.org.timers
Then the name 'timers' would be local. The programmer would still be /required/ to specify the namespace name as in
timers.enable()
The cost is that the identifiers (functions and objects) no longer match
the source code name and the assembly level name - you need some kind of
mangling (it could be as simple as $timers$init, if you don't need
mangling for overloads or other purposes).
Interesting point. Can you give an example?
On 16/11/2021 16:11, David Brown wrote:
On 16/11/2021 16:16, luserdroog wrote:
I've been following with interest many of the threads started
by James Harris over the year and frequently the topic of
namespaces and modularity come up. Most recently in the
"Power operator and replacement..." thread.
And I tend to find myself on James' side through unfamiliarity
with the other option. What's the big deal about namespaces
and modules? What do they cost and what do they facilitate?
...
namespace timers {
void init();
void enable();
int get_current();
void set_frequency(double);
}
You can use them as "timers::init();" or by pulling them into the
current scope with "using timers; enable();".
As this is about namespaces, I'd suggest some problems with that "using timers" example:
* AIUI it mixes the names from within 'timers' into the current
namespace. If that's right then a programmer taking such an approach
would have to avoid having his own versions of those names. (I.e. no
'enable' or 'init' names in the local scope.)
* If multiple namespaces are imported in the same way then there's
another potential for name conflicts. It's especially bad for names
imported from external files.
* There could be a stable situation with no name conflicts, but then an upgrade to an imported module which is not directly under the
programmer's control could introduce a new name which /does/ conflict.
Therefore namespace support needs to be more extensive and more
restrictive, IMO. For example,
namespace T = lib.org.timers
then
T.enable()
In that, T would be a name in the local scope that the programmer could control and ensure it doesn't conflict with any other names. The import
could be
namespace timers = lib.org.timers
Then the name 'timers' would be local. The programmer would still be /required/ to specify the namespace name as in
timers.enable()
The cost is that the identifiers (functions and objects) no longer match
the source code name and the assembly level name - you need some kind of
mangling (it could be as simple as $timers$init, if you don't need
mangling for overloads or other purposes).
Interesting point. Can you give an example?
On 16/11/2021 16:11, David Brown wrote:
On 16/11/2021 16:16, luserdroog wrote:
I've been following with interest many of the threads started
by James Harris over the year and frequently the topic of
namespaces and modularity come up. Most recently in the
"Power operator and replacement..." thread.
And I tend to find myself on James' side through unfamiliarity
with the other option. What's the big deal about namespaces
and modules? What do they cost and what do they facilitate?
...
namespace timers {
void init();
void enable();
int get_current();
void set_frequency(double);
}
You can use them as "timers::init();" or by pulling them into the
current scope with "using timers; enable();".
As this is about namespaces, I'd suggest some problems with that "using timers" example:
* AIUI it mixes the names from within 'timers' into the current
namespace. If that's right then a programmer taking such an approach
would have to avoid having his own versions of those names. (I.e. no
'enable' or 'init' names in the local scope.)
* If multiple namespaces are imported in the same way then there's
another potential for name conflicts. It's especially bad for names
imported from external files.
* There could be a stable situation with no name conflicts, but then an upgrade to an imported module which is not directly under the
programmer's control could introduce a new name which /does/ conflict.
On 17/11/2021 10:43, James Harris wrote:
On 16/11/2021 16:11, David Brown wrote:
The cost is that the identifiers (functions and objects) no longer match >>> the source code name and the assembly level name - you need some kind of >>> mangling (it could be as simple as $timers$init, if you don't need
mangling for overloads or other purposes).
Interesting point. Can you give an example?
If you know your assembler/linker support characters that are not
allowed in the source language - such as $ - then you can probably make
a neater and more compact name-mangling scheme.
Some languages - like C++ and Ada - support function overloading. In
that case, you cannot have a simple HA identifier scheme such as you
proposed as the mangled names also have to encode the parameter types.
On 17/11/2021 10:51, David Brown wrote:
On 17/11/2021 10:43, James Harris wrote:
On 16/11/2021 16:11, David Brown wrote:
...
The cost is that the identifiers (functions and objects) no longer
match
the source code name and the assembly level name - you need some
kind of
mangling (it could be as simple as $timers$init, if you don't need
mangling for overloads or other purposes).
Interesting point. Can you give an example?
...
If you know your assembler/linker support characters that are not
allowed in the source language - such as $ - then you can probably make
a neater and more compact name-mangling scheme.
Where would such namespace mangling be needed? AFAICS only in two cases.
The first is where tools (compiler, linker, debugger, and so on) were
not hierarchy-aware (HA) so that multipart names would need to be concatenated into a single name.
The second is where another language is not HA and code written in it
needs to refer to a symbol in one in which names /can/ be hierarchical. References in the converse direction, from an HA language to one which
is not HA, would not be a problem.
So AISI if a language and its toolchain are HA then programs written in
that language would have full access to symbols exported from other
modules whatever language those modules were written in.
For example, if there's an assembly module called fred which exports a
name joe then a language which supported hierarchical namespaces could, perhaps, refer to that symbol as
fred.joe
AISI all languages and build tools should be able to work with multipart names. But I suspect that few are!
On Tuesday, November 16, 2021 at 10:11:30 AM UTC-6, David Brown wrote:[snip]
[the thing that was requested]
The piece I'm missing then, is a "using" directive. I suppose this would be scoped to whole functions, because I don't really have any kind of <statement>[snip]
syntax. It's just <expression><expression><expression>...<End-of-function>. Returns are either by Fortran-style copy from a named variable or the value of the last expression if you didn't a specify a return variable.
On Tuesday, November 16, 2021 at 10:11:30 AM UTC-6, David Brown wrote:
[the thing that was requested]
Well, dang that does seem mighty useful. Of my various language projects
the only one where I'm truly designing it (albeit backburnered) is `olmec`, my APL variant. But I may (or may not) have a problem with incorporating namespaces.
For starters, it's APL. Arrays, Characters (21bit unicode), Integers, Multiprecision
Floating Point, infix functions and operators (or verbs and adverbs). But here's
the weird part:
Any contiguous string of non-numeric, non-whitespace, non-bracket characters is an <identifier-string> which then gets peeled apart by scanning left-to-right
for the longest defined prefix.
This enable extremely stupid golfing tricks, like making the letter 'b' be the
addition function and then the expression 'abc' becomes an addition if
'a' and 'c' are suitably defined.
So I kind of have long identifiers and "free" scope-resolution operators.
You can just pick any crazy character you like to be a separator and define
a long name of whatever symbols.
The piece I'm missing then, is a "using" directive. I suppose this would be scoped to whole functions, because I don't really have any kind of <statement>
syntax. It's just <expression><expression><expression>...<End-of-function>. Returns are either by Fortran-style copy from a named variable or the value of the last expression if you didn't a specify a return variable.
I don't even have "if(){}". So far, you have to build it out of lambdas if you want
to do that. Maybe that should be on the high priority list, too.
The long way is IBM APL style DEL definitions. You start a line with DEL
(the downward pointing big triangle) and then a preamble or prototype
on the rest of the line and hit enter. BTW you're typing all this into the interactive editor bc I insist on handling all the unicode internally and maybe
throwing in extra characters from the VT220 graphics sets.
Then you're in a separate mode, entering lines (expressions) for the function to execute sequentially. The del-function-definition mode ends when you
give it a single DEL by itself on a line.
So the question is, stuff it (the "using" directive) into the preamble, which already specifies
(<optional return variable><left arrow>)(optional left arg)(function)(optional right arg);(optional local variables)
Or make it a "statement" or special expression, a directive.
I don't know. Maybe I haven't given enough information for help, but that's what I got.
On Tuesday, November 16, 2021 at 10:11:30 AM UTC-6, David Brown wrote:
[the thing that was requested]
Well, dang that does seem mighty useful. Of my various language projects
the only one where I'm truly designing it (albeit backburnered) is `olmec`, my APL variant. But I may (or may not) have a problem with incorporating namespaces.
For starters, it's APL. Arrays, Characters (21bit unicode), Integers, Multiprecision
Floating Point, infix functions and operators (or verbs and adverbs).
But here's
the weird part:
Any contiguous string of non-numeric, non-whitespace, non-bracket characters is an <identifier-string> which then gets peeled apart by scanning left-to-right
for the longest defined prefix.
This enables extremely stupid golfing tricks, like making the letter 'b' be the
addition function and then the expression 'abc' becomes an addition if
'a' and 'c' are suitably defined.
So I kind of have long identifiers and "free" scope-resolution operators.
You can just pick any crazy character you like to be a separator and define
a long name of whatever symbols.
The piece I'm missing then, is a "using" directive. I suppose this would be scoped to whole functions, because I don't really have any kind of <statement>
syntax. It's just <expression><expression><expression>...<End-of-function>. Returns are either by Fortran-style copy from a named variable or the value of the last expression if you didn't a specify a return variable.
I don't even have "if(){}". So far, you have to build it out of lambdas if you want
to do that. Maybe that should be on the high priority list, too.
On 2021-11-17 10:43, James Harris wrote:
On 16/11/2021 16:11, David Brown wrote:
namespace timers {
void init();
void enable();
int get_current();
void set_frequency(double);
}
You can use them as "timers::init();" or by pulling them into the
current scope with "using timers; enable();".
As this is about namespaces, I'd suggest some problems with that
"using timers" example:
* AIUI it mixes the names from within 'timers' into the current
namespace. If that's right then a programmer taking such an approach
would have to avoid having his own versions of those names. (I.e. no
'enable' or 'init' names in the local scope.)
* If multiple namespaces are imported in the same way then there's
another potential for name conflicts. It's especially bad for names
imported from external files.
You mean making simple names inside the namespace directly visible in
the scope? Because there are two kind of imports:
1. Making the namespace visible without direct visibility of its names.
So you must qualify the simple name Foo from My_Namespace:
My_Namespace.Foo
[ My_Namespace::Foo in C++ ]
2. Making simple names directly visible. So you can omit all parents:
Foo
* There could be a stable situation with no name conflicts, but then
an upgrade to an imported module which is not directly under the
programmer's control could introduce a new name which /does/ conflict.
You are talking about #2. There are two approaches to that:
2.1. Allow direct visiblity so long there is no conflicts inside the
scope. E.g. let both Namespace_1 and Namespace_2 have Foo, but the code
does not refer to Foo, then is OK.
2.2. Disallow it even if there is no conflicts inside the scope. This
would prevent surprises so long the namespaces are stable. Of course a modification of a namespace can break an existing program. However this
is not a problem from the SW development POV, because you must review
the client code anyway if you change any namespace it uses.
Therefore namespace support needs to be more extensive and more
restrictive, IMO. For example,
namespace T = lib.org.timers
then
T.enable()
In that, T would be a name in the local scope that the programmer
could control and ensure it doesn't conflict with any other names. The
import could be
namespace timers = lib.org.timers
Then the name 'timers' would be local. The programmer would still be
/required/ to specify the namespace name as in
timers.enable()
These are "fully qualified names," i.e. #1. Note that T and Timers are
simple names and may still conflict with some other T's and Timers'. You
have further choices:
A. You allow overloading of Timers. Let only one of Timers has Enable:
Timers.Enable
This is OK, if another Timers has no Enable
B. You do not allow it regardless.
Ada has both #1 and #2. It uses #2.1(A). The predefined package Standard
is the root namespace. So, qualified names may use it to disambiguate,
e.g. like
Standard.Timers.Enable
You can mix #1 and #2. I.e. if a fully qualified name is Standard.A.B.C,
then A.B.C is OK because Standard is directly visible #2. If you make Standard.A directly visible, then B.C would be also OK. If you make Standard.A.B directly visible, then you could use just C.
I would prohibit option 2 (making imported names directly visible)
altogether (although I would allow names to be searched for in different scopes - but that's getting in to another topic).
If the language allows hierarchical names and aliases as in
namedef T = long.complex.path.timers
where T becomes an alias of the name on the RHS then a programmer could
refer to any name A within timers as
T.A
That's no hardship. Further, the name T would defined locally so would
not have any surprising conflicts.
For full disclosure (!) I would like to go slightly further and allow imported names to follow a prefix - i.e. without the dot. For example,
say the syntax allowed something like
namedef P* = long.complex.path.timers.*
where P became a prefix to all the names in timers. Then timers.enable
could be invoked as
Penable()
In a sense, P would replace "timers." including the dot.
Overloading is a big topic but I think there would have to be only one
name such as Timers in each scope. Remember that such a name would be
local to the source file that the programmer is editing. Just as a C programmer should not declare
int i, i;
it ought to be wrong to declare Timers twice in the same scope.
On 2021-11-21 11:11, James Harris wrote:
I would prohibit option 2 (making imported names directly visible)
altogether (although I would allow names to be searched for in
different scopes - but that's getting in to another topic).
You cannot, otherwise you will have to name all local scopes:
void Foo ()
{
int X;
X := 1; // Illegal!
}
If the language allows hierarchical names and aliases as in
namedef T = long.complex.path.timers
where T becomes an alias of the name on the RHS then a programmer
could refer to any name A within timers as
T.A
Which is worse than the original problem. Renaming is a dangerous thing
and you are asking for overusing it because otherwise the code would be unreadable with extremely long names.
That's no hardship. Further, the name T would defined locally so would
not have any surprising conflicts.
It is. Basically instead of resolving the issue you burden the
programmer with renaming and invoke havoc in maintenance because who
would manage and document all these renamings, if not the language?
A problem related to visibility and renaming is transitivity of using.
Consider B that uses or renames things in A. If C uses B will it see the
uses and renamings of B?
In Ada it will see no uses but all remanings. Before you say it is good,
it is not straightforward, because there is no way to maintain derived namespaces like B. If B uses a lot of other namespaces there is no
simple way to shape the effect using B will have in C. Again, renaming
is not a solution in the real-world projects.
For full disclosure (!) I would like to go slightly further and allow
imported names to follow a prefix - i.e. without the dot. For example,
say the syntax allowed something like
namedef P* = long.complex.path.timers.*
where P became a prefix to all the names in timers. Then timers.enable
could be invoked as
Penable()
In a sense, P would replace "timers." including the dot.
Abhorrent.
Overloading is a big topic but I think there would have to be only one
name such as Timers in each scope. Remember that such a name would be
local to the source file that the programmer is editing. Just as a C
programmer should not declare
int i, i;
it ought to be wrong to declare Timers twice in the same scope.
Except that you cannot control it unless you rename each and every
entity of each namespace manually.
The problem is with means supporting manipulation of namespaces as a
*whole*, like using does. These impose problems. Individual renamings is
not a solution, it makes things much worse.
On 17/11/2021 17:16, James Harris wrote:
For example, if there's an assembly module called fred which exports a
name joe then a language which supported hierarchical namespaces could,
perhaps, refer to that symbol as
fred.joe
AISI all languages and build tools should be able to work with multipart
names. But I suspect that few are!
All of what you write here sounds perfectly reasonable - but that last
part is a big issue. You typically have to interact with other tools in
a system - writing /everything/ yourself is not practical. So at some
point you have to switch to "flat" names - and if you still need to
encode hierarchical information there, you need some kind of mangling.
On 21/11/2021 11:05, Dmitry A. Kazakov wrote:
On 2021-11-21 11:11, James Harris wrote:
I would prohibit option 2 (making imported names directly visible)
altogether (although I would allow names to be searched for in
different scopes - but that's getting in to another topic).
Throughout your post you seem to be talking about something other than
what I proposed. I cannot work out what model you have in mind but I'll
reply as best I can.
You cannot, otherwise you will have to name all local scopes:
void Foo ()
{
int X;
X := 1; // Illegal!
}
I didn't suggest that. In your example your reference to X would use the
X declared above it.
Which is worse than the original problem. Renaming is a dangerous
thing and you are asking for overusing it because otherwise the code
would be unreadable with extremely long names.
The name was long to illustrate a point.
That's no hardship. Further, the name T would defined locally so
would not have any surprising conflicts.
It is. Basically instead of resolving the issue you burden the
programmer with renaming and invoke havoc in maintenance because who
would manage and document all these renamings, if not the language?
There's no renaming.
If you mean aliasing then that depends on the
details of the scheme. Consider
int Outer;
void F(void)
{
int Inner;
X
}
Under 'nominal' schemes that we are all familiar with, at point X the
names Inner and Outer would be visible.
If we stick to that and add an
alias such as
int Outer;
alias External = A.B.C.D.E; <=== line added to the prior example
void F(void)
{
int Inner;
X
}
Are you saying that at point X name External should not be visible? If
so, why should it not?
A problem related to visibility and renaming is transitivity of using.
Consider B that uses or renames things in A. If C uses B will it see
the uses and renamings of B?
Let me check what you mean. Say we were to have
module A
public name A1
endmodule
module B
public alias B1 = A.A1
endmodule
Are you saying that another module, C, should not refer to the B.B1 name?
In Ada it will see no uses but all remanings. Before you say it is
good, it is not straightforward, because there is no way to maintain
derived namespaces like B. If B uses a lot of other namespaces there
is no simple way to shape the effect using B will have in C. Again,
renaming is not a solution in the real-world projects.
Say module B presents a certain interface. Why should it not collect
together and present names from other modules?
For full disclosure (!) I would like to go slightly further and allow
imported names to follow a prefix - i.e. without the dot. For
example, say the syntax allowed something like
namedef P* = long.complex.path.timers.*
where P became a prefix to all the names in timers. Then
timers.enable could be invoked as
Penable()
In a sense, P would replace "timers." including the dot.
Abhorrent.
:-) Do you have a semantic objection to it or is it purely that you
don't think a programmer should be offered such a facility - perhaps
because you think it's too close to having names require the dot?
Consider a case where lots of names are imported. For example, say an external module had the tens or hundreds of mnemonics for an assembly language. In the context of this discussion one could consider that
there are three choices:
1. Import all mnemonics into the current scope - which would cause the problems mentioned in my prior post.
2. Give them all a common name as with the x in
x.add
x.and
x.call
etc
3. Use a prefix as in the x in
xadd
xand
xcall
Except that you cannot control it unless you rename each and every
entity of each namespace manually.
It's typically only external namespaces which would have to be given an alias. Within a single module normal scope resolution could apply -
although I may take a different approach which I'll not go in to just
now as it would muddy the waters.
The problem is with means supporting manipulation of namespaces as a
*whole*, like using does. These impose problems. Individual renamings
is not a solution, it makes things much worse.
To be clear, what would normally be aliased would be entire namespaces,
not individual symbols (although they could be aliased if required).
What's the big deal about namespaces
On 2021-11-21 14:55, James Harris wrote:
On 21/11/2021 11:05, Dmitry A. Kazakov wrote:
On 2021-11-21 11:11, James Harris wrote:
I would prohibit option 2 (making imported names directly visible)
altogether (although I would allow names to be searched for in
different scopes - but that's getting in to another topic).
Throughout your post you seem to be talking about something other than
what I proposed. I cannot work out what model you have in mind but
I'll reply as best I can.
You cannot, otherwise you will have to name all local scopes:
void Foo ()
{
int X;
X := 1; // Illegal!
}
I didn't suggest that. In your example your reference to X would use
the X declared above it.
You said no directly visible names. Now you have directly visible X,
instead of Foo.X.
int Outer;
void F(void)
{
int Inner;
X
}
Under 'nominal' schemes that we are all familiar with, at point X the
names Inner and Outer would be visible.
Why different rules for nesting?
If we stick to that and add an alias such as
int Outer;
alias External = A.B.C.D.E; <=== line added to the prior example
void F(void)
{
int Inner;
X
}
Are you saying that at point X name External should not be visible? If
so, why should it not?
Not only it. Outer and Inner must not be visible either. Inner is not a proper name. They actually are:
Root.Outer
Root.External
Root.F.Inner
Again, provided you hold to your proposal not to allow direct
visibility. Otherwise, you have a problem of irregularity of the rules.
Since Ada has such rules, I can tell you that this is quite annoying and
the source of a rift between people favoring fully qualified names vs
ones who prefer the direct names. Note that the choice directly
influences the design, since you must name things differently if you are
a true Lilliputian vs a treacherous Blefuscudian... (:-))
Anyway, the point is that direct visibility exist for a reason. That
reason is valid for both nested and imported scopes.
No. I am saying this:
module A
public name A1
endmodule
module B
use A
-- A1 is directly visible here?
endmodule
module C
use B
-- Is A1 directly visible here?
endmodule
In Ada it will see no uses but all remanings. Before you say it is
good, it is not straightforward, because there is no way to maintain
derived namespaces like B. If B uses a lot of other namespaces there
is no simple way to shape the effect using B will have in C. Again,
renaming is not a solution in the real-world projects.
Say module B presents a certain interface. Why should it not collect
together and present names from other modules?
Because "use" is non-transitive in Ada. [You want to prohibit "use" altogether.]
Consider a case where lots of names are imported. For example, say an
external module had the tens or hundreds of mnemonics for an assembly
language. In the context of this discussion one could consider that
there are three choices:
1. Import all mnemonics into the current scope - which would cause the
problems mentioned in my prior post.
2. Give them all a common name as with the x in
x.add
x.and
x.call
etc
3. Use a prefix as in the x in
xadd
xand
xcall
... and have all alleged problems.
The proper solution is for the language to provide means to maintain namespaces at the *module* side. The designer of the module chooses the
way mnemonics to be used in the client code. Note, not otherwise.
The
client code may only apply some emergency exceptional rules to resolve problems, but there should be none with *properly designed* modules.
On 2021-11-21 17:43, James Harris wrote:
And in case it's not clear, External would allow access to any names
within it. For example,
alias External = A.B.C.D.E;
void F(void)
{
int inner = External.baseval;
}
where baseval is a name within E.
Sure, unless it gets hidden due to name conflicts. As I said aliases
solve nothing. E.g. your example is valid in Ada and represents renaming
a package:
package External renames A.B.C.D.E;
It is used rarely and not for the purpose you are talking. The most
frequent use case is platform-dependent packages. E.g. you would have
package Real_Time_Extensions renames Win32.A.B.C.E;
and another compilation unit
package Real_Time_Extensions renames Linux.A.B.C.E.D;
All through the client code you refer on
with Real_Time_Extensions;
and swap files using the project target. Of course the public interfaces
must be same.
No. I am saying this:
module A
public name A1
endmodule
module B
use A
-- A1 is directly visible here?
endmodule
module C
use B
-- Is A1 directly visible here?
endmodule
OK. I wouldn't allow the "use" statements so the issue would not apply.
Yes, you would alias all 1000+ functions and objects manually.
"use" is
merely a tool to automate the process of creating aliases. If you ever
worked with C++ you would know how difficult to build a C++ DLL
exporting classes. This is basically same problem. There are modifiers
you can apply to a class to export everything required.
Unfortunately in
produces a huge tail of errors. A newly designed language must address
such automation problems, if you do that afterwards.
The proper solution is for the language to provide means to maintain
namespaces at the *module* side. The designer of the module chooses
the way mnemonics to be used in the client code. Note, not otherwise.
I accept that that is your view but I cannot agree with it. AISI it's
important for the client to remain in control.
No, it is important to protect clients from stupid mistakes. Because
here you have a bug multiplier. Each client can introduce a bug to fix.
If the interface is stable and that includes which names the client may
use *by safe default*, you reduce that to a single fault point.
If, by contrast, such choices were made in the module providing the
service then the propagation of names would be invisible to the client
code.
Yes, if the client should not see them, that would be the module
interface designer's choice.
And in case it's not clear, External would allow access to any names
within it. For example,
alias External = A.B.C.D.E;
void F(void)
{
int inner = External.baseval;
}
where baseval is a name within E.
No. I am saying this:
module A
public name A1
endmodule
module B
use A
-- A1 is directly visible here?
endmodule
module C
use B
-- Is A1 directly visible here?
endmodule
OK. I wouldn't allow the "use" statements so the issue would not apply.
The proper solution is for the language to provide means to maintain
namespaces at the *module* side. The designer of the module chooses
the way mnemonics to be used in the client code. Note, not otherwise.
I accept that that is your view but I cannot agree with it. AISI it's important for the client to remain in control.
If, by contrast, such choices were made in the module providing the
service then the propagation of names would be invisible to the client
code.
On 17/11/2021 09:43, James Harris wrote:
On 16/11/2021 16:11, David Brown wrote:
On 16/11/2021 16:16, luserdroog wrote:
I've been following with interest many of the threads started
by James Harris over the year and frequently the topic of
namespaces and modularity come up. Most recently in the
"Power operator and replacement..." thread.
And I tend to find myself on James' side through unfamiliarity
with the other option. What's the big deal about namespaces
and modules? What do they cost and what do they facilitate?
...
namespace timers {
void init();
void enable();
int get_current();
void set_frequency(double);
}
You can use them as "timers::init();" or by pulling them into the
current scope with "using timers; enable();".
As this is about namespaces, I'd suggest some problems with that
"using timers" example:
* AIUI it mixes the names from within 'timers' into the current
namespace. If that's right then a programmer taking such an approach
would have to avoid having his own versions of those names. (I.e. no
'enable' or 'init' names in the local scope.)
It depends on how you make it work. Languages already work with multiple instances of the same name within nested scopes; it will match the name
in the nearest surrounding scope.
With 'using', it could just introduce an extra scope beyond the ones
that are already known.
With two 'using's in two nested scopes, it gets a bit more involved:
{ using ns1;
{ using ns2;
enable();
If 'enable' exists in both ns1 and ns2, will there be a conflict, or
will it use ns2?
I don't have a 'using' statement. My namespaces are only linked to
module names. And all modules in a program (now, a subprogram) are
visible at the same time. Then there can be a conflict:
import A # both export 'enable'
import B
proc enable = {}
enable()
This is OK; 'enable' is found within the normal set of scopes. But
delete the proc definition, and then there is a conflict.
* If multiple namespaces are imported in the same way then there's
another potential for name conflicts. It's especially bad for names
imported from external files.
Yes, there's a conflict. The same as if you declared 'abc' then declared another 'abc' in the same scope. Or removed braces so that two 'abc's
are now in the same block scope.
* There could be a stable situation with no name conflicts, but then
an upgrade to an imported module which is not directly under the
programmer's control could introduce a new name which /does/ conflict.
There are all sorts of issues, but most of them already occur with
nested scopes:
{
double abc;
{
int abc;
printf(fmt, abc);
Here, the 'int abc;' could be deleted or commented out. Now 'abc'
matches the other definition, with no detectable error.
No need! The programmer would usually refer to the namespace (or an
alias thereof). For example, if there was a namespace ns1 which had many names within it:
namespace ns1
name A
name B
name C
1000 other names
end namespace
and the programmer imported it with
alias ns1 = whereever.ns1
then the name "ns1" would be entered into the current scope and the
program could refer to names within that namespace with such as
ns1.A
2. When names are imported from two or more modules there's no way for a programmer to be able to tell where a certain name is referring to. For example,
from mod1 import *
from mod2 import *
print a_name
there's no way to tell whether a_name comes from mod1 or mod2.
Maybe I am misunderstanding what you mean. Could you illustrate it with
a small example?
Under the scheme I have in mind names would still have protections such
as being private or public. Private names would not be visible to
another module.
On 17/11/2021 14:01, Bart wrote:
It depends on how you make it work. Languages already work with
multiple instances of the same name within nested scopes; it will
match the name in the nearest surrounding scope.
AISI there are two issues:
1. Definition. How to declare a name within a certain scope.
2. Searching. The order in which to search scopes for a name.
On 2021-11-21 18:57, James Harris wrote:
No need! The programmer would usually refer to the namespace (or an
alias thereof). For example, if there was a namespace ns1 which had
many names within it:
namespace ns1
name A
name B
name C
1000 other names
end namespace
and the programmer imported it with
alias ns1 = whereever.ns1
then the name "ns1" would be entered into the current scope and the
program could refer to names within that namespace with such as
ns1.A
Like
A ns1.+ B
And since you have no direct visibility you have a problem with prefix notation.
ns1.A.Print ()
Why Print is visible?
I do not want to explain further, I am not a language lawyer, but there
exit massive problems with your approach if you have generic and other polymorphic bodies. In essence all methods must be visible in order to prevent calling wrong a method, since you implicitly convert to the
parent if the overriding is not visible. Ada had such issues until 2005.
2. When names are imported from two or more modules there's no way for
a programmer to be able to tell where a certain name is referring to.
For example,
from mod1 import *
from mod2 import *
print a_name
there's no way to tell whether a_name comes from mod1 or mod2.
You can use fully qualified name to disambiguate.
On 21/11/2021 17:19, James Harris wrote:
2. Searching. The order in which to search scopes for a name.
I'm working on related matters right now.
First I'm revising my module
schemes across two languages, which also affects how namespaces work.
But there was also the issue of finding a module or support file within
the file system. I had had a stack of possible search paths, but I've
dropped that in order to have more confidence in exactly what version of
a file will be found, and where.
That's why I say an option is to have a reserved
name such as underscore. Then factorial.xx can include
function _ (parameter)
... function body ...
endfunction
and that function could be invoked simply as function(...) from the
outside.
On 21/11/2021 19:32, Bart wrote:
On 21/11/2021 17:19, James Harris wrote:
...
2. Searching. The order in which to search scopes for a name.
I'm working on related matters right now.
If you find a really good scheme ... be sure to share! ;-)
First I'm revising my module schemes across two languages, which also
affects how namespaces work.
But there was also the issue of finding a module or support file
within the file system. I had had a stack of possible search paths,
but I've dropped that in order to have more confidence in exactly what
version of a file will be found, and where.
I am unsure about resolving names by search paths. In some ways they
make a lot of sense but in others they introduce uncertainty.
You may have already thought of this but in some cases it can be
convenient to have a reserved name to represent any namespace itself -
in this case to represent the file. For example, say you wanted to write
a factorial function and your language extension was xx. You might
create a file called
factorial.xx
That's fairly normal. But then say within it that you create a function
to compute the factorial. You would naturally call it 'factorial' but
then its name from the outside of the file would be factorial.factorial
which could be a bit irritating. :-(
It would be better for a caller simply to refer to the function
'factorial' yet you cannot drop the function header and begin
factorial.xx in function mode because, for example, you need to specify
a parameter or two. That's why I say an option is to have a reserved
name such as underscore. Then factorial.xx can include
function _ (parameter)
... function body ...
endfunction
and that function could be invoked simply as function(...) from the
outside.
On Saturday, November 20, 2021 at 9:19:14 PM UTC-6, luserdroog wrote:[snip]
On Tuesday, November 16, 2021 at 10:11:30 AM UTC-6, David Brown wrote:[snip]
[the thing that was requested]
The piece I'm missing then, is a "using" directive. I suppose this would be scoped to whole functions, because I don't really have any kind of <statement>[snip]
syntax. It's just <expression><expression><expression>...<End-of-function>. Returns are either by Fortran-style copy from a named variable or the value of the last expression if you didn't a specify a return variable.
Into the meat of the situation...
So the question is, stuff it (the "using" directive) into the preamble, which already specifies
(<optional return variable><left arrow>)(optional left arg)(function)(optional right arg);(optional local variables)
Or make it a "statement" or special expression, a directive.
I don't know. Maybe I haven't given enough information for help, but that's what I got.
On Saturday, November 20, 2021 at 9:48:06 PM UTC-6, luserdroog wrote:
So the question is, stuff it (the "using" directive) into the preamble, which
already specifies
(<optional return variable><left arrow>)(optional left arg)(function)(optional right arg);(optional local variables)
Or make it a "statement" or special expression, a directive.
I don't know. Maybe I haven't given enough information for help, but that's >> what I got.
Erhm. That was not the meat of the situation, just the surface syntax. sigh. The real issue is how to implement the directive. I have support for Weizenbaum environment chains, although so far there are only ever two environments: global or local to a (DEL) function.
On 21/11/2021 22:43, James Harris wrote:
You may have already thought of this but in some cases it can be
convenient to have a reserved name to represent any namespace itself -
in this case to represent the file. For example, say you wanted to
write a factorial function and your language extension was xx. You
might create a file called
factorial.xx
That's fairly normal. But then say within it that you create a
function to compute the factorial. You would naturally call it
'factorial' but then its name from the outside of the file would be
factorial.factorial which could be a bit irritating. :-(
It would be better for a caller simply to refer to the function
'factorial' yet you cannot drop the function header and begin
factorial.xx in function mode because, for example, you need to
specify a parameter or two. That's why I say an option is to have a
reserved name such as underscore. Then factorial.xx can include
function _ (parameter)
... function body ...
endfunction
and that function could be invoked simply as function(...) from the
outside.
I don't quite get this (I've seen your follow-up correction).
If I create such a file called fact.m:
global function fact(int n)int = {(n<=1 | 1 | n*fact(n-1))}
And I call it from the lead module:
module fact # (new module scheme uses 'module')
proc start=
println(fact.fact(12))
println(fact(12))
end
Either of those designations will work.
fact() will first resolve to the exported fact.fact routine before the
module name.
But fact.fact will resolve first to the module name.
So there is no ambiguity since there is only one actual fact function,
and the rules say that module names are resolved first, for the A in A.B.
But if there is a local version of fact():
fact() resolves to that local version.
fact.fact() is an error: the first 'fact' resolves to that local function, which does not contain a definition of 'fact'.
Generaly, module names that match function names etc are a nuisance (especially in my dynamic languages where a standalone module name not followed by "." is a valid expression term).
But there is a workaround for this example:
module fact as ff
function fact(int n)int = {n*2}
proc start=
println(ff.fact(12))
println(fact(12))
end
Here, I've provided an alias for the module name 'fact', so that I can
use 'ff' to remove the ambiguity.
On 21/11/2021 22:43, James Harris wrote:
On 21/11/2021 19:32, Bart wrote:
But there was also the issue of finding a module or support file
within the file system. I had had a stack of possible search paths,
but I've dropped that in order to have more confidence in exactly
what version of a file will be found, and where.
I am unsure about resolving names by search paths. In some ways they
make a lot of sense but in others they introduce uncertainty.
No that was about finding source files. The similarity was in using a
list of search folders, and trying each of them in turn, and using a
list of scopes.
While files are not limited to one or two folders, top-level names are limited to a fixed number of scopes, perhaps 3 or 4.
On 22/11/2021 01:54, Bart wrote:
So there is no ambiguity since there is only one actual fact function,
and the rules say that module names are resolved first, for the A in A.B.
As I just wrote in another reply to the same post perhaps we should get
away from the idea of a name being resolved to the first match from
multiple places. That may work for human convenience when typing things
in to a command line but there's no need for it when a programmer
prepares a source file and can specify in it exactly what he's referring
to.
It's well illustrated in your example about imports. Why would you want
fact.fact
fact
both to resolve to the same thing?
External names could potentially be long with many parts but I'd still
have the ability to define a local alias for anything external so that
it could be referred to by a short name in program code.
As I say, the above is about imports. What of names within nested scopes
in our code? I don't know, yet. Jury's out!
But if there is a local version of fact():
fact() resolves to that local version.
fact.fact() is an error: the first 'fact' resolves to that local
function, which does not contain a definition of 'fact'.
Generaly, module names that match function names etc are a nuisance
(especially in my dynamic languages where a standalone module name not
followed by "." is a valid expression term).
But there is a workaround for this example:
module fact as ff
function fact(int n)int = {n*2}
proc start=
println(ff.fact(12))
println(fact(12))
end
Here, I've provided an alias for the module name 'fact', so that I can
use 'ff' to remove the ambiguity.
I prefer that. It's clearer. In the call, ff clearly relates to the ff defined above.
Alternatively, what about fact.m including reserved function name _ as in
global function _(int n)int = {(n<=1 | 1 | n*fact(n-1))}
such that the caller's code would by naming the module refer to the _ function within it as in
module fact # (new module scheme uses 'module')
proc start=
println(fact(12))
end
On 23/11/2021 17:52, James Harris wrote:
On 22/11/2021 01:54, Bart wrote:
C:\fact>type \fact\fact
This is FACT.
The difference is that here, it will settle for the first match; it will
no report an ambiguity if there are multiple fact.exe files accessible.
They both resolve to the same thing because they do. A file system gives
you the choice of an absolute or relative path, when it is possible.
So can a langage. The advantage of an absolute path, like fact.fact, is
that it will work from anywhere.
Alternatively, what about fact.m including reserved function name _ as in
global function _(int n)int = {(n<=1 | 1 | n*fact(n-1))}
(What about the 'fact' in the body of the function? Actually what would
this function be called? They can't all be _!)
such that the caller's code would by naming the module refer to the _
function within it as in
module fact # (new module scheme uses 'module') >>
proc start=
println(fact(12))
end
I don't get this. Module 'fact' might contain 1000 functions, all with different names.
Is this still about the module and that one function of many sharing the
same name?
What problem is this solving?
On 23/11/2021 20:38, Bart wrote:
On 23/11/2021 17:52, James Harris wrote:
On 22/11/2021 01:54, Bart wrote:
...
C:\fact>type \fact\fact
This is FACT.
...
The difference is that here, it will settle for the first match; it
will no report an ambiguity if there are multiple fact.exe files
accessible.
Yes, IMO that's not ideal. A program should be able to invoke a specific external function - rather than the first one which matches. :-o
They both resolve to the same thing because they do. A file system
gives you the choice of an absolute or relative path, when it is
possible.
So can a langage. The advantage of an absolute path, like fact.fact,
is that it will work from anywhere.
Have to say I would call that a relative path. Wouldn't an absolute path
be one which begins at somewhere fixed such as your \fact\fact, above, starting at the root directory (assuming that \ means the root dir).
...
Alternatively, what about fact.m including reserved function name _
as in
global function _(int n)int = {(n<=1 | 1 | n*fact(n-1))}
(What about the 'fact' in the body of the function? Actually what
would this function be called? They can't all be _!)
Good point. As I said in my earlier post I am not sure what would be
best to do about resolving internal references. If the language
specifies that the compiler is to search for a name in successively surrounding scopes (as in probably most HLLs) then the program could be
global function _(int n)int = {(n <= 1 | 1 | n * _(n - 1))}
(If the language specifies that all references to names begin at file
level then the code would be the same because _ is at the file level.)
However, if the language specifies that references made within a
function are rooted at the function level then there would have to be
some way to go 'up' a level to where the function is defined. Something
where the @@ is in
global function _(int n)int = {(n <= 1 | 1 | n * @@/_(n - 1))}
such that the caller's code would by naming the module refer to the _
function within it as in
module fact # (new module scheme uses 'module')
proc start=
println(fact(12))
end
I don't get this. Module 'fact' might contain 1000 functions, all with
different names.
Yes, there could be any number of other functions in the same file.
Ignoring overloading, only one would take the name of the file. For
example, if the main _ function came with other functions Convert and
Reduce (say) then the file would have the following functions.
function _
function Convert
function Reduce
Only one, the _ function, would represent the file itself. If the file were
calc.xx
then from outside the file the three functions would be invocable as
calc(....)
calc.Convert(....)
calc.Reduce(....)
And, yes, I am wary of the potential for that to cause problems!
What problem is this solving?
It's just an idea I am exploring in the context of hierarchical
namespaces. It would not always be relevant. For example, if one had a
file called
utils.xx
(where xx is the programming language extension) then one could imagine
that the functions within it would all have their own names such as
utils.advance(....)
with none having the name of the file. But as above if the programmer
created a file such as
discriminant.xx
then it could look a bit awkward to give the primary or only function
within that file the same name if it had to be invoked as
discriminant.discriminant(....)
That's not nice! :-(
On 23/11/2021 22:36, James Harris wrote:
There /is/ a special top-level name in my ST, that I give the internal
name $prog, but my name resolver is not set up to deal with that.
Then a fully absolute name would be:
$prog.fact.fact()
Unsurprisingly I tend to do the same thing with files in folders:
everything is usually on one level, I hate nested directories.)
So I think that what you call "_", I deal with as "start". Except your
"_" is only called on demand; my start() is used to initialise a module.
discriminant.discriminant(....)
That's not nice! :-(
Pythn programs do that all the time; no one cares.
On 24/11/2021 01:02, Bart wrote:
On 23/11/2021 22:36, James Harris wrote:
...
There /is/ a special top-level name in my ST, that I give the internal
name $prog, but my name resolver is not set up to deal with that.
Then a fully absolute name would be:
$prog.fact.fact()
Cool. I have a similar idea - mnemonics indicating predefined places in
the name hierarchy. For example, the folder in which the current source
file sits would be something like
&base
so a factorial program in the same folder could be invoked as
&base.fact()
...
Unsurprisingly I tend to do the same thing with files in folders:
everything is usually on one level, I hate nested directories.)
Would you still hate nested directories if they were simply part of the
name hierarchy? For example, say you had
folderA
prog.xx
folderB
folderC
sub.xx
Then prog.xx would be able to invoke the code in sub.xx even though it's
two folders deeper by
&base.folderB.folderC.sub()
or as with any name prog could define an alias for it and invoke that.
That's OK, isn't it?
Pythn programs do that all the time; no one cares.
You surprise me. You dislike the repetition of i in
for (i = 0; i < LEN; i++)
but you don't mind the repetition in
discriminant.discriminant
On 23/11/2021 01:58, luserdroog wrote:
On Saturday, November 20, 2021 at 9:48:06 PM UTC-6, luserdroog wrote:...
So the question is, stuff it (the "using" directive) into the preamble, which
already specifies
(<optional return variable><left arrow>)(optional left arg)(function)(optional right arg);(optional local variables)
Or make it a "statement" or special expression, a directive.
I don't know. Maybe I haven't given enough information for help, but that's
what I got.
Erhm. That was not the meat of the situation, just the surface syntax. sigh.What are "Weizenbaum environment chains"?? Most of Google's suggestions
The real issue is how to implement the directive. I have support for Weizenbaum environment chains, although so far there are only ever two environments: global or local to a (DEL) function.
don't seem to relate.
On 24/11/2021 08:52, James Harris wrote:
Would you still hate nested directories if they were simply part of
the name hierarchy? For example, say you had
folderA
prog.xx
folderB
folderC
sub.xx
Then prog.xx would be able to invoke the code in sub.xx even though
it's two folders deeper by
&base.folderB.folderC.sub()
or as with any name prog could define an alias for it and invoke that.
That's OK, isn't it?
I just wouldn't do it. I've seen folder hierarchies 9-11 levels deep (eg
in VS installations). They are not meant for use by humans.
In a language, I already have long dotted sequences for member selection
in records. For mere name resolution. I usually have 0 dots, or at most 1.
I like things flat!
Note that A.B.C.D for member selection is not necessarily a hierarchy.
If P is the head of a linear linked list, then P.next.next.data just
accesses a member of a node futher than the chain.
On Tuesday, November 23, 2021 at 11:57:05 AM UTC-6, James Harris wrote:
On 23/11/2021 01:58, luserdroog wrote:
The real issue is how to implement the directive. I have support for
Weizenbaum environment chains, although so far there are only ever two
environments: global or local to a (DEL) function.
What are "Weizenbaum environment chains"?? Most of Google's suggestions
don't seem to relate.
It's the solution to the FUNARG problem described in:
http://www.softwarepreservation.org/projects/LISP/MIT/Weizenbaum-FUNARG_Problem_Explained-1968.pdf
He calls it a "symbol table tree". But I quibble whether it's really a tree like we
usually define them. All the links go from leaves back up toward the root.
I learned about them from /Anatomy of Lisp/, and I think my implementation
in olmec copies what I understood from that book. IIRC Anatomy of Lisp describes them as chains instead of a tree.
On 25/11/2021 00:22, luserdroog wrote:
On Tuesday, November 23, 2021 at 11:57:05 AM UTC-6, James Harris wrote:...
On 23/11/2021 01:58, luserdroog wrote:
The real issue is how to implement the directive. I have support for
Weizenbaum environment chains, although so far there are only ever two >>> environments: global or local to a (DEL) function.
What are "Weizenbaum environment chains"?? Most of Google's suggestions
don't seem to relate.
It's the solution to the FUNARG problem described in:
http://www.softwarepreservation.org/projects/LISP/MIT/Weizenbaum-FUNARG_Problem_Explained-1968.pdf
He calls it a "symbol table tree". But I quibble whether it's really a tree like we
usually define them. All the links go from leaves back up toward the root. I learned about them from /Anatomy of Lisp/, and I think my implementation in olmec copies what I understood from that book. IIRC Anatomy of Lisp describes them as chains instead of a tree.
Thanks for the info. It seems to be related to partial function
application and/or currying.
https://en.wikipedia.org/wiki/Partial_application https://en.wikipedia.org/wiki/Currying
Have to say, all such 'clever' approaches seem to me to make a program unnecessarily hard to understand. I've never needed any of them.
On 16/11/2021 16:11, David Brown wrote:
On 16/11/2021 16:16, luserdroog wrote:
I've been following with interest many of the threads started
by James Harris over the year and frequently the topic of
namespaces and modularity come up. Most recently in the
"Power operator and replacement..." thread.
And I tend to find myself on James' side through unfamiliarity
with the other option. What's the big deal about namespaces
and modules? What do they cost and what do they facilitate?
...
namespace timers {
void init();
void enable();
int get_current();
void set_frequency(double);
}
You can use them as "timers::init();" or by pulling them into the
current scope with "using timers; enable();".
As this is about namespaces, I'd suggest some problems with that "using timers" example:
* AIUI it mixes the names from within 'timers' into the current
namespace. If that's right then a programmer taking such an approach
would have to avoid having his own versions of those names. (I.e. no
'enable' or 'init' names in the local scope.)
I did not check C++ rules here. But other languages have simple rule:
local name wins. That is to access global one you either should
not declare local one or use qualified version.
If you think about this for a while you should see that this is
the only sensible rule: imported modules can change without
programmer of "client" module knowing this. And breakning clients
by merely adding new export is in most cases not acceptable.
OTOH client should know which imported functions will be used.
So avoiding local use of imported names usually is not a big
burden.
On 2022-01-29 03:35, antispam@math.uni.wroc.pl wrote:
I did not check C++ rules here. But other languages have simple rule: local name wins. That is to access global one you either should
not declare local one or use qualified version.
The rules most languages deploy are rather quite complicated. There are
three choices:
1. Silent hiding of external entities with conflicting names
2. Overloading the entities with conflicting names
3. Signaling error
The rule #2 is interesting as it can create mutually hiding names when overloading cannot be resolved.
Languages tend to have a mixture of all three from case to case.
If you think about this for a while you should see that this is
the only sensible rule: imported modules can change without
programmer of "client" module knowing this. And breakning clients
by merely adding new export is in most cases not acceptable.
Well, it is not that simple.
The first point is that from the SW development POV, if the imported
module's interface changes, the client code must be reviewed anyway.
The second point is that the rule #1 does not actually protect clients.
As an example consider two modules used by the client. If one of the
modules introduces a name conflict with another module, that breaks the client anyway, because this case cannot be handled by the rule #1 anymore.
OTOH client should know which imported functions will be used.
So avoiding local use of imported names usually is not a big
burden.
In practice it is. Ada even introduced partial visibility of imported
names. For example you can say:
use Linear_Algebra;
This will make all names from Linear_Algebra publicly visible and
potentially conflicting with other stuff. But you could write:
declare
X, Y, Z : Matrix; -- Linear_Algebra.Matrix
begin
...
Z := X + Y; -- Operation "+" of Linear_Algebra.Matrix
Now if you wanted to use only the Matrix type in your code without
importing anything else, you could do:
use type Linear_Algebra.Matrix;
This would make operations of Matrix directly visible, but nothing else.
So the code would look like:
declare
X, Y, Z : Linear_Algebra.Matrix; -- Qualified name
begin
...
Z := X + Y; -- Operation "+" of Linear_Algebra.Matrix
Without either the code would be:
declare
X, Y, Z : Linear_Algebra.Matrix; -- Qualified name
begin
...
Z := Linear_Algebra."+" (X, Y);
Dmitry A. Kazakov <mailbox@dmitry-kazakov.de> wrote:
On 2022-01-29 03:35, antispam@math.uni.wroc.pl wrote:
I did not check C++ rules here. But other languages have simple rule:
local name wins. That is to access global one you either should
not declare local one or use qualified version.
The rules most languages deploy are rather quite complicated. There are
three choices:
1. Silent hiding of external entities with conflicting names
2. Overloading the entities with conflicting names
3. Signaling error
The rule #2 is interesting as it can create mutually hiding names when
overloading cannot be resolved.
Overloading creates its own complexities. First, with overloading
what matters is really not name but full signature: name + types
of arguments and result. If language requires exact match for
signature than the rule above works with name replaced by
signature. If there are automatic type convertions or return
type is needed to resolve overloading then things indeed one
needs extra rules.
One language that I use has following extra rules:
- when interface inherists from two different interfaces each
having "the same" signature this resuls in single inherited
signature
- when overloading allows more than one function choice is
"arbitrary"
Note: in case of exact match local signature wins.
Overloading
only plays role when there are multiple alternatives leading to
different types.
Both rules work under assumption that design has right names,
so functons with the same signatur do equivalent work.
Expirience with 200 thousend lines codebase shows that
this works well. However, it is not clear how this would work
with bigger codebase (say 20 million lines) or in less regular
problem domain.
Arguably, signaling error when overloading can not be resolved
in unique way would be safer.
The first point is that from the SW development POV, if the imported
module's interface changes, the client code must be reviewed anyway.
Well, if your coding rules say the review is in place, do what rules
say. However, adding new signature without changing behaviour of
existing signatures may be "safe" change. Namely, it is safe
for languages without overloading.
The second point is that the rule #1 does not actually protect clients.
As an example consider two modules used by the client. If one of the
modules introduces a name conflict with another module, that breaks the
client anyway, because this case cannot be handled by the rule #1 anymore.
Sure. But there is important difference: global object must be
coordinated to avoid conflicts.
With say "error when names are
equal" rule there will be conflicts with local routines which
would significanlty increase number of conflicts.
Well, there are many ways. Extended Pascal have selective import
(only names that you specify are imported) and interfaces, that
is module may have several export lists and client decides which
one to use. And there is possiblity of renaming at import time.
All of those means that client has several ways of resolving
conflict.
Extended Pascal significanly decreases chance of
artificial conflict, that is accidentally importing name that
client do not want to use.
OTOH explicit import/export lists
are less attractive when there is overloading (to get equivalent
effect they require repeating type information).
Concerning Ada way, it is not clear for me how this is supposed
to work when there are multiple intersecting subsets of functions.
On 2022-02-03 20:24, antispam@math.uni.wroc.pl wrote:
Dmitry A. Kazakov <mailbox@dmitry-kazakov.de> wrote:
On 2022-01-29 03:35, antispam@math.uni.wroc.pl wrote:
I did not check C++ rules here. But other languages have simple rule: >>> local name wins. That is to access global one you either should
not declare local one or use qualified version.
The rules most languages deploy are rather quite complicated. There are
three choices:
1. Silent hiding of external entities with conflicting names
2. Overloading the entities with conflicting names
3. Signaling error
The rule #2 is interesting as it can create mutually hiding names when
overloading cannot be resolved.
Overloading creates its own complexities. First, with overloading
what matters is really not name but full signature: name + types
of arguments and result. If language requires exact match for
signature than the rule above works with name replaced by
signature. If there are automatic type convertions or return
type is needed to resolve overloading then things indeed one
needs extra rules.
One language that I use has following extra rules:
- when interface inherists from two different interfaces each
having "the same" signature this resuls in single inherited
signature
Inheritance is dynamic polymorphism. Overloading is ad-hoc polymorphism.
As such they are unrelated.
- when overloading allows more than one function choice is
"arbitrary"
Arbitrary? That sound like a very poorly designed language.
Note: in case of exact match local signature wins.
= #1
Overloading
only plays role when there are multiple alternatives leading to
different types.
= #2
Both rules work under assumption that design has right names,
so functons with the same signatur do equivalent work.
Well, this is the Liskov's substitutability principle (LSP). Whether to
apply it to ad-hoc polymorphism is a question of program semantics.
In any case, the language does not interfere with the semantics, that
would be incomputable.
LSP is not not enforceable, it is only a design
principle.
Expirience with 200 thousend lines codebase shows that
this works well. However, it is not clear how this would work
with bigger codebase (say 20 million lines) or in less regular
problem domain.
Large projects are split into weakly coupled modules.
Arguably, signaling error when overloading can not be resolved
in unique way would be safer.
That is the only way. The question is different. Whether visibility
rules should allow programs with *potentially* unresolvable overloading.
E.g. both M1 and M2 declare conflicting Foo. M3 uses both M1 and M2 but
does not reference Foo. Is M3 legal? Or just the fact of using
conflicting M1 and M2 should make M3 illegal.
The first point is that from the SW development POV, if the imported
module's interface changes, the client code must be reviewed anyway.
Well, if your coding rules say the review is in place, do what rules
say. However, adding new signature without changing behaviour of
existing signatures may be "safe" change. Namely, it is safe
for languages without overloading.
It is never safe. The change can always introduce a conflict. The only
choice is between flagging it as an error always or only when a client actually uses it.
The second point is that the rule #1 does not actually protect clients.
As an example consider two modules used by the client. If one of the
modules introduces a name conflict with another module, that breaks the
client anyway, because this case cannot be handled by the rule #1 anymore.
Sure. But there is important difference: global object must be
coordinated to avoid conflicts.
There should be no global objects, for the start.
Extended Pascal significanly decreases chance of
artificial conflict, that is accidentally importing name that
client do not want to use.
You cannot accidentally import anything. The module interface must be designed rationally to be useful for the clients. There is no defense
against poorly designed interfaces.
However interfaces can be designed in favor of clients deploying fully qualified names vs clients deploying direct visibility. Unfortunately
one should choose one.
The former tends to choose same names for everything. So that the client
does something like:
IO.Integers.Put;
The latter would rather do
IO.Integer_IO.Put_Integer
which with direct visibility becomes
Put_Integer
OTOH explicit import/export lists
are less attractive when there is overloading (to get equivalent
effect they require repeating type information).
Why? Explicit import in Ada is very popular. I gave example of
use type T;
clause which is that thing.
Usually such types are numeric which leads
to massive overloading of +,-,*,/. Nobody cares as these are all resolvable.
Concerning Ada way, it is not clear for me how this is supposed
to work when there are multiple intersecting subsets of functions.
I am not sure what you mean.
Overloading works pretty well Ada because
of types. Languages shy of overloading are ones with weak type system
when everything gets lumped into something unresolvable. E.g. if there
is no distinct numeric types or when the result is not a part of the signature etc.
Dmitry A. Kazakov <mailbox@dmitry-kazakov.de> wrote:
On 2022-02-03 20:24, antispam@math.uni.wroc.pl wrote:
Dmitry A. Kazakov <mailbox@dmitry-kazakov.de> wrote:
On 2022-01-29 03:35, antispam@math.uni.wroc.pl wrote:
I did not check C++ rules here. But other languages have simple rule: >>>>> local name wins. That is to access global one you either should
not declare local one or use qualified version.
The rules most languages deploy are rather quite complicated. There are >>>> three choices:
1. Silent hiding of external entities with conflicting names
2. Overloading the entities with conflicting names
3. Signaling error
The rule #2 is interesting as it can create mutually hiding names when >>>> overloading cannot be resolved.
Overloading creates its own complexities. First, with overloading
what matters is really not name but full signature: name + types
of arguments and result. If language requires exact match for
signature than the rule above works with name replaced by
signature. If there are automatic type convertions or return
type is needed to resolve overloading then things indeed one
needs extra rules.
One language that I use has following extra rules:
- when interface inherists from two different interfaces each
having "the same" signature this resuls in single inherited
signature
Inheritance is dynamic polymorphism. Overloading is ad-hoc polymorphism.
As such they are unrelated.
You are making unwarranted assumptions here. I am writing about
language which is probably unknown to you. In this language
interface inheritance is mostly static (some folks would say
purely static).
Interfaces are related to overloading: visible interfaces decide
which signatures are visible.
Well, theorem proving is uncomputable, but proof checking is computable. Arguably, program should be deemed correct only when programmer
can justify its correctness. So, at least in theory you could have
language that requires programmer to include justification (that is
proof) of correctnes...
Expirience with 200 thousend lines codebase shows that
this works well. However, it is not clear how this would work
with bigger codebase (say 20 million lines) or in less regular
problem domain.
Large projects are split into weakly coupled modules.
This 200 thousend lines is split into about 1000 modules. But
there is cluster of about 600 mutually connected modules. From
one point of view many of those modules are "independent", but
there are subtle connections and if you transitively track them there
is largish cluster...
Arguably, signaling error when overloading can not be resolved
in unique way would be safer.
That is the only way. The question is different. Whether visibility
rules should allow programs with *potentially* unresolvable overloading.
E.g. both M1 and M2 declare conflicting Foo. M3 uses both M1 and M2 but
does not reference Foo. Is M3 legal? Or just the fact of using
conflicting M1 and M2 should make M3 illegal.
Word "potentially" have many different meanings. In my case
types are hairy enough that there are 3 cases:
- compiler can decide that there is only one applicable signature
- compiler can decide that there is conflict
- compiler can not decide and only at runtime it possible to
decide if there is a conflict.
The core of problem may be illustrated using Extended Pascal.
Namely, one can define schema type:
type T(i : integer) = 0..100;
above type discriminant 'i' is otherwise unused but ensures
that later w get incompatible types.
We can have procedure taking argument of schema type:
function foo(a : T) : integer;
....
But we can also use discriminanted version of type:
function foo(a : T(42)) : integer;
....
Of course, in Ada spirit it would be to disallow both versions.
OTOH even in Ada some checks are delayed to runtime...
Well, assuming that there are no conflict between two different
imported signatures, but only imported signature gets shadowed by
local one you will get the same code. Of course, after adding
new export compilation may discover that added signature is
in conflict with other imported signature, but this is different
case.
The second point is that the rule #1 does not actually protect clients. >>>> As an example consider two modules used by the client. If one of theSure. But there is important difference: global object must be
modules introduces a name conflict with another module, that breaks the >>>> client anyway, because this case cannot be handled by the rule #1 anymore. >>>
coordinated to avoid conflicts.
There should be no global objects, for the start.
Sorry, you are attaching different meaning to words that I am.
To have meaningful disscussion we need common (global to the
disscussion) understanding of "global" and "object". To put
it differently common words should be global objects. Similarly
to be able to program you need global objects.
However interfaces can be designed in favor of clients deploying fully
qualified names vs clients deploying direct visibility. Unfortunately
one should choose one.
The former tends to choose same names for everything. So that the client
does something like:
IO.Integers.Put;
The latter would rather do
IO.Integer_IO.Put_Integer
which with direct visibility becomes
Put_Integer
Well, with overlaoding it is normal to use direct visibility and
use the same name for all types: overloading take care of types
and you rarely need qualified names.
Of course, you need to
properly choose names so that they correspond well with actual
meaning, but this is easier if you do not have to invent new
names just to avoid conflicts...
OTOH explicit import/export lists
are less attractive when there is overloading (to get equivalent
effect they require repeating type information).
Why? Explicit import in Ada is very popular. I gave example of
use type T;
clause which is that thing.
No, this is not explicit import/export list. I meant something
like:
import signature foo : U -> V from T
meaning that you want from T only function foo having argument of
type U and return value of type V.
This is different than what you showed: you use named interface.
Concerning Ada way, it is not clear for me how this is supposed
to work when there are multiple intersecting subsets of functions.
I am not sure what you mean.
You showed how to import whole interface (all exported signatures).
But how you handle situation when you have module exporting several signatures and in different uses you need different subsets.
Say, for files one client wants 'open', 'read', 'close'. Another
Works with existing files and wants 'read' and 'write'. Package
for file operations is likely to contains several other operations.
I did not specify types above, but at least for some operations
it is natural to provide overloaded variants. In principle
client may want arbitrary subset of exported operations.
Clearly
creating separate interface per subset is not feasible.
In principle one could have one interface per operation, but
this looks clumsy.
James Harris <james.harris.1@gmail.com> wrote:
On 16/11/2021 16:11, David Brown wrote:
On 16/11/2021 16:16, luserdroog wrote:
I've been following with interest many of the threads started
by James Harris over the year and frequently the topic of
namespaces and modularity come up. Most recently in the
"Power operator and replacement..." thread.
And I tend to find myself on James' side through unfamiliarity
with the other option. What's the big deal about namespaces
and modules? What do they cost and what do they facilitate?
...
namespace timers {
void init();
void enable();
int get_current();
void set_frequency(double);
}
You can use them as "timers::init();" or by pulling them into the
current scope with "using timers; enable();".
As this is about namespaces, I'd suggest some problems with that "using
timers" example:
* AIUI it mixes the names from within 'timers' into the current
namespace. If that's right then a programmer taking such an approach
would have to avoid having his own versions of those names. (I.e. no
'enable' or 'init' names in the local scope.)
I did not check C++ rules here. But other languages have simple rule:
local name wins. That is to access global one you either should
not declare local one or use qualified version.
If you think about this for a while you should see that this is
the only sensible rule: imported modules can change without
programmer of "client" module knowing this. And breakning clients
by merely adding new export is in most cases not acceptable.
Sysop: | Keyop |
---|---|
Location: | Huddersfield, West Yorkshire, UK |
Users: | 286 |
Nodes: | 16 (3 / 13) |
Uptime: | 86:22:22 |
Calls: | 6,496 |
Calls today: | 7 |
Files: | 12,099 |
Messages: | 5,277,030 |