Allow '::' to access the global scope from within a module

Got an idea for enhancing PureBasic? New command(s) you'd like to see?
Mistrel
Addict
Addict
Posts: 3415
Joined: Sat Jun 30, 2007 8:04 pm

Allow '::' to access the global scope from within a module

Post by Mistrel »

Modules do not inherit their parent's scope. I think that this is a GOOD thing as it ensures that modules will never have the risk of name clashes. But we still need a way to access the portions of global scope we need to prevent code duplication which can result in errors.

We can't do this:

Code: Select all

Structure SomeStruct
EndStructure

DeclareModule SomeModule
  ; [COMPILER] Structure Not found: SomeStruct.
  Define a.SomeStruct
EndDeclareModule
We have to do this:

Code: Select all

Structure SomeStruct
  a.i
  b.i
  c.i
EndStructure

DeclareModule SomeModule
  Structure SomeStruct
    a.i
    b.i
    c.i
  EndStructure
  
  ; [COMPILER] Structure Not found: SomeStruct.
  Define a.SomeStruct
EndDeclareModule
We CAN do this by explicitly wrapping things in another module and then accessing it by its module name:

Code: Select all

DeclareModule Stuff_I_Need
  Structure SomeStruct
    a.i
    b.i
    c.i
  EndStructure
EndDeclareModule

Module Stuff_I_Need
EndModule

DeclareModule SomeModule
  Define a.Stuff_I_Need::SomeStruct
EndDeclareModule

Module SomeModule
EndModule
But imo, this is needlessly complex, confusing to read, and verbose.

Alternatively we could just use '::' with no prefix to denote that we want to access the global scope:

Code: Select all

Structure SomeStruct
  a.i
  b.i
  c.i
EndStructure

DeclareModule SomeModule
  Define a.::SomeStruct
EndDeclareModule
and

Code: Select all

Structure SomeStruct
  a.i
  b.i
EndStructure

DeclareModule OtherModule
  Structure OtherStruct Extends ::SomeStruct
    c.f
    d.f
  EndStructure
  
  Define b.OtherStruct
EndDeclareModule

Module OtherModule
EndModule
Last edited by Mistrel on Wed Jan 23, 2019 7:40 am, edited 1 time in total.
User avatar
helpy
Enthusiast
Enthusiast
Posts: 552
Joined: Sat Jun 28, 2003 12:01 am

Re: Allow '::' to access the global scope

Post by helpy »

Another idea: completly avoid the global scope!

Code: Select all

XIncludeFile "moduleA.pbi"
XIncludeFile "moduleB.pbi"

DeclareModule Application
   EnableExplicit
EndDeclareModule

Module Application
   Procedure Main()
      Debug "Start Application::Main()"
      ; ...
      A::Init()
      B::Init()
      ; ...
      Debug "Finish Application::Main()"
      ProcedureReturn #Null
    EndProcedure
    Main()
 EndModule
If you realy need application wide elements (structures, variables, ...) you can declare them inside the application module and use it with "Application::..." or with "Use Application" inside other modules.
Windows 10 / Windows 7
PB Last Final / Last Beta Testing
Mistrel
Addict
Addict
Posts: 3415
Joined: Sat Jun 30, 2007 8:04 pm

Re: Allow '::' to access the global scope

Post by Mistrel »

helpy wrote:If you realy need application wide elements (structures, variables, ...) you can declare them inside the application module and use it with "Application::..." or with "Use Application" inside other modules.
This isn't portable as it tightly couples modules to this convention.
User avatar
helpy
Enthusiast
Enthusiast
Posts: 552
Joined: Sat Jun 28, 2003 12:01 am

Re: Allow '::' to access the global scope

Post by helpy »

Mistrel wrote:This isn't portable as it tightly couples modules to this convention.
OK! I understand your argument.

But, I could say the same about your convention.
Windows 10 / Windows 7
PB Last Final / Last Beta Testing
Cyllceaux
Enthusiast
Enthusiast
Posts: 464
Joined: Mon Jun 23, 2014 1:18 pm
Contact:

Re: Allow '::' to access the global scope

Post by Cyllceaux »

I solved it the ugly way.

Code: Select all

DeclareModule Test
    includefile "structureModuleDeclare.pbi"
EndDeclareModule

Module Test
    EnableExplicit
    includefile "structureModule.pbi"
EndModule
Fred
Administrator
Administrator
Posts: 16664
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: Allow '::' to access the global scope

Post by Fred »

The proper way to this is to use a 'Common' or 'Global' Module and use UseModule in module which needs it:

Code: Select all

DeclareModule Stuff_I_Need
  Structure SomeStruct
    a.i
    b.i
    c.i
  EndStructure
EndDeclareModule

Module Stuff_I_Need
EndModule

DeclareModule SomeModule
  UseModule Stuff_I_Need
  Define a.SomeStruct
EndDeclareModule

Module SomeModule
EndModule
fryquez
Enthusiast
Enthusiast
Posts: 367
Joined: Mon Dec 21, 2015 8:12 pm

Re: Allow '::' to access the global scope

Post by fryquez »

How much bounty would such a feature take? :)
Mistrel
Addict
Addict
Posts: 3415
Joined: Sat Jun 30, 2007 8:04 pm

Re: Allow '::' to access the global scope

Post by Mistrel »

Fred wrote:The proper way to this is to use a 'Common' or 'Global' Module and use UseModule in module which needs it
Again, this isn't portable as it tightly couples modules to this convention. It can conflict with other people's code which uses the same convention and cause problems when trying to merge code with different conventions.

I know that you were trying to be helpful citing UseModule but the whole point of my example was to demonstrate how I don't want to put what would otherwise be available in global scope into a module.

Because modules do not inherit the global scope, this would mean that literally everything everywhere would have to be contained in some module somewhere for this to work.
Fred
Administrator
Administrator
Posts: 16664
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: Allow '::' to access the global scope

Post by Fred »

This the whole point of module to be self contained, allowing global scope access would break this. if you plan to write your program using module, you should have everything in a module, as helpy said, it's not an issue to reuse any module you want in another. 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. FYI, the IDE uses a lot of module, and it works great (we have a 'Common' module for all global items and modules which requiers it, just import it).
Mistrel
Addict
Addict
Posts: 3415
Joined: Sat Jun 30, 2007 8:04 pm

Re: Allow '::' to access the global scope

Post by Mistrel »

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.
mestnyi
Addict
Addict
Posts: 999
Joined: Mon Nov 25, 2013 6:41 am

Re: Allow '::' to access the global scope from within a modu

Post by mestnyi »

Mistrel You are actually saying the right thing, I support you with both hands.

Code: Select all

DeclareModule ModuleB
  UseModule ModuleA
 
  ; ModuleB Item
  Structure Item
  EndStructure
the only thing I do not think is possible to implement.
I don’t understand why Fred doesn’t want to implement this.
Mistrel
Addict
Addict
Posts: 3415
Joined: Sat Jun 30, 2007 8:04 pm

Re: Allow '::' to access the global scope from within a modu

Post by Mistrel »

Modifying the compiler to add new features is hard and Fred et al might not have the financial incentive to expand the language as we may want.

It's much easier to instead add additional libraries and this is probably sufficient for the majority of their users.

I have a lot of other interesting feature requests if you haven't seem them. These are my top three:

Allow structure pointers as return types
Allow interfaces as return types

This last one is subjective but it is important to me:

Allow splitting function lines before the paremeter list
Mistrel
Addict
Addict
Posts: 3415
Joined: Sat Jun 30, 2007 8:04 pm

Re: Allow '::' to access the global scope from within a modu

Post by Mistrel »

I just discovered a new problem caused by the way modules are implemented. Despite modules being considered as self-contained and non-inheriting of the global scope, they do in fact inherit from the global scope so long as long as they have been defined in a resident file:

You can't do this because PointXY is out of scope:

Code: Select all

Structure PointXY
EndStructure

DeclareModule Shapes
  a.PointXY
EndDeclareModule
Structure not found: PointXY.
You also can't provide your own structure POINT as that is inherited from a resident file (Win32 API):

Code: Select all

DeclareModule Shapes
  Structure Point
  EndStructure
EndDeclareModule
Structure already declared: Point (in a resident file).
Unless it's something you've defined yourself. Then it works:

Code: Select all

Structure PointXY
  a.l
  b.l
EndStructure

DeclareModule Shapes
  Structure PointXY
    x.l
    y.l
  EndStructure
EndDeclareModule

Module Shapes
EndModule

Define a.PointXY

a\a=1
a\b=2

Define b.Shapes::PointXY

b\x=1
b\y=2
This also works fine due to resident definitions being in scope:

Code: Select all

DeclareModule Shapes
  a.POINT
  b=#PI
EndDeclareModule

Module Shapes
EndModule
So, modules don't inherit from the global scope. Except when they do. And can redefine definitions from the global scope. Except when they can't.
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.
This is another example of how it's not the same. Other languages, such as C++, inherit their parent scope while also allowing for redefinition.

Fred, please consider this feature request to bring sanity and life to PureBasic modules. They are a missed opportunity.
Rinzwind
Enthusiast
Enthusiast
Posts: 638
Joined: Wed Mar 11, 2009 4:06 pm
Location: NL

Re: Allow '::' to access the global scope from within a modu

Post by Rinzwind »

Agree totally with Mistrel. All I want to add. Modules as of current state seem like an afterthought.
Post Reply