Skip to main content

Dart's modest proposal for Mixins

Posted by Gilad Bracha

(Editor's note: mixins is a work in progress, and is not slated for the M1 release. You can track the bug for this feature, and star it to be notified of changes. Please direct discussion to the Dart mailing list.)


Mixins in Dart

This document a minimalist proposal for introducing mixins in Dart. It is deliberately restricted in several ways, so as to minimize disruption to our existing implementations, while allowing future evolution toward a full fledged mixin implementation. The hope is that this restricted version already provides considerable value.

The intent is that something very much like this would be incorporated into Dart at some time after M1.

Basic Concepts


If you are familiar with the academic literature on mixins you can probably skip this section.
Otherwise, please do read it, as it defines important concepts and notation.


In a language supporting classes and inheritance, a class implicitly defines a mixin. The mixin is usually implicit - it is defined by the class body, and constitutes the delta between the class and its superclass. The class is in fact a mixin application - the result of applying its implicitly defined mixin to its superclass.

The term mixin application comes from a close analogy with function application. Mathematically, a mixin M can be seen as a function from superclass to subclass: feed M a superclass S, and a new subclass of S is returned. This is often written as M |> S in the research literature.

Functions are useful because they can be applied to different arguments. Likewise mixins. The mixin implicitly defined by a class is usually applied only once, to the superclass given in the class declaration.  To allow mixins to be applied to different superclasses, we need to be able to either declare mixins independently of any particular superclass, or alternately, to extricate the implicit mixin of a class and reuse it outside its original declaration. That is what we propose to do below.

A Modest Proposal


We propose that mixins be defined via ordinary class declarations.  In principle, every class defines a mixin that can be extracted from it. However, in this proposal, a mixin may only be extracted from a class that obeys the following restrictions:

  1. The class has no fields.
  2. The class has no declared constructors.
  3. The class’ superclass is Object.
  4. The class contains no super calls.


Restrictions (1) & (2) avoid complications that arise due to the need to pass constructor parameters up the inheritance chain.  Under those circumstances, restriction (3) encourages mixins to be declared explicitly. Restriction (4) means that implementations can continue to statically bind super calls rather than either rebinding them upon mixin application, or binding them dynamically.


Example:

class Collection<E> {
  abstract Collection<E> newInstance();
  Collection<E> map(f) {
    result = newInstance();
    this.forEach((E e){result.add(f(e));})
    return result;
 }
}

class DOMElementList<E> mixin Collection<E> extends DOMList {
  DOMElementList<E> newInstance() => new DOMElementList();
  ...
}

class DOMElementSet<E> mixin Collection<E> extends DOMSet {
  DOMElementSet<E> newInstance() => new DOMElementSet();
  ...
}


… 28 more variants


Here, Collection<E> is a normal class that is used to declare a mixin.  Both the classes DOMElementList and DOMElementSet inherit from mixin applications.

In the above, the superclass of DOMElementList is Collection mixin |> DOMList, while the superclass of DOMElementList is Collection mixin |> DOMSet.

The benefit here is that the code in class Collection can be shared in multiple class hierarchies. We list two such hierarchies above - one rooted in DOMList and one rooted in DOMSet. One need not repeat/copy the code in Collection, and every change made to Collection will propagate to both hierarchies greatly easing maintenance of the code. This particular example is loosely based on a real and very acute case in the Dart libraries.

How do the above examples work if DOMList or DOMSet have non-trivial constructors?

class DOMElementList<E> mixin Collection<E> extends DOMList {
  DOMElementList<E> newInstance() => new DOMElementList(0);
  DOMElementList(int size): super(size); // super size me
}


This looks good, but notice that:

  1. The superconstructor call invokes a non-trivial superconstructor.
  2. Collection has no non-trivial constructor.
  3. Constructors are not inherited.
  4. The superclass is the mixin application Collection |> DOMList, which by the above, has no non-trivial constructor, and so the code would appear to be erroneous.


To deal with this problem, I propose that each element of the mixin list has its constructor called independently. Each mixin has its own constructor called, and so does the superclass.  Since a mixin constructor does nothing at all, the call to it can be elided in both the syntax and the underlying implementation.  

Note however, that the generic type arguments must be set somehow. Usually, this would be set up by the constructors.

This rule ensures that these examples run smoothly and also generalize cleanly once one lifts restrictions (1) & (2).


A second example illustrates the limitations of the proposal, Suppose we want to create a Person who is Demented, Aggressive and Musical.

class Person {
 String name;
 Person(this.name);
}

class Maestro  mixin Demented, Aggressive, Musical extends Person {
 Maestro(name):super(name);
}


Here, the superclass is the mixin application

Demented mixin |> Aggressive mixin |> Musical mixin |> Person

we assume that only Person has a constructor with arguments. Hence Musical mixin |> Person inherits Person’s constructors, and so on until the actual superclass of Maestro, which is formed by a series of mixin applications.

However, in reality in this example we’d expect that Demented, Aggressive and Musical actually have interesting properties that are likely to require state. If they don’t, they are liable to be simply marker interfaces, in which case we did not need mixins at all.

Later on we will discuss how the restrictions might be lifted to accommodate such cases.

There remain many details to attend to.  This proposal is not yet at the stage of a full specification, but we can anticipate issues in the following areas:

  • Privacy
  • Statics
  • Types

these are discussed in the next few sections.

Privacy


A mixin application may well be declared outside the library that declared the original class. Should this have any effect on who can access members of a mixin application instance? I would hope not. Access to members would be determined based on the library where they were originally declared, exactly as with ordinary inheritance. Strictly speaking, I need not even bring this up, as it follows from the semantics of mixin application, which are determined by the semantics of inheritance in the underlying language.

Statics


Can one use the statics of the original class via the mixin application or not.  Again, the answer follows from the semantics of inheritance. Statics are not inherited in Dart.

Types


What is the type of a mixin application instance? In general, it is a subtype of its superclass, and also supports the methods defined on the mixin.  The mixin name itself however denotes the type of the original class, which has its own superclass and may not be compatible with a particular mixin application.

This would argue for defining mixins as distinct constructs, so that the mixin name would denote a stable type. However, this requires pre-planning. Instead, we might choose to denote the type of the mixin of a class C by a special type expression such as C.mixin.  Since this precludes the use of the word mixin as a member, I suggest instead that we provide a special notation such as C mixin.

Examples: x is C mixin or List<C mixin>.

What about the interfaces a class supports? Does its mixin support them? In general, no, since interface support may rely on inherited functionality. This implies that a mixin application must declare what interfaces it implements explicitly.

Generics are also an issue. If a class has type parameters, its mixin necessarily has identical type parameters. The first example illustrated this point and it raises no syntactic issues, but it does raise constructor-related issues for the implementation, even under our current restrictions.



Extensions


A key question is whether this proposal can be cleanly extended when we relax its restrictions. Restriction (3) by itself is of minor significance and can be dropped easily.  Restriction (4) requires more sophistication in the implementation: super calls must appear to bind dynamically to the actual superclass.  

Restrictions (1) and (2) are more complex.  An mixin clause with multiple identifiers is analogous to Scala’s with clause, which has no such restrictions. However, because of Dart syntax, there is no way to pass the constructor arguments up the inheritance chain.

One approach to addressing the issue is  illustrated below via an variation on our mad maestro example.

class Musical{
  final Instrument instrument;
  Musical(this.instrument);
}

class Aggressive {
 final String aggressionLevel;
 Aggressive(this.aggressionLevel);
}

class Demented {
final disorder;
Demented(this.disorder);)
}

class Maestro  mixin  Demented, Aggressive, Musical extends Person {
 Maestro(name, disorder, degree, instrument) :
    Demented(disorder), Aggressive(degree), Musical(instrument), super(name);
}


The constructor for Maestro explicitly channels the various parameters to the various mixins that are used to define its superclasses.  

The rules given in the restricted proposal still apply: each mixin has its constructor called independently, as does the superclass.  Only the part of the constructor that operates on the mixin itself is called. If the mixin had a superclass, that superconstructor is not run.

If calls to mixin constructors are absent, a default call of the form M(), where M is the name of the mixin, should be inserted by the implementation. This will ensure nulling out any of the mixin’s fields and calling of its default constructor if it exists. Of course, these calls may be optimized away if the mixin has no fields or constructors. Hence, both the behavior and performance of the restricted proposal are preserved.

Finally


There is one aspect of this proposal that is rather irregular:  mixin applications are defined via the mixin clause and always result in anonymous superclasses.

We could introduce a second form of mixin application written as a special kind of class declaration. It has its own name, type parameters, superclass and superinterfaces just like a normal class. Instead of a class body, it specifies a class whose mixin is used, followed by a comma- separated list of constructors, terminated with a semicolon.  

That form can be used to define a class that specifies constructors with appropriate plumbing - providing both the mixin and the superclass with the desired parameters.  We could support this in a simple form with a single class specifying the mixin.

Originally, the constructors were given inside curly braces, but this confused people who thought it was a regular class body.

This syntax closely matches the semantic structure. A mixin application creates a new class, and so is written very much like a class declaration, including its type parameters (if any) extends and implements clauses. The difference is only in the mixin of the new class. In a normal class the class body implicitly defines the class’ mixin. In a mixin application, the class body is replaced by an alternative way of specifying a mixin. The new syntax specifies the class whose mixin will be applied in this case. If necessary, its specifies what type arguments should be passed to the mixin. Optionally, a list of constructors for the mixin application is given.

Here is a variant of the malign musician example that illustrates the above

class Musician  mixin Musical extends Person:
 Musician(name, instrument): Musical(instrument), super(name) ;

class ViolentViolinist extends Musician mixin Aggressive:
 ViolentViolinist(name, instrument): Aggressive(‘very’), super(name, new Violin());


It is not yet clear that this additional form is useful enough to be justified.

Popular posts from this blog

Dart in 2016: The fastest growing programming language at Google, 2nd fastest growing in TIOBE Index

Dart was the fastest growing programming language at Google in 2016 with millions of lines of code written. It also made it to TIOBE Index Top 20 this month (see TIOBE's methodology).

It takes time to build something as ambitious as Dart and, in some ways, Dart is still in its infancy. But we're glad the hard work is starting to pay off.

Many thanks to our amazing community!

We're going to celebrate by ... releasing 1.22 next week (as per our usual 6 week release schedule).

A stronger Dart for everyone

We are constantly asking ourselves:
How do we make developers even more productive when writing Dart apps? We believe that a critical part of the answer to this question is to make strongmode – a sound static type system for Dart – the standard for all Dart developers.

Teams that use Dart to build apps like Soundtrap, AdWords, AdSense, and Greentea say they really enjoy using strong mode features, such as early error detection. In fact, teams that have switched completely to strong mode cite not only early error detection but also better code readability and maintainability as major benefits. We hear this both from small teams and Рeven more so Рfrom large teams with hundreds of developers writing and maintaining millions of lines of Dart code. As Björn Sperber from Soundtrap says,
Strong mode and the smooth integration with IntelliJ is a joy to use and a huge improvement. If you’ve tried out Flutter, you’ve already used strong mode checks from the Dart analyzer.

Given the benefits …

AngularDart 3.0: Easy upgrade, better performance

AngularDart 3.0 is now available. It brings better performance and smaller generated code, while also making you more productive.


Version 3.0 is an evolution: although it has some breaking changes (detailed below), it is a smooth upgrade due to minimal public API adjustments. Most of the progress is under the hood—in code quality, stability, generated code size, performance, and developer experience.

Code quality:
2731 instances of making the framework code more type safe (using sound Dart).The AngularDart framework code size is down by 12%.Many additional style updates to the codebase:Changed to use idiomatic <T> for generic methods.Removed NgZoneImpl, all the code exists in NgZone now.Stability:
Many CSS encapsulation fixes due to update with csslib package.Fixed bugs with IE11.

Performance:
For the Mail sample app, we see 30% faster time-to-interactive (currently 3812 ms on a simulated 3G connection, measured via Lighthouse).Our large app benchmark shows 2x faster render times of…