Proposal idea: partial classes (mixins)

Class-factory mixins are a powerful concept for organizing re-usable class code. However, with the introduction of #hashtag private fields, mixins suffer the problem that private fields are not reliable across mixin applications (because technically each class returned by a mixin is a different class, but that’s not the intent developers typically have with private fields).

Another issue is that class-factory mixins are very difficult to use in TypeScript, requiring a lot of boilerplate, and riddled with issues causing TypeScript type features to be unavailable for that use case. For example private and protected members are not currently supported with class-factory mixins, among others.

The following issue details both problems:

What if we had a way to define a partial class, which can be used to define a concrete class later? Mixins could look like the following:

partial class Foo {
  #foo = 123

  meth() {
    console.log(this.#foo)
  }
}

It could then be used as a regular class to extend from, or as a mixin. Without types in plain JS, like this:

class MyClass extends Foo {}
new MyClass().meth() // logs 123

class OtherClass extends MyClass(AnotherClass) {}
new OtherClass().meth() // logs 123

A partial class cannot have an extends clause. A base class can only be provided to the partial class by the user of the partial class, and the extends becomes implicit with the given user base class.

For TypeScript, the type of base classes could be constrained with added type syntax, similar to a return type:

partial class Foo: HTMLElement {
  #foo = 123

  meth() {
    console.log(this.#foo)
  }
}

In this example, the Foo class is restricted to accepting only base classes that extend from HTMLElement.

class MyEl extends Foo(HTMLDivElement) {...}

class OtherEl extends Foo(Object) {...} // type error in TypeScript

The presence of the type annotation, in TypeScript, also means that APIs inside of the partial class can be type checked based on the base class constraint:

partial class Foo: HTMLElement {
  #foo = 123

  meth() {
    console.log(this.#foo)

    this.querySelector('.foo') // ok, this member is available due to the base class constraint.
  }
}

If the colon is awkward there, TypeScript could also introduce a type-only keyword, f.e.

partial class Foo accepts HTMLElement {
  ...
}

or similar. Actually, I think I like accepts better than a colon!

This ES feature would

  • allow for #private syntax to work across all instances of the partial class, regardless of which base class is provided (similar to WeakMaps semantics that are defined outside the lexical scope of a mixin)
  • potentially make type definitions for mixins in TypeScript easy, or if technically the same complexity would encourage TypeScript to officially support mixin types due to alignment of TypeScript with EcmaScript, finally allowing TS developers to enjoy mixins without all the fuss but with all the practicality

It's also possible that the accepts HTMLElement idea for TypeScript, could be for JavaScript, and enforce an instanceof-like check to verify a given class is or extends from the required base class. Runtime type checking hasn't been a JS feature though.

https://github.com/tc39/proposal-mixins already exists, and faces a lot of pushback.

Personally, i strongly prefer the https://github.com/tc39/proposal-first-class-protocols proposal.

You may also find https://legacy.reactjs.org/blog/2016/07/13/mixins-considered-harmful.html worth reading.

The proposal-first-class-protocols concept seems a lot less ergonomic than mixins or partial classes. They seem better suited for adding (at runtime) arbitrary clash-free methods onto existing objects or prototypes. It has parts that aren't statically analyzable at the syntax level, and without actual usage examples, it seems like we can do all of that with Symbol very well. I see examples of how to define protocols on classes (making classes into functional patterns is just odd) yet no examples of how to actually use the classes, so it is hard to imagine what's the benefit.

The partial class idea on the other hand is intended to be fully static analyzable (like proposal-mixins). It is possible we can avoid clashes by using symbols with such a proposal too. I think that would be easy.

That Mixins Considered Harmful article is fairly biased, by a certain author who works on a certain framework that I consider legacy, with too many foot guns (just in Hooks alone). I'll never willfully use that framework again (unless they make some serious breaking changes, which I doubt they will after having chatted with people in the company).

I mean goodness sakes, the code in that article is from an era before ES2015 class syntax.

var PureRenderMixin = require('that-framework-addons-pure-render-mixin');

var Button = ThatFramework.createClass({
  mixins: [PureRenderMixin],
  // ...
});

It's hard to take that article seriously today, especially when in context of a limited framework with a bag of other problems.

I'm a Solid.js contributor, and unlike Ryan C who likes to boast about the strengths of every framework (to be diplomatic perhaps), I am honest in saying that That Framework is my least favorite framework.

I've had multiple opportunities to see early-career developers move from that framework to Signals and Effects (Solid.js), and the difference in productivity is notable. They do much better in a system that provides the best pit of success out of the box.

That Mixins Considered Harmful article was written in a time when TypeScript was still new (let alone ES2015!), and the article mentions various issues that exist in typeless plain JavaScript.

Mixins in that framework, in plain JavaScript, may be a bad thing. Sure.

But that's not a nice way to blanket cover mixins. Mixins are useful when they are well-scoped (#private fields would help).

With whatever pattern you think is best, I can show you spaghetti code with that pattern right away. So like just because some That Framework developers made spaghetti code in archaic That Framework in pre-classes pre-TypeScript times has little to no bearing here.

That Framework's Hooks system is a good idea taken and implemented in the worst possible way that leads to many issues a lot worse than anything I've seen people encounter with mixins.

If we add Symbols to the mix, consider that TypeScript is king today, and that we may even get proposal-type-annotations if we're lucky, mixins (with clash-free symbol sugar, sure!) will allow easy-to-implement code re-use patterns.

Remember, no matter what patterns we have, we can make spaghetti in all of them. That framework's Hooks pattern allows for some very serious spaghetti to be made.

Why I like the partial class idea more than proposal-mixins

A partial class can behave just like any other class when not used as a mixin. This is more useful than proposal-mixins, where a mixin class can only be used as a mixin, and requires secondary syntax in the extends section of a class. The following is possible with partial classes, but not with proposal-mixins:

partial class Foo {...}
   
// extend like a regular class
class MyClass extends Foo {}

This makes it easy to continue using partial classes regularly in scenarios where mixin-style usage is not necessary.

But a partial class gains one thing: when its constructor is called without new, then it behaves like a class-factory mixin, automatically applying the passed-in base class.

partial class Foo {...}
   
// apply it like a mixin
class MyClass extends Foo(SomeOtherClass) {}

That's about it. It's a little bit simpler than proposal-mixins in that sense, as far as end usage and syntax.

With type safety (f.e. from TypeScript), we can easily enforce relationships (f.e. we TypeScript would be able to throw an error if a non-constructor is passed in).

It's not a huge difference, but I think it's a little bit nicer.

Plus this idea includes the idea of engines making #private work. So it is not just sugar for class-factory mixin functions.

clash-free options

We could just use Symbols as-is.

But could we make it a little bit more ergonomic (for any type of class, regular or partial)?

partial class Foo {
  symbol foo = 123 // unique like private fields, but public not private.
}

This could be a separate proposal that works for object literals too.

class MyClass extends Foo(OtherClass) {
  method() {
    console.log(this[Foo.foo])
  }
}

A symbol property, similar to protocols, could just be create a static field with a symbol value basically, as well as having an instance initializer.

We could make up some rule such as that partial classes have only symbol properties, or something. I could totally live with that.

This,

class Foo {
  symbol foo = 123
}

could be sugar for this,

const foo = Symbol()

class Foo {
  static foo = foo
  ;[foo] = 123
}

I'd like to re-iterate one key piece:

Making #private fields work in partial classes (or proposal-mixins) would solve key issues of name clashes so we don't need to use still-brittle __foo conventions.

With static Symbols, we can also know which namespace we're calling explicitly.