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

Why is this ignoring common practices used in other object oriented languages? #297

Closed karlsve closed 4 years ago

karlsve commented 4 years ago

I am specifically referencing this line and the following information on the order of execution of tasks.

The current state of classes in ECMAScript as implemented by browser developers seems to introduce a major inheritance issue coming from any other object oriented language. (Even if like PHP not build with OOP at its core)

The common and natural inheritance order would be:

  1. Define all properties from the base class and set values
  2. Define all additional properties from the derived class, set their values and override existing base values
  3. Execute base constructor
  4. Execute derived constructor

The order currently proposed is going to break any inheritance driven development that relies on being able to override the default value of a base class.

I mean behavior like this should never be expected anywhere: TS: https://stackblitz.com/edit/typescript-g15h8r JS: https://stackblitz.com/edit/js-davxsr

As soon as someone extends a class it might break entire libraries just because they redefined some public property.

As I do not want to create another big discussion I would like to know in detail why this order of inheritance was chosen and not the one as used by a lot of object oriented languages?

PS: Just to show how PHP behaves I am leaving the following code snippet here for anyone to test.

<?php
​
class Base {
    protected $prop = 0;
    public $otherProp = 'foo';
    function __construct($base) {
        foreach (get_object_vars($base) as $key => $value) {
            $this->$key = $value;
        }
        if($this->prop === 0) {
            $this->otherProp = 'foobar';
        }
    }
}
​
class Extension extends Base {
    protected $prop = 5;
    public $otherProp = 'bar';
}
​
$base = new stdClass();
$base->prop = 0;
$extended = new Extension($base);

// $extended results in { prop: 0, otherProp: 'foobar'  }

Please out of my own interest, just hit me up with other languages behaving as expected or once that are doing it similar to this proposal.

ljharb commented 4 years ago

Fields have to run as part of the constructor; JS inheritance works a bit differently than you might be used to in other languages. There’s no facility in the language for a superclass constructor to have any visibility into the code the subclass constructor chooses to run.

bakkot commented 4 years ago

The common and natural inheritance order would be: [...]

That is not the execution order in, say, Java or C++: the following Java program prints getting 0, getting 1, getting 2, getting 3.

class Main {
  static int get(int a) {
    System.out.println("getting " + a);
    return a;
  }
  static class Base {
    public int x = get(0);
    public int y;
    Base() {
      this.y = get(1);
    }
  }
  static class Derived extends Base {
    public int x = get(2);
    public int y;
    Derived() {
      this.y = get(3);
    }
  }
  public static void main(String[] args) {
    Derived d = new Derived();
  }
}

and the following C++ program does the same:

#include <iostream>

int get(int a) {
  std::cout << "getting " << a << std::endl;
  return a;
}

class Base {
  public:
  int x = get(0);
  int y;
  Base() {
    this->y = get(1);
  }
};

class Derived : Base {
  public:
  int x = get(2);
  int y;
  Derived() {
    this->y = get(3);
  }
};

int main() {
  Derived d = Derived();
}

I mean behavior like this should never be expected anywhere:

Really? Here's some Java which looks an awful lot like your JS and which prints 20:

class Main {
  static class Test {
    int prop = 10;
    Test(int prop) {
      this.prop = prop;
    }
  }
  static class ExtendedTest extends Test {
    int prop = 20;
    ExtendedTest(int prop) {
      super(prop);
    }
  }
  public static void main(String[] args) {
    ExtendedTest t = new ExtendedTest(5);
    System.out.println(t.prop);
  }
}

Of course it does so for slightly different reasons, but this is hopefully not too surprising because the languages are not identical; the point is that if you are reasoning from what "similar-looking" code would print in another language, then the extent to which you find JS's behavior surprising will depend on which language you are coming from.


I think you overestimate the extent to which the behaviors of the languages you are familiar with are universally shared. Just because something works a particular way in PHP does not mean everyone else is going to find that natural.

karlsve commented 4 years ago

That's why I opened this issue, to make sure I was not just basing stuff on my limited knowledge. It still feels awkward to me, but I guess I gotta live with it :/

hax commented 4 years ago

Of course it does so for slightly different reasons

It's not "slightly" different but totally different.

Change ExtendedTest t = new ExtendedTest(5); to Test t = new ExtendedTest(5); will print 5.