Fred wrote:In C++ or any other OOP language you don't use the global scope anymore, you just do it all with class based coding, it's the same for modules.
In C++, scope operators which are defined by open and close braces ('{' and '}') always inherit their parent scope:
Code: Select all
struct SomeStruct {
};
class SomeClass {
// Inherits SomeStruct from global scope
SomeStruct _struct;
};
namespace space {
// Inherits SomeClass from global scope
SomeClass _object;
}
You mention that "other OOP languages do not have this issue", which is true. But this is not a fair comparison because OOP languages have the benefit of encapsulation behind class objects and the ease of access through methods. To share anything with modules you must either use the module name everywhere or risk name clashes which result from UseModule. While class methods are accessible through their objects, we must bring all functions and other relevant definitions into the current scope before we can do anything. But because we have no automatic conflict resolution, we must use overly verbose module names or risk a compiler error.
Name clashing with Modules:
Code: Select all
DeclareModule Module_With_Item
; Public
Structure Item
EndStructure
EndDeclareModule
Module Module_With_Item
EndModule
DeclareModule Module_Using_Item
; Using everything in this module that is public
UseModule Module_With_Item
; [COMPILER ERROR] Cannot redefine when UseModule conflicts
Structure Item
EndStructure
EndDeclareModule
Module Module_Using_Item
EndModule
As a comparison, name clashing does not happen in OOP due to classes encapsulating all scope (pseudocode):
Code: Select all
Class ClassA
Structure Item
EndStructure
EndClass
Class ClassB
; Structure does not conflict because ClassA.Item is still scoped even when
; the class is public
obj.ClassA
Structure Item
EndStructure
a.ClassA::Item ; <- ClassA scoped Item is still accessible
b.Item ; <- ClassB scoped Item never conflicts
EndClass
PureBasic's implementation of modules is a strange amalgamation which uses strict class-like scoping without inheritance designed for OOP in a procedural language without support for submodules (modules cannot be defined inside other modules) and name conflict resolution. All of this makes modules very difficult to use and has me questioning whether they actually provide any real benefit over a prefix.
My opinion of the current implementation is that it was a decision to make modules easier to implement. For example, if modules were as they are right now but DID inherit from the global scope then you would have this immediate problem:
Code: Select all
Structure Item
EndStructure
DeclareModule SomeModule
; [COMPILER ERROR] Cannot redefine inherited Item structure
Structure Item
EndStructure
EndDeclareModule
PureBasic does NOT allow the redefinitions like this for obvious reasons, which makes sense in a single global scope. But within modules this should be ALLOWED. But because it isn't, heavy use of modules only shifts the problem out of global scope and into module scope.
Code: Select all
DeclareModule ModuleA
Structure Item
EndStructure
EndDeclareModule
DeclareModule ModuleB
UseModule ModuleA
; [COMPILER ERROR] Cannot redefine inherited Item structure
Structure Item
EndStructure
EndDeclareModule
I was initially excited about modules after reading the documentation:
Modules are an easy way to isolate code part from the main code, allowing code reuse and sharing without risk of name conflict. In some other programming languages, modules are known as 'namespaces'.
Until I discovered that, while they do isolate code, they still carry the same risk of name conflict and are nothing at all like namespaces.
The suggestion to use a "common" or "global" module is especially frightening and this is the polar opposite encapsulation by encouraging what is akin to global variables being accessed in functions to share data. Although truly a throwback to Basic, this style of coding is
brain damaging. Here is an excerpt from the documentation (emphasis mine):
To share information between modules, a common module can be created and then used in every modules which needs it. It's the common way have GLOBAL data available for ALL modules.
Code: Select all
; The common module, which will be used by others to share data
;
DeclareModule Cars
Global NbCars = 0
EndDeclareModule
Module Cars
EndModule
; First car module
;
DeclareModule Ferrari
EndDeclareModule
Module Ferrari
UseModule Cars
NbCars+1
EndModule
; Second car module
;
DeclareModule Porche
EndDeclareModule
Module Porche
UseModule Cars
NbCars+1
EndModule
Debug Cars::NbCars
Imo, modules were a good idea but tried to sidestep the elephant in the room: name clashes. The obvious casualty of this was the loss of global scope. To fix modules we need two things. Firstly,
modules should inherit the global scope. Secondly,
name clashes should be ALLOWED with definitions inside modules taking precedence.
Here is an example of how it could be implemented (this is how C++ does it with namespaces):
Code: Select all
; Global scope Item
Structure Item
EndStructure
DeclareModule ModuleA
; ModuleA Item
Structure Item
EndStructure
EndDeclareModule
DeclareModule ModuleB
UseModule ModuleA
; ModuleB Item
Structure Item
EndStructure
Define a.Item ; <- ModuleB Item
Define a.ModuleA::Item ; <- ModuleA Item (explicit because of conflict)
Define a.::Item ; <- Global scope Item (explicit because of conflict)
EndDeclareModule
These two subtle changes will allow for cleaner, clearer, and more portable code and breathe life back into our modules.
I know this has been a long post but there were a lot of points to cover on this topic. I put a long of time, testing and research into this and hope that I've been able to present all of my arguments clearly enough for everyone to understand. Please let me know if you need anything further explained.