How to disallow default 'New' Constructor?

Community Forums/Monkey2 Talk/How to disallow default 'New' Constructor?

Danilo(Posted 2016) [#1]
What's the best way to disallow the default 'New' constructor?
Class Singleton
    Public

        Function getInstance:Singleton()
            If Not _instance Then _instance = New Singleton
            Return _instance
        End Function
    
    Private

        Method New()
            '#Error "err" ?
            'Throw "err"  ?
        End Method
        
        Global _instance:Singleton
End Class

Class ExpliciteConstructor
    Public

        Method New(name:String)
            _name = name
        End Method

    Private

        Method New()
        End Method
        
        Field _name:String
End Class


Function Main()

    Local singleton1 := New Singleton           ' I want to disallow this
    Local singleton2 := Singleton.getInstance() ' OK

    Local obj1 := New ExpliciteConstructor      ' I want to disallow this
    Local obj2 := New ExpliciteConstructor("X") ' OK

    ' Semanting...
    ' ***** Uncaught Monkey 2 Exception: Memory access violation *****
    ' ***** Reason: use of '=' instead ':='                      *****
    'Local obj3 = New ExpliciteConstructor

End Function

Private stuff is not reachable from the outside, but that rule is not respected for constructors.


marksibly(Posted 2016) [#2]
Will fix!

Note that in this case though, you can actually invoke New because private/protected does not apply to stuff declared in the same file.


Danilo(Posted 2016) [#3]
Any special reason to make Public/Private/Protected File-based instead of Class/Struct-based? Does not make much sense, in my opinion.
When I specify Public/Private/Protected inside a Class/Struct, it schould be limited/qualified to that Class/Struct.

The Class or Struct itself is the independent entity, not a file. File-based access does not make ANY sense at all...

In the case of file-based access you are forced to put every class into a separated file,
otherwise access-specifiers/qualifiers don't work at all. Really weird.


marksibly(Posted 2016) [#4]
It's a low-budget version of 'friend' from c++. This allows, for example, a 'tree' data struct to access the contents of a 'node' without having to make the contents of node public, eg:

http://www.cprogramming.com/tutorial/friends.html

More flexible visibility control might be added in future, but I'm OK with this simple solution for now (which is pinched from monkey1).

Public/private/protected are only ignored in the same file they are declared in, so the only person that gains 'free' access to everything is the file's author(s). All code outside of the file must follow the normal rules for access.


Danilo(Posted 2016) [#5]
While the general idea is great, this system has the big dis-advantage that it does only work
if you exactly follow the steps required to make it work.

Like in my source-code, presented above, you get the feeling MX2 just does not work correctly.
You have to follow certain steps *exactly* (like putting every Class into it's own file) to make the system work -
otherwise it just fails! Somehow I think it's just wrong, because it limits the flow of creativity.
You have to follow too many conventions to be correct. If you don't follow all those conventions, it still
compiles and works, but it looks like MX2 is not working correctly! It's a big trap/pitfall!

What about sustainability? A Class works correctly only if it remains in a separate file,
otherwise the access-behaviour is completely different!? A Class should be an independent thing,
so this does not make any sense at all.

Imagine you write a book about using the most common 23 design-patterns with MX2, you have to say that
it requires to follow exactly this and that steps, otherwise it just will not work - althought it still compiles fine.
It just does not work as expected, because MX2 uses very counter-intuitive ways to make things possible.

The simplest 10- or 20-liners have to be split into two or three separate files to work correctly with MX2 -
otherwise they still would compile and run, but they would be incorrect - because access-qualifiers are just ignored.

Class/Struct/Method access-qualifiers ignored within the main/same file is really weird...

Actually, to get/check access-rights correctly, you are forced to put every single class and struct
into a separate file - or it just will not work! Althought it still compiles correctly... just ignoring any access-rights.

It's cumbersome and counter-intuitive, in my opinion. Classes and Structs should work the same way,
independent of the file they are in. Currently the meaning of access-qualifiers *inside* Classes depends on the filename they are in!

People are giving you a Star for that weird, un-logical, system - just because they don't understand anything!
Providing a better system is more important, in my opinion...


Gerry Quinn(Posted 2016) [#6]
I think the idea is that every class *should* under normal conditions be in its own file, and you are making a deliberate decision when you choose not to do that.


Danilo(Posted 2016) [#7]
@Gerry Quinn:
That's fine for final (extern) frameworks, but for an end-user app that's just ridiculous, in my experience.
End-users want to get things done, and they don't care about app design actually. They just want it to work,
and with MX2, 'Classes' work differently - depending on the file they are in.
Some programmers even have problems to grasp the 'namespace' thingy - they want to stay with BlitzMax forever
because of such 'simple' things.


Playniax(Posted 2016) [#8]
they want to stay with BlitzMax forever

Some of them seem quite reluctant to accept anything else, sad but true...

While I will not interfere with this discussion :)


tiresius(Posted 2016) [#9]
Speaking of namespaces, could that be a solution to this problem? Maybe allow multiple namespaces in the same file ? Might get weird ...


marksibly(Posted 2016) [#10]
I agree the current system is not exactly ideal, but I do think *something* like this is needed - there's needs to be some sort of 'horizontal' access control so 'peer' classes can have access to their own interface. I think recognizing and understanding this is what got me the 'star', not so much the particular implementation.

But the current system has never really bugged me in the past - or, as far as I can tell, any mx1 users. I have never really thought of protected/private as being about 'protecting the programmer from themselves', but about protecting code from accidental/intentional misuse by 3rd parties. So if you're writing a 'one file wonder', it's your file and you should be able to access whatever you want anyway (although I guess in a big enough project, it's fair enough to consider a single author to also be a '3rd party').

An alternative approach would be something like C#'s 'Internal' which grants 'module level' access to members. This shouldn't be too hard to do, but it is in a way more relaxed than the current system as it grants entire module level access. In 90% of cases, it's kind of overkill IMO. C++'s approach, where you explicitly grant 'friendship' to classes/functions/methods inside a class, is the most flexible/precise but it's kind of long winded and IMO not monkey2-ish.

Anyway, I'm open to alternative ideas here. But something is needed and the current system, as much as you may dislike it, works.


Danilo(Posted 2016) [#11]
Thanks for your explanation! Requires a detailed section in the docs, IMO - to explain access-rights are ignored within the same file.


marksibly(Posted 2016) [#12]
I just had a quick play with 'internal' (where internal means 'module internal') and I sort of like it, but I think I still prefer the current system (for the way I code anyway).

As expected, all the private stuff in container nodes, iterators etc broke, and the next step would have been to make them 'internal'. But that just didn't feel right - I only want List, Map, Stack etc. to be able access to Node._next, Node._prev etc, not just anything in std.

The problem here is that a module is kind of an arbitrary thing. It's a physical package containing stuff you can #Import, and doesn't represent any logical namespace as far as code is concerned. So making something module-internal makes it available in kind of a fuzzy way. Also, I really like the flexibility of being able to split/merge modules pretty freely knowing you're only affecting what people have to #import, but each time you do this you'd be changing the scope of internal stuff too, which may not be important but still...

In some cases I can see module internal being useful, eg: if a module is 'in charge' of a DLL, your might want some module-internal stuff for managing the dll.

Other options I can think of include:

* Internal could mean 'file access', so things marked 'internal' would work the way things work now, and things marked 'private' would become truly private. This doesn't sound too bad to me, but I don't think it's a complete solution. Being forced to put Node in the same file as List is still a bit nasty so this is just a 'bigger bandaid' really.

* Use C++ style 'Friend'. This allows for very find grained access, but would be a little more complicated to implement. It can also be a bit of a hassle, and is not very monkey2ish IMO.

Deadlock!

I'm gonna keep thinking about this for a while. I think it is in fact the kind of thing that needs to be settled before V1.0 because, if a change is made, it could be a fairly major hassle for users.


marksibly(Posted 2016) [#13]
ps: has anyone else been getting 'mandril errors' from monkey-x.com recently? I've had 2 today...


Danilo(Posted 2016) [#14]
Yep, had the same error with last posting.


Gerry Quinn(Posted 2016) [#15]
I'm okay with the current system. Clearly it does have an adverse impact on the methodologies of some people such as Danilo. But in my opinion, the people who 'just want to get it to work' aren't usually going to be big on restrictive access qualifiers anyway! I agree that it needs to be documented for those who do use them, though I kind of grokked the system as is without thinking very much about it. The rules seem simpler than Java, if anything.


Danilo(Posted 2016) [#16]
Imagine somebody wants to test or learn MX2 (or learn OOP using MX2), writing a simple test like this:
Class TestClass
    Public
        Field publicField:Int
    Private
        Method New()
        End

        Field privateField:Int
End

Function Main()

    Local obj := New TestClass
    
    obj.privateField = 12
    Print( obj.privateField )

End

My main concern is, I think many people would get the impression MX2 does not work correctly... but maybe that's just me.

Don't you think it's counter-intuitive? The opposite of what OOP books teach about access privileges?


abakobo(Posted 2016) [#17]
For me it's stange too. Private means somthing else in all other languages... and we don't have the real private thing.
Why not use some other word for it like ModulePrivate, ModPrivate, Produle, Pridule, Filevate, Privule, Modivate.... ;P
This way we could have every aspect of access privileges. (And it would stay in accordance with classical oop standards)


Danilo(Posted 2016) [#18]
'Private' means private to the class - not accessible from the outside.
Maybe the correct keyword for sharing it with the current file could be 'Shared'?


hub(Posted 2016) [#19]
Strange to read the Danilo code sample. 'Private' should be not used for this usage.


Danilo(Posted 2016) [#20]
@hub: What's the correct keyword in MX2 to hide (restrict access to) members in classes? (encapsulation)

When splitting the last code into a main file and TestClass.monkey2, the compiler hangs at "Semanting..."

Using Local for the 'private field' gives C++ errors:
Class TestClass
    Public
        Field publicField:Int
    Private
        Method New()
        End

        Local privateField:Int
        'Field privateField:Int
End

Function Main()

    Local obj := New TestClass
    
    obj.privateField = 12
    Print( obj.privateField )

End



Gerry Quinn(Posted 2016) [#21]
I guess the docs should read something like: "Fields and methods defined using the Private modifier can only be accessed by entities defined in the same file (usually a single class and perhaps some associated helper classes or functions)"

I can't see what's so terrible about it. It's not an outrage against human decency like Python's whitespace syntax ;-)


Nobuyuki(Posted 2016) [#22]
>I can't see what's so terrible about it. It's not an outrage against human decency like Python's whitespace syntax ;-)

agreed, but then again I was never trained in a formal OOP dojo nor have I prayed to the 4 gods of OOP best practice (nor even remember their names!). So maybe I don't see an encapsulation travesty as much as I can see the convenience. Private's scope wasn't handed down from some mountain written in stone, AFAIK. Scopes can mean whatever you want them to mean and indeed they mean all sorts of different things in different languages without anything canonical amongst all of them save for how scoping keywords generally contrast different "classifications" of scope...


AndroidAndy(Posted 2016) [#23]
I would tend to agree with Danilo's comments about the potential confusion around the dual interpretation of Private based on the physical file location of an encapsulating object. I can see it causing problems if you jammed a bunch of classes into a file during a coding flurry then later came back to break everything out or do some sort of organizational code cleaning for example. How about new keyword like others suggested above named "Partner". Another option could be using the "Friend" keyword in the same context, but the 3 P's seem to fit the model a bit better.

Class TestClass
    Public
        Field publicField:Int
    Partner
        Field partnerField:Int  'Visible to all objects within this physical file
    Private
        Method New()
        End

        Field privateField:Int   'Private to this class, no worries about the value being modified outside this class
End

Function Main()

    Local obj := New TestClass
    
    obj.privateField = 12         'FAIL
    Print( obj.privateField )     'FAIL
    obj.partnerField = 12        'SUCCEED
    Print( obj.partnerField )     'SUCCEED
End


In the above example if TestClass was moved out to a separate file, then the Partner field would by default become private and inaccessible to the Main(). Not sure would be worth all the effort, but would be more clear. It implies that there is a need to have "Partner" methods and fields, but for that I am not sure?


Floyd(Posted 2016) [#24]
ps: has anyone else been getting 'mandril errors' from monkey-x.com recently? I've had 2 today...


I had one at the Blitz site. You can see it here: http://www.blitzbasic.com/Community/post.php?topic=105438&post=1304700


Danilo(Posted 2016) [#25]
@AndroidAndy:
There is also 'Protected' already built-in, which is basically like 'Private' (not reachable from outside / protected from the outside) -
but accessible by derived classes / relatives. 'Private' is only for the one 'Class' itself, while 'Protected' is a 'Family-internal thingy' that gets inherited. ;)
The next level outside the family is usually 'Friends', if any. Not same blood, so very limited access to family-internals (only if the family allows it explicitly).


muddy_shoes(Posted 2016) [#26]
I think not having a level that allows some "group" access would be limiting. The problem here is the grouping mechanism.and that it destroys the existence of a truly locked down private access mode.

The slightly ironic thing is that if I were creating one of these single file libraries with multiple dependent classes that is being put forward as a use case then I want that locked down private even more. I want to be able to revisit the code and know when I look at a private member that it's not being messed about with elsewhere in the file. I don't want to be faced with a large file with any number of classes but no way of knowing where the dependencies might be because everything is de facto public amongst those classes.


AndroidAndy(Posted 2016) [#27]
@Danilo:
There is also 'Protected' already built-in, which is basically like 'Private' (not reachable from outside / protected from the outside) -


So it seems like the real issue is why not just make Private truly Private, which was your original point and I would agree it is important to make that consistent to avoid confusion. Looks like Mark is working on some ideas and as he mentioned up above it would important to finalize before v1.0 is released. Overall, giving it some thought is good to do no matter what the outcome.


Danilo(Posted 2016) [#28]
I think the outcome is very important! So it would be cool/awesome if MX2 follows some common - at least the very basic - OOP-concepts.

Good for teaching, spreading, and general use - while still being easily useable. 'Public' is the Default, so if you don't care about
deeper understanding of OOP-concepts, you can just ignore it - without every using Public/Protected/Private/Shared/Internal/Friend.

BASIC-like syntax with nearly/almost the power of C++, and without the hassles like writing header files,
forward declarations, etc. - and, at the same time, still being cross-platform and native as it can be,
without the requirement for a big .Framework and JIT-compiler-environment to be present.

MX2 forever! On all/most important platforms, of course! ;)

Just wanted to write some MX2-examples for the most common 23 design patterns, three days ago.
Yep, I think that task should be easily possible with such an advanced object-oriented programming-language...
...so I think it's good in general to discuss those (design) things/issues.


marksibly(Posted 2016) [#29]
I'm still unconvinced sorry!

I mean, I could add something like 'internal' that works the way 'private' currently does, but then I'd just start using 'internal' everywhere instead of 'private' (which just sounds wrong) because I don't think it's worth stressing over whether something should be 'internal' or 'private'.

I guess I don't fully understand why all code in a file *shouldn't* have access to all other code in the same file - it's all by the same author so I fail to see the harm unless you seriously don't 'trust yourself' or something. You're the one writing the private stuff in the first place, and I'm quite comfortable with allowing you convenient access to your own code.

> I can see it causing problems if you jammed a bunch of classes into a file during a coding flurry then later came back to break everything out or do some sort of organizational code cleaning for example.

The only problem I could imagine arising is if you inadvertently made something private that should be public (and how often would that happen, let alone the initial scenario?). Not a big deal, you just fix it. This is IMO not a good enough reason to add another access control level to the language.

I guess a similar example would be coders who like to stick 101 classes in one file (I assume there are still some around?). My argument here would simply be 'don't do that', for reasons that have nothing to do with public/private! And I suspect public/private subtleties are gonna be the least of their problems, so again I don't feel any urgent need to change anything for an 'edge' case like this.

> Just wanted to write some MX2-examples for the most common 23 design patterns, three days ago.

You're right here - you will need multiple files to do a 'private' example. This kind of sucks I guess, but again it's not enough reason to modify the language.

> ...so I think it's good in general to discuss those (design) things/issues.

Definitely! You always manage to raise 'interesting' points...

But my current thinking is to stick with the current system AND add 'friend' at some point in the future so you can split up tightly coupled classes into multiple files. I think the only real alternative I would consider is to go with 'friend' for all non-class private access - yet I still can't really justify to myself the idea of 'locking out' coders from their own code.

All in all, the current system works, it makes sense (to me anyway) and neither I nor anyone else appear to have had any problem with it in the past so I'm just inclined to leave it alone.

Finally, it turns out 'D' does exactly the same thing as mx2. There is no 'friend' or 'internal' etc in 'D' - code in the same file (which is a 'module' in D) can access any private stuff in the same file ala mx2. D was written by a much better coder than me (he's written c++ compilers in the past) and is used by a pretty hardcore OO crowd so at least I know I'm not totally out to lunch on this!


AndroidAndy(Posted 2016) [#30]
To 'D' or not too 'D'? that is the question, but too 'C' is not the answer :)... Seems like we have a winner, as long as it is documented as such shouldn't be a problem.


Danilo(Posted 2016) [#31]
Okay. Already mentioned several problems, but here the summary - to make sure it does not get unnoticed:

4 bugs/issues I see currently. Download all 4 tests as Bugs_2016_04_30.zip


1.)
AccessPrivateField_Main.monkey2
'
' MX2 compiler hangs at 'Semanting'
' when accessing the private field
'
#Import "AccessPrivateField.monkey2"

Function Main()

    Local obj := New TestClass
    
    obj.privateField = 12
    Print( obj.privateField )
    
    obj.protectedField = 10 ' Protected is OK

    Print("END")

End

AccessPrivateField.monkey2
' TestClass
Class TestClass
    Public

        Field publicField:Int

    Private

        Method New()
        End

        Field privateField:Int

    Protected

        Field protectedField:Int

End



2.)
ExpliciteConstructor_Main.monkey2
'
' Constructors seem to be always Public, even if they are Private
'
#Import "ExpliciteConstructor.monkey2"

Function Main()

    Local obj1 := New ExpliciteConstructor      ' I want to disallow this: Method New() is Private
    Local obj2 := New ExpliciteConstructor("X") ' OK

    Print("END")

End

ExpliciteConstructor.monkey2
Class ExpliciteConstructor
    Public

        Method New(name:String)
            _name = name
        End

    Private

        Method New()
        End
        
        Field _name:String
End



3.)
LocalVariableInClass_Main.monkey2
'
' C++ Build errors when accessing a 'Local' variable inside a class
' MX2 should probably give an error if that's not allowed
'
Class TestClass
    Public
        Local publicField:Int
        'Field publicField:Int
    Private
        Local privateField:Int
        'Field privateField:Int
End

Function Main()

    Local obj := New TestClass

    obj.publicField  = 11
    obj.privateField = 12
    
    Print("END")
End



4.)
MemoryAccessViolation_Main.monkey2
Class TestClass
End

Function Main()

    ' Semanting...
    ' ***** Uncaught Monkey 2 Exception: Memory access violation *****
    ' ***** Reason: use of '=' instead ':='                      *****
    Local obj = New TestClass
    
    Print("END)

End



marksibly(Posted 2016) [#32]
Nice examples!

Got most of the issues fixed now, but the new debug stuff is still in a bit of a 'delicate' state so I wont be updating git for a wee while yet.


Danilo(Posted 2016) [#33]
Thanks in advance, Mark!