Skip to content
This repository has been archived by the owner on Jan 25, 2022. It is now read-only.

What about protected fields? #86

Closed
trusktr opened this issue Mar 21, 2018 · 5 comments
Closed

What about protected fields? #86

trusktr opened this issue Mar 21, 2018 · 5 comments

Comments

@trusktr
Copy link

trusktr commented Mar 21, 2018

The majority of the time, when I write something like this._foo = 'bar', it ends up being a "protected" member when subclasses refer to the property yet I don't want the property to be part of the public API. A good chunk of the time the properties remain "private".

It would be great if this proposal would have all three of public, protected, and private fields.

Here's an class utility I made with public, protected, and private members, including a super helper for super public or protected methods (I can support super for private methods too, but I wasn't sure yet if that makes sense): https://github.com/trusktr/lowclass.

I'm thinking it would interesting to make a Babel plugin that can convert syntax from this proposal into something that uses lowclass at runtime, for proof of concept.

@ljharb
Copy link
Member

ljharb commented Mar 21, 2018

The quotes around "protected" are accurate; in your usage, it's fully public - can you elaborate on how you think we could specify "protected" such that subclasses defined in separate modules/files could have access to the fields, but everyone else would be fully denied access?

@bakkot
Copy link
Contributor

bakkot commented Mar 21, 2018

See previously and also the rest of that thread, which has some links to other previous discussions.

@trusktr
Copy link
Author

trusktr commented Mar 21, 2018

can you elaborate on how you think we could specify "protected" such that subclasses defined in separate modules/files could have access to the fields, but everyone else would be fully denied access?

If you mean how to implement it in the engine, I'm not sure about that, I'm not familiar with engine implementation much (barely).

If you mean, how to symbolize it syntactically, I'm sure we can come up with something. F.e. this.# and this.& for private and protected, respectively (or similar, I can come up with many ideas 😃 ).

The "protected" concept of my JS version in lowclass "accessible only to the class and its subclasses" (as mentioned where you linked @bakkot) but external code can not access the properties (similar to C++/Ruby I suppose, which I think I like most so far, and I think C++ is a good reference point in general).

With private properties in lowclass, "a property is only accessible by the class" as mentioned in that other thread too.

For example, try this:

npm i lowclass
const Class = require('lowclass')

const Foo = Class((public, protected, private) => ({
    foo() {
        console.log('foo is public')
    },
    protected: {
        bar() {
            console.log('bar is protected')
        },
    },
    private: {
        baz() {
            console.log('baz is private')
        },
    },
}))

// (alternate "syntax" here, defining props/methods on helpers instead of returning an object)
const Bar = Foo.subclass((public, protected, private, _super) => {
    public.test = function() {
        this.foo()

        // call super protected method from public method
        _super(protected(this)).bar()

        try { _super(private(this)).baz() }
        catch(e) { console.log('unable to access privates, 1') }

        try { this.baz() }
        catch(e) { console.log('unable to access privates, 2') }

        protected(this).test2()
    }

    protected.test2 = function() {
        console.log('call inherited protected member from protected')

        // try various ways:
        this.bar()
        _super(this).bar() // redundant
        _super(protected(this)).bar() // even more reduntant

        private(this).test3()
    }

    private.test3 = function() {
        console.log('call inherited protected member from private')
        // try various ways:

        try { this.bar() } // error
        catch(e) { console.log('bar is not private of this class') }

        try { _super(this).bar() } // error
        catch(e) { console.log('privates are not currently inheritable (we could change that)') }

        _super(protected(this)).bar() // works
    }
})

const f = new Foo
f.foo()

// not possible to access privates in outside code
try { f.baz() }
catch(e) { console.log('unable to access privates, 3') }

// not possible to access protecteds in outside code
try { f.bar() }
catch(e) { console.log('unable to access protected, 4') }

const b = new Bar
b.test()

Output:

foo is public
unable to access privates, 3
unable to access protected, 4
foo is public
bar is protected
unable to access privates, 1
unable to access privates, 2
call inherited protected member from protected
bar is protected
bar is protected
bar is protected
call inherited protected member from private
bar is not private of this class
privates are not currently inheritable (we could change that)
bar is protected

Personally I like the expressiveness of the words public/protected/private in my implementation; it is clear what we're accessing, and plus various text editors highlight these words differently as a bonus 😃.

@trusktr
Copy link
Author

trusktr commented Mar 21, 2018

Sidenote, it is possible to leak the access helpers, which usually should be avoided, but it might be useful in cases where the data is still private to a module file, and only the public API is exported (this is the nice thing about the runtime implementation):

let parentPrivate

const Parent = Class((public, protected, private) => {
  parentPrivate = private
  private.foo = 'i am private'
})

const Child = Parent.subclass(() => ({
  constructor() {
    const parent = new Parent
    console.log( parentPrivate(parent).foo ) // => i am private
  },
}))

export { Parent, Child }
import { Child } from '...'

new Child // => i am private

There could be some interesting use cases for this, sort of like "module private" of sorts, etc. Not possible with only the syntax in this proposal, but possible with runtime because it's all just references.

@littledan
Copy link
Member

@bakkot provided good references to related discussions, so closing this thread.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants