Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Constructor Returns Subclass of Constructed Class: Extra Newness v2: Deceptively Sensical Looking #208

Open
joedski opened this issue May 6, 2021 · 2 comments
Labels
new-example A proposal of the new example

Comments

@joedski
Copy link

joedski commented May 6, 2021

I previously fiddled around with the new new Foo('foo')('bar') pattern by extending Function. However, after working with more reasonable code my brain rebelled once again and remembered an odd fact about the new operator: if the function called as a constructor returns an object, that object becomes the result of the new operator, not the constructed instance!

This means we can write a far more fun example:

class Renewable {
  static get values() {
    return [];
  }

  constructor(value) {
    const Base = this.constructor;
    const prevValues = Base.values;

    return class $Renewable extends Base {
      static get values() {
        return [...prevValues, value];
      }

      constructor(nextValue) {
        return super(nextValue);
      }
    };
  }
}

Which means you can now write the most obnoxious chained call that no one ever wanted:

new new new Renewable('Foo')('Bar')('Zap').values; // => ['Foo', 'Bar', 'Zap']

This is deceptively simple until you look closer, in which case it becomes a recursive pile of dumb:

class Renewable {
  // We want to set an initial value for `constructor.values` just to make
  // later code cleaner.
  static get values() {
    return [];
  }

  // A perfectly ordinary constructor on a perfectly (not) ordinary base class.
  constructor(value) {
    // Begin BS: since this is a base class, we can reference `this` without
    // a call to `super()`.  `this` will be the constructed/ing instance,
    // and as an instance it will have a reference to its constructor.
    // AN IMPORTANT NOTE: If this instance is an instance of a _subclass_,
    // `this.constructor` will be a reference to that _subclass's constructor_,
    // not to the base class!
    const Base = this.constructor;

    // We defined `values` as `static`, so it's attached to the constructor.
    const prevValues = Base.values;

    // Remember, a Class is referenced by its Constructor Function, so we can
    // just use that Constructor off of `this` as the base class for this new class...
    // NOTE: This is a _return statement_!  We're not just defining a subclass
    // of the current instance's class, we're also replacing the result of
    // the `new` operator by returning some object other than `this`!
    return class $Renewable extends Base {

      // In each subclass, we return all the values from the base class plus the
      // next value sent through the constructor.
      static get values() {
        return [...prevValues, value];
      }

      // A seemingly innocuous subclass constructor, which just passes the value
      // on to the super class's constructor...
      constructor(nextValue) {
        // ... except that it's continuing the trainwreck of madness by returning
        // the result of super()!
        return super(nextValue);
      }
    };
  }
}

So what happens each time we call new Renewable('Foo')?

  1. Renewable's constructor function is called with 'Foo'
  2. Base is set to this.constructor, which the first time around will be Renewable itself.
  3. prevValues is set to Base.values, which the first time is Renewable.values, which is [].
  4. A new class $Renewable is defined that extends Base, which right now is just Renewable itself.
  5. That new class $Renewable is returned instead of the newly constructed instance, which results in new returning that subclass rather than the constructed instance of Renewable.

At that point, if we checked .values we'd see an array: ['Foo']

What happens with new new Renewable('Foo')('Bar') then?

  1. First, all of the above occurs, causing the first new operator to return that subclass constructor.
  2. Then, the next new operator calls that returned constructor with 'Bar'.
  3. The fun begins with $Renewable's constructor calling super(nextValue): This goes up to the original Renewable constructor.
  4. In that constructor, this.constructor is now $Renewable's constructor!
  5. And Base.values will then be $Renewable.values, which is ['Foo'].
  6. And then... Renewable's constructor creates another subclass, this time of the first $Renewable class!
  7. ... And then returns that sub-sub-class, continuing the unvirtuous cycle.

Thus, from that, new new Renewable('Foo')('Bar').values will be ['Foo', 'Bar'].

Because each class just returns another subclass, this can be done as far as you want!

Edit: The Same Pattern, but Fewer Class Keywords

Despite the trickery to support the class keyword, Classes in JS are still essentially functions, albeit rather funky ones.

This extra-new pattern however is trivial to implement even without this newfangled feature:

const Renewable = (() => {
  function Renewable(value) {
    const Base = this.constructor;
    const prevValues = Base.values;

    function $Renewable(nextValue) {
      return Renewable.call(this, nextValue);
    }

    // Uncomment if you actually care about the prototype chain...
    // $Renewable.prototype = Object.create(Base.prototype);
    // $Renewable.prototype.constructor = $Renewable;

    Object.defineProperty($Renewable, 'values', {
      get() {
        return [...prevValues, value];
      },
    });

    return $Renewable;
  }

  Object.defineProperty(Renewable, 'values', {
    get() {
      return [];
    },
  });

  return Renewable;
})();
@joedski joedski added the new-example A proposal of the new example label May 6, 2021
@denysdovhan
Copy link
Owner

Would you like to send a PR with this?

@joedski
Copy link
Author

joedski commented Aug 8, 2021

I do apologize for completely missing this for so long, I'll try to get a PR in at some point though life has sadly been a bit preoccupying

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
new-example A proposal of the new example
Projects
None yet
Development

No branches or pull requests

2 participants