tc39 / proposal-class-fields

Orthogonally-informed combination of public and private fields proposals
https://arai-a.github.io/ecma262-compare/?pr=1668
1.72k stars 113 forks source link

Collision with `Static Class Variable` concept from other similar languages #113

Closed skatcat31 closed 5 years ago

skatcat31 commented 6 years ago

NOTE: I am not talking about Static Variables which are on instances of the class, but can never be changed. These by another name are called getters without setters

Classes were created as a way to make ES6 more approachable from users of other languages and to solve the problem of prototype pollution. It also lead to more maintainable code. That made it instantly recognizable to developers of any other language by design and drove adoption rates. Now this proposal seems to compete with a prior art from other key worded dynamic languages and threaten to pollute what should and should not belong in a constructor and give it a footprint that is confusing to any adopting developers from Python,or any other language that implements the Static Class Variable concept and key worded constructors. Why would we want to meld the Python constructor keyword and member variable declaration pattern with the Java/C++/PHP instance variable declaration pattern leading to multiple places to declare instance variables?

class Person{
  ancestor = 'monkey'
  constructor(name){
    this.name = name;
  }

  betterScience(newAncestor){
    ancestor = newAncestor
  }
}

let fredSmith = new Person('Fred Smith')
fredSmith.betterScience('ape')

let georgeJones = new Person('George Jones')
georgeJones.ancestor == 'ape' // true

In this example a Python(almost identical), Java, and C++ developer would easily be able to recognize that the ancestor variable is a static class variable(single instance of the variable, accessible by all instances) that can be modified by a single instance, and then fetched on any instance. We currently do this in ES6 with IIFE statements:

const Person = (function () {
  let ancestor = 'monkey'
  class {
    constructor(name){
      this.name = name;
    }

    betterScience(newAncestor){
      ancestor = newAncestor
    }

    get ancestor(){
      return ancestor
    }
  }
})()

let fredSmith = new Person('Fred Smith')
fredSmith.betterScience('ape')

let georgeJones = new Person('George Jones')
georgeJones.ancestor == 'ape' // true

This is also a concept used consistently in React/Redux environments to pass a single instance of the state around to the React Classes to be used and is still accessed in the constructor to be referenced by the member. The slight difference being that redux maps it to a props key instead of just adding it to decleration, but the single instance accessible to all inheriting children with a concurrent state is similar in concept, if not exact in execution.

While this could just fall under another keyword, it shows collision with widely adopted principles that are extremely similar and the prior art in JS and other similar languages that do impliment static class variables in this way(Python, R, Ruby, Perl).

bakkot commented 6 years ago

In this example a Python(almost identical), Java, and C++ developer would easily be able to recognize that the ancestor variable is a static class variable(single instance of the variable, accessible by all instances) that can be modified by a single instance, and then fetched on any instance.

... What? The equivalent Java code:

class Person {
  String ancestor = "monkey";
}

creates an instance variable, not a static variable. Try it.


To create a static variable in Java, you'd do

class Person {
  static String ancestor = "monkey";
}

and in JS with this proposal you would do

class Person {
  static ancestor = "monkey";
}

i.e., pretty much just like the Java version.

skatcat31 commented 6 years ago

While you're right on Java, Java has a confusing way of defining classes anyways... Competing constructors that can be anywhere in a Class makes it a fun hunt and peck exercise. I'm not certain we'd want to adopt that version when we've already adopted the Python constructor version of keywords and assignment in constructor. I'll remove the list of langauge and make more about implimentation since while a similar concept, they all implement differently.

Python however has a direct collision: https://www.geeksforgeeks.org/g-fact-34-class-or-static-variables-in-python/

# Python program to show that the variables with a value 
# assigned in class declaration, are class variables

# Class for Computer Science Student
class CSStudent:
    stream = 'cse'                  # Class Variable
    def __init__(self,name,roll):
        self.name = name            # Instance Variable
        self.roll = roll            # Instance Variable

# Objects of CSStudent class
a = CSStudent('Geek', 1)
b = CSStudent('Nerd', 2)

print(a.stream)  # prints "cse"
print(b.stream)  # prints "cse"
print(a.name)    # prints "Geek"
print(b.name)    # prints "Nerd"
print(a.roll)    # prints "1"
print(b.roll)    # prints "2"

# Class variables can be accessed using class
# name also
print(CSStudent.stream) # prints "cse"

They do however do not have predefined instance variables either. This is the closest to the ES6 class declaration. This also helps maintainability by keeping all member/instance variables in a single defined place.

C++ is a little bit more odd in it's decleration of Static Class Variables: http://www.learncpp.com/cpp-tutorial/811-static-member-variables/

class Something
{
public:
    static int s_value;
};

int Something::s_value = 1;

int main()
{
    Something first;
    Something second;

    first.s_value = 2;

    std::cout << first.s_value << '\n';
    std::cout << second.s_value << '\n';
    return 0;
}

It sets them afterward, but still declares them in the class body.

bakkot commented 6 years ago

In addition to Java, C++'s statics are also pretty much like this proposal's:

class Something {
    static int a; // static variable
    int b; // instance variable
};

So are PHP's:

class Something {
    public static $a = 1; // static variable
    public $b = 2; // instance variable
}

So are Swift's:

class Something {
    static var a = 1 // static variable
    var b = 1 // instance variable
}

Python's class is pretty unusual in a number of ways, this among them. But since we're following the precedent in most related languages which have classes, I'm not too worried.

skatcat31 commented 6 years ago

I noted that this could probably be avoided by key wording statics as well. The problem is we aren't adhering to those classes in ES. We adhere to Python and other key worded constructor dynamic languages more so than the OOP version and it's what skyrocketed adoption of ES classes. So maybe this issue is moot more so and it should be clarified that it's conflicting with other similar language principles. Which is more likely the case. I'll update the title to match.

bakkot commented 6 years ago

What languages other than python have that particular behavior for classes?

skatcat31 commented 6 years ago

R, Ruby, Perl to name three common ones

bakkot commented 6 years ago

Ruby, yes, although in my experience Ruby's behavior here tends to surprise people. In what sense do R and Perl share this behavior?

skatcat31 commented 6 years ago

R has the same 'variables outside of constructor are static class variables' and Perl has the same sense of module scoped variables for static variables to the class(meaning outside of class constructor are static class variables because of closures) however Perl's exact syntax means it kind of actually matches the IIFE pattern more precisely.

skatcat31 commented 6 years ago

basically key worded constructors languages with closures consider variables outside the class to be static if in closure.

bakkot commented 6 years ago

Sorry, to be precise, I meant that the things R and Perl call classes are not very syntactically similar to JavaScript's classes, so it's hard for me to see what you mean when you say that they have the same behavior as Python. The examples I gave are all very syntactically similar to JavaScript and have correspondingly similar semantics. Could you give an example of idiomatic R and/or Perl code which you think would lead programmers only familiar with those languages to be surprised by the proposed JavaScript behavior?

I also don't know what you mean by "key worded constructors languages". PHP and Swift both use keyword-like method names to declare class constructors (__construct and init respectively), for example - is that what you mean?

skatcat31 commented 6 years ago

@bakkot key worded constructor languages are languages that only allow constructors and that the constructor is where you assign member variables to the instance. It means that in a class, constructors are uniquely referenced and has a specified purpose. Constructors will always be the method called when you use new Class() if present.

As for R:

make.f <- function() { # class decleration
    count <- 0 # static class variable
    f <- function(x) {
        count <<- count + 1
        return( list(mean=mean(x), count=count) )
    }
    return( f )
}

As for Perl:

package Bar; # class(package) decleration. It's what gets imported
use strict;
use warnings;

sub new { bless {}, shift }; # constructor

my $foo = 123; # class variable with getter, setter possible
sub get_foo { $foo }

our $fubb = 456; # can be access on the class as Bar::fubb

Perl like I said is probably closer to the IIFE method of defining it, but outside of the constructor is where you are accessing it.

bakkot commented 6 years ago

It means that in a class, constructors are uniquely referenced and has a specified purpose. Constructors will always be the method called when you use new Class() if present.

OK. That definition includes both PHP and Swift, both of which have closures (in some sense), and both of which behave like this proposal; I'm not sure this dichotomy is as strong as you are suggesting.

As for R: / As for Perl:

Thanks for these examples. Since these are very dissimilar from JavaScript's class syntax, I am not very worried about programmers coming from those languages being surprised by this particular piece of this proposal.

skatcat31 commented 6 years ago

Then I need to clarify the definition. Rereading it I seem to have word garbled(as well as taken a hatchet to grammar)

Key worded constructor languages are languages that limit member variable assignment to the constructor methods.

As for R example translating it to JS:

class f{
  count = 0
  // class has no constructor because no instance variables

  f (x) {
    count += 1
    return [mean(x), count]
  }
}

As for Perl example translation to JS:

class Bar{
  foo = 123
  constructor(){...}

  get foo() {
    return foo
  }
}

Bar.fubb = 456
littledan commented 5 years ago

My impression is that the Python semantics are unusual compared to other languages and a bug farm, rather than a good concept to be copied. For the other examples, JavaScript closures can provide the analogous behavior.