Dart
  • Get Started
    • Get Dart
    • Dart Tutorials
    • Technical Overview
  • Docs
    • Programmer's Guide
    • API Reference
    • Language Specification
    • Dart Cookbook (Beta)
    • Dart: Up and Running
    • More Books
    • Articles
    • FAQ
  • Tools
    • Dart Editor
    • Pub Package Manager
    • More Tools
  • Resources
    • Code Samples
    • Translations from Dart
    • Try Dart!
    • Presentations
    • Dartisans Videos and Podcast
    • Dart Tips Videos
    • Bugs and Feature Requests
  • Community
    • Contact Us
    • Contributor's Guide
    • More Resources
  • Tweet
On Air

Mixins in Dart

Written by Gilad Bracha
December 2012

This document describes mixins in Dart. The semantics are deliberately restricted in several ways, so as to reduce disruption to our existing implementations, while allowing future evolution toward a full-fledged mixin implementation. This restricted version already provides considerable value.

The intent is to incorporate mixins into Dart in M3.

Contents

  1. Basic concepts
  2. Syntax and semantics
  3. Possible issues
    1. Privacy
    2. Statics
    3. Types
  4. Extensions
  5. Spec changes
    1. 7. Classes
    2. 7.9 Superclasses
    3. 9. Mixins
    4. 14. Libraries and Scripts
    5. 15.3.1 Typedef
    6. 15.4 Interface Types
    7. 16.1.1 Reserved Words

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. Those wishing to delve deeply into the topic can start with this paper: Mixins in Strongtalk.

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.

Based on the notion of function application, one can define function composition. The concept carries through to mixin composition; we define the composition of mixins M1 and M2, written M1 * M2, as: (M1 * M2) |> S = M1 |> (M2 |> S).

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.

Syntax and semantics

Mixins are implicitly 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 declared constructors.
  2. The class’ superclass is Object.
  3. The class contains no super calls.

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

Example 1:

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

typedef DOMElementList<E> = abstract DOMList with Collection<E>;

typedef DOMElementSet<E> = abstract DOMSet with Collection<E>;

// ... 28 more variants

Here, Collection<E> is a normal class that is used to declare a mixin. Both the classes DOMElementList and DOMElementSet are mixin applications. They are defined by the typedef declaration that gives them a name and declares them equal to an application of a mixin to a superclass, given via a with clause. The class is abstract because it does not implement the abstract method newInstance() declared in Collection.

In the above, DOMElementList is effectively Collection mixin |> DOMList, while DOMElementSet 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.

The above examples illustrate one form of mixin application, where the mixin application specifies a mixin and a superclass to which it applies, and provides the application with a name.

In an alternative form, mixin applications appear in the with clause of a class declaration as a comma-separated list of identifiers. All the identifiers must denote classes. In this form, multiple mixins are composed and applied to the superclass named in the extends clause, producing an anonymous superclass. Taking the same examples again, we would have:

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

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

Here, DOMElementList is not the application Collection mixin |> DOMList. Instead, it is a new class whose superclass is such an application. The situation with respect to DOMElementSet is analogous. Note that in each case, the abstract method newInstance() is overridden with an implementation, so these classes can be instantiated directly.

Consider what happens if DOMList has a non-trivial constructor:

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

Each mixin has its own constructor called independently, and so does the superclass. Since a mixin constructor cannot be declared, the call to it can be elided in the syntax; in the underlying implementation, the call can always be placed at the start of the initialization list.

The constructor would set the values for any fields and for the generic type parameters.

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

The second form is a convenient sugar that allows multiple mixins to be mixed into a class without the need to introduce multiple intermediate declarations. For example:

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

class Maestro extends Person with Musical, Aggressive, Demented {
  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.

In reality in this example we’d expect that Demented, Aggressive, and Musical actually have interesting properties that are likely to require state.

Possible issues

Having illustrated the proposal, we now discuss several areas where we can anticipate issues:

  • Privacy
  • Statics
  • Types

Privacy

A mixin application may well be declared outside the library that declared the original class. This should not have any effect on who can access members of a mixin application instance. Access to members is 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 (No) 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.

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.

We may safely ignore this issue for the time being. Because of restriction (2), the type of a mixin does not include additional members beyond those declared by the mixin or shared by all objects. Even if the mixin implements interfaces, the mixin itself must implement the methods of the interfaces, and so it is safe to assume that anyone mixing in the mixin is a subtype of the full type denoted by the mixin’s name.

However, when restriction (2) is lifted, the problem will arise.

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.

Generics are also an issue. If a class has type parameters, its mixin necessarily has identical type parameters.

Extensions

A key question is whether this proposal can be cleanly extended when we relax its restrictions. The implications of lifting restriction (2) have already been discussed. Restriction (3) requires more sophistication in the implementation: super calls must appear to bind dynamically to the actual superclass. This can be achieved either by copying methods that use super, or by making super calls late bound. See section 6 of the paper Mixins in Strongtalk for a discussion of relevant implementation techniques.

Restriction (1) is more complex. Because of Dart syntax, there is no way to pass the constructor arguments up the inheritance chain as part of the with clause (as there is in, say, Scala).

A comprehensive approach to addressing the issue is illustrated below via a 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 extends Person with Musical, Aggressive, Demented {
  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 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.

Spec changes

The rest of this document describes and shows how mixins change the language specification. Changes to existing sections are highlighted in yellow.

7. Classes

Summary of changes: Add the mixin clause, explain its implications on the superclass chain, its effects on superconstructor call, and so on. Basically, sugar for series of anonymous mixin applications.

classDefinition:
     metadata abstract? class identifier typeParameters? (superclass mixins?)? interfaces?
      ‘{‘ (metadata classMemberDefinition)* ‘}’
    ;

mixins:
      with typeList
    ;
…

An abstract class is a class that is explicitly declared with the abstract modifier, either by means of a class declaration or via a type alias for a mixin application.

…

7.9 Superclasses

The superclass of a class C that has a with clause with M1, …, Mk and an extends clause extends S is the application of Mk * .. * M1 to S. If no with clause is specified then the extends clause of a class C specifies its superclass. If no extends clause is specified, then either:

  • C is Object, which has no superclass. OR
  • Class C is deemed to have an extends clause of the form extends Object, and the rules above apply.

It is a compile-time error to specify an extends clause for class Object.

superclass:
      extends type
    ;

It is a compile-time error if the extends clause of a class C includes a type expression that does not denote a class available in the lexical scope of C.

The type parameters of a generic class are available in the lexical scope of the superclass clause, potentially shadowing classes in the surrounding scope. The following code is therefore illegal and should cause a compile-time error:

class T{}
class G<T> extends T {} // Compilation error: Attempt to subclass a type parameter

A class S is a superclass of a class C iff either:

  • S is the superclass of C, or
  • S is a superclass of a class S’ and S’ is a superclass of C.

It is a compile-time error if a class C is a superclass of itself.

9. Mixins

Summary of changes: This section is completely new.

Caveat 1: Mixins are not implemented at this time.

Caveat 2: This section of the spec is work in progress.

A mixin describes the difference between a class and its superclass. A mixin may be declared directly or derived from an existing class declaration.

It is a compile-time error if a declared or derived mixin refers to super. It is a compile-time error if a declared or derived mixin explicitly declares a constructor. It is a compile-time error if a mixin is derived from a class whose superclass is not Object.

These restrictions are temporary. We expect to remove them in later versions of Dart.

The restriction on the use of super avoids the problem of rebinding super when the mixin is bound to difference superclasses.

The restriction on constructors simplifies the construction of mixin applications because the process of creating instances is simpler.

The restriction on the superclass means that the type of a class from which a mixin is derived is always implemented by any class that mixes it in. This allows us to defer the question of whether and how to express the type of the mixin independently of its superclass and superinterface types.

Reasonable answers exist for all these issues, but their implementation is non-trivial.

9.1 Mixin Application

A mixin may be applied to a superclass, yielding a new class. Mixin application may occur when a mixin is mixed into a class declaration via its with clause, or it may occur in the context of a type alias.

mixinApplication:
      qualified_mixins interfaces?’;’
;

A mixin application of the form S with M defines a class C with superclass S.

A mixin application of the form S with M1, …, Mk defines a class C whose superclass is the application of the mixin composition Mk * … * M1 to S.

In both cases above, C declares the same instance members as M. If any of the instance fields of M have initializers, they are executed in the scope of M to initialize the corresponding fields of C. The class C has an implicitly declared nullary generative constructor with no initializer list and no body.

It is a compile-time error if S does not denote a class available in the immediately enclosing scope. It is a compile-time error if M (respectively, any of M1, …, Mk) does (respectively, do) not denote a class or mixin available in the immediately enclosing scope. It is a compile-time error if a well-formed mixin cannot be derived from M (respectively, from each of M1, …, Mk).

Let K be a class declaration with the same superclass and interfaces as C, and the instance members declared by M (respectively M1, …, Mk). It is a static warning if the declaration of K would cause a static warning. It is a compile-time error if the declaration of K would cause a compile-time error.

If, for example, _M_ declares an instance member _im_ whose type is at odds with the type of a member of the same name in _S_, this will result in a static warning just as if we had defined _K _by means of an ordinary class declaration extending _S _with a body that included _im_.

9.2 Mixin Composition

Dart does not directly support mixin composition, but the concept is useful when defining how the superclass of a class with a mixin clause is created.

The composition of two mixins, M1<T1 … TkM1> and M2<U1 … UkM2>, written M1<T1 … TkM1> * M2<U1 … UkM2>, defines an anonymous mixin such that for any class S<V1 … VkS>, the application of M1<T1 … TkM1> to S<V1 … VkS> is equivalent to

typedef Id1<T1 … TkM1, U1 … UkM2, V1 … VkS> = abstract Id2<U1 … UkM2, V1 … VkS> with M1 <T1 … TkM1>;

where Id2 denotes

typedef Id2<U1 … UkM2, V1 … VkS> = abstract S<V1 … VkS> with M2<U1 … UkM2>;

and Id1 and Id2 are unique identifiers that do not exist anywhere in the program.

The classes produced by mixin composition are regarded as abstract because they cannot be instantiated independently. They are only introduced as anonymous superclasses of ordinary class declarations and mixin applications. Consequently, no warning is given if a mixin composition includes abstract members, or incompletely implements an interface.

Mixin composition is associative.

Note that any subset of M1, M2, and S may or may not be generic. For any non-generic declaration, the corresponding type parameters may be elided, and if no type parameters remain in the derived declarations Id1 and/or Id2 then those declarations need not be generic either.

14. Libraries and Scripts

A Dart program consists of one or more libraries, and may be built out of one or more compilation units. A compilation unit may be a library or a part.

A library consists of (a possibly empty) set of imports, and a set of top level declarations. A top level declaration is either a class, a type alias declaration, a function, or a variable declaration.

topLevelDefinition:
       classDefinition
    | typeAlias
    | external functionSignature
    | external getterSignature
    | external setterSignature
    | functionSignature functionBody
    | returnType? getOrSet identifier formalParameterList functionBody
    | (final | const) type? staticFinalDeclarationList ‘;’
    | variableDeclaration ‘;’
    | ;

15.3.1 Typedef

Summary of changes: Generalize to support mixin applications.

A type alias declares a name for a type expression or mixin application.

typeAlias:
     metadata
typedef typeAliasBody
     ;

typeAliasBody:
     identifier typeParameters? `=’
abstract? mixinApplication
     | functionTypeAlias
     ;

functionTypeAlias:
     functionPrefix typeParameters? formalParameterList ‘;’
     ;

functionPrefix:
     returnType? identifier
     ;

The effect of a type alias of the form typedef T id (T1 p1, …, Tn pn, [Tn+1 pn+1, …, Tn+k pn+k]) declared in a library L is to introduce the name id into the scope of L, bound to the function type (T1, …, Tn, [Tn+1 pn+1, …, Tn+k pn+k]) → T. The effect of a type alias of the form typedef T id (T1 p1, …, Tn pn, {Tn+1 pn+1, …, Tn+k pn+k}) declared in a library L is to introduce the name id into the scope of L, bound to the function type (T1, …, Tn, {Tn+1 pn+1, …, Tn+k pn+k]}) → T. In either case, if no return type is specified, it is taken to be dynamic. Likewise, if a type annotation is omitted on a formal parameter, it is taken to be dynamic.

The effect of a type alias of the form typedef C = M; or the form typedef C<T1, …, Tn> = M; is to introduce the name C into the scope of L, bound to the class defined by the mixin application M. The name of the class is also set to C. Iff the type alias body includes the built-in identifier abstract, the class being defined is an abstract class.

It is a compile-time error if any default values are specified in the signature of a function type alias. It is a compile-time error if a typedef refers to itself via a chain of references that does not include a class type.

15.4 Interface Types

Summary of changes: Add the effects of mixin clause on subtyping.

The implicit interface of class I is a direct supertype of the implicit interface of class J iff:

  • I is Object, and J has no extends clause.
  • I is listed in the extends clause of J.
  • I is listed in the implements clause of J.
  • I is listed in the with clause of J.
  • J is a mixin application of the mixin of I.

16.1.1 Reserved Words

Summary of changes: Add the reserved word with (turning an evil keyword into a good one!).

A reserved word may not be used as an identifier; it is a compile-time error if a reserved word is used where an identifier is expected.

assert, break, case, catch, class, const, continue, default, do, else, extends, false, final, finally, for, if, in, is, new, null, return, super, switch, this, throw, true, try, var, void, while, with.

Popular on this site

  • Web UI
  • Performance
  • Language tour & library tour
  • Code samples
  • Tutorials & Code Lab
  • Cookbook

More resources

  • Try Dart!
  • Translations from Dart
  • Dart bugs and feature requests
  • Pub packages

Community

  • Mailing lists
  • G+ community
  • G+ announcement group
  • Stack Overflow

Dart is an open-source project with contributors from Google and elsewhere.

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code samples are licensed under the BSD License.

Terms of Service — Privacy Policy