OLD | NEW |
(Empty) | |
| 1 # Dart 2.0 Mixins |
| 2 |
| 3 Author: [lrn@google.com](mailto:lrn@google.com) |
| 4 |
| 5 Version 0.6 (2017-06-14) |
| 6 |
| 7 Status: Mostly designed, ready for external comments. |
| 8 |
| 9 ## Proposal |
| 10 This proposal introduces a new syntax for declaring mixins, separate from derivi
ng a mixin from a class declaration. It expects to deprecate and remove the abil
ity to derive a mixin from a class declaration, but doesn't require it. |
| 11 |
| 12 |
| 13 ## Background |
| 14 |
| 15 Dart 1 mixins have the following features: |
| 16 |
| 17 * Derived from a class declaration. |
| 18 * Applied to a superclass to create a new class. |
| 19 * May be derived from class with super-class, then application must be on clas
s implementing super-class interface. |
| 20 * May have super-invocations if the mixin class has a super-class. |
| 21 * Cannot have generative constructors. |
| 22 * Mixin application forwards non-const constructors. |
| 23 |
| 24 There are a number of problems with this approach, especially the super-class co
nstraints. |
| 25 |
| 26 * The super-calls (`super.foo()`) are not statically guaranteed to hit a match
ing method. There is no specified static check of a mixin application that ensur
es that any mixed-in methods containing a super-call will actually hit an existi
ng method. If the superclass is abstract, the super-call may fail dynamically. |
| 27 * Deriving a mixin from a class means that moving a method from the class to i
ts superclass is a breaking change, not just a refactoring. Many class changes t
hat are generally considered safe in OO languages are breaking if the class is u
sed as a mixin. For that reason, we have guidelines saying not to use a class as
a mixin unless it's documented as being intended as such (the creator has opted
in to the extra constraints). |
| 28 * The super-class constraint on a "mixin" is derived from the `extends` clause
which only allows a single type. There is no way to specify two requirements, a
nd users trying to do so ends up with code that doesn't work like they expect. |
| 29 * A mixin derived from a mixin-application might have a different super-class
than expected. |
| 30 * Nobody understands how the super-feature actually works (http://dartbug.com/
29758, http://dartbug.com/25765) |
| 31 * When any class can be used as a mixin, there are local optimizations that ca
nnot be performed (like DDC not being able to detect that a private field isn't
overridden). Also, if a class that is not intended as a mixin is used as a mixin
, many otherwise safe refactorings (e.g., moving a method to a superclass) will
be breaking. |
| 32 |
| 33 |
| 34 ### Mixin Declaration |
| 35 |
| 36 To avoid some of the problems mentioned above, we introduce a *mixin declaration
syntax* separate from class declarations: |
| 37 |
| 38 *mixinDeclaration* : *metadata*? 'mixin' *identifier* *typeParameters*? <br> |
| 39 ('requires' *types*)? ('implemen
ts' *types*)? '{' <em>mixinMember</em>* '}' |
| 40 |
| 41 The `mixinMember` production allows the same instance or static members that a c
lass would allow, but no constructors (for now). |
| 42 |
| 43 The `mixin` word will have to be at least a **built-in identifier** to avoid par
sing ambiguities. It does not need to be a reserved word. |
| 44 |
| 45 It might be possible to just use `mixin` as a contextual keyword, but it would r
equire some look-ahead to determine whether an occurrence is a type named `mixin
` or a mixin declaration, and we would like to discourage the former anyway. |
| 46 |
| 47 #### Meaning |
| 48 |
| 49 A mixin declaration introduces a mixin and an *interface*, but *not a class*. Th
e mixin derived from a mixin declaration contains all the non-static members dec
lared by the mixin, just as the mixin derived from a class currently does. |
| 50 |
| 51 The interface of `mixin A requires B, C implements D, E { body }`, which has the
same name as the mixin (`A` here), is equivalent to the interface that would be
derived from the class declaration: |
| 52 ```dart |
| 53 abstract class A implements B, C, D, E { body' } |
| 54 ``` |
| 55 where `body'` contains abstract declarations corresponding to the instance membe
rs of `body` of the mixin `A`. |
| 56 |
| 57 The `requires` keyword on the mixin declaration is open for discussion. It's bet
ter than `super`, but fairly long. Another option is `on`. |
| 58 |
| 59 It's a static warning (strong-mode error) if an instance method in a mixin body
has a super-access (`super.foo`, `super.foo()`, `super + bar`, etc.) that is |
| 60 |
| 61 * not declared by at least one of the *types* in the mixin's `requires` declar
ation, or |
| 62 * not type-compatible with at least one such declaration from a `requires` typ
e. |
| 63 |
| 64 The mixin cannot be marked as `abstract`. |
| 65 All mixins are effectively abstract because they don't need to implement the mem
bers of the required superclass types. |
| 66 We could say that a mixin must implement all other members than the ones declare
d by the required superclass types, and then allow the declaration to be marked
as `abstract` if it doesn't. |
| 67 It would still require mixin applications to be marked independently, so there i
s no large advantage to marking the mixin itself as non-abstract. |
| 68 |
| 69 |
| 70 ### Mixin application |
| 71 |
| 72 Mixin application syntax is unchanged. A mixin application `S with M` introduces
a *class* with superclass `S`, implementing `M` and with copies of all the non-
static members of `M`. As usual code in the copies of members retain the static
scope of their original declaration. |
| 73 |
| 74 In a mixin application declaration `class C = S with M;`, the class is named `C`
, otherwise it has a fresh name (it's effectively anonymous since nobody knows i
ts name, but it needs a name because constructor names include the class name an
d forwarding constructors need to have names). |
| 75 |
| 76 Multiple applications introduce a chain of classes, so `S with M1, M2` has an an
onymous `S with M1` application class as superclass and applies `M2` to that. |
| 77 |
| 78 Mixin application semantics is mostly unchanged, except that it's a static warni
ng (strong mode error) to apply a mixin to a class that doesn't implement *all*
the `requires` types of the mixin declaration. |
| 79 |
| 80 All non constructors of the superclass causes a forwarding constructor to be add
ed to the mixin application with the same arguments. |
| 81 |
| 82 |
| 83 #### Super-calls of mixin applications must be valid |
| 84 |
| 85 Currently, the specification doesn't warn at compile-time if a `super`-invocatio
n targets an abstract method. This allows declaring a mixin that extends an abst
ract interface, but it also means that mistakes are only runtime-errors. We want
to fix that. |
| 86 |
| 87 * One solution is to *require the superclass of a mixin application to be non-
abstract*. This would ensure that all `super`-invocations in mixin applications
are valid. The mixin declaration only allows `super`-invocations declared by the
ir `requires` constraints and the mixin application requires the superclass to s
atisfy those constraints, and by also being non-abstract, there must be an actua
l implementation of the superclass method. |
| 88 * Alternatively, we only make it a compile-time error if a mixin application i
ntroduces a method on the mixin application class which contains a super-access
(<code>super.<em>x</em></code>, <code>super.<em>x</em>(...)</code>, <code>super
<em>op</em> arg</code>, etc), and the actual superclass of the mixin application
doesn't have a non-abstract implementation of a member with *that* name. |
| 89 (The compile-time error applies to the mixed-in method with the super-call, so a
lazy-compile-time-error implementation can fail to compile that method only.) |
| 90 |
| 91 Obviously, if the superclass is not abstract, this check won't be necessary. |
| 92 |
| 93 * As a third alternative, we can add syntax to explicitly declare and expose t
he super constraints. Syntax could be like an abstract method that is marked as
"super", perhaps one of: |
| 94 |
| 95 ``` |
| 96 int super.foo(int bar); |
| 97 super int foo(int bar); |
| 98 super { |
| 99 int foo(int bar); // and perhaps multiple declaration in the block. |
| 100 } |
| 101 super foo; // comma separated list of just the names. |
| 102 super { foo }; // ditto. |
| 103 ``` |
| 104 |
| 105 |
| 106 The block approach is unlike anything else we do in Dart. The `super.foo` declar
ation is also different from other syntax and would complicate the grammar more
than just a prefixed `super`, but if it's easier to understand for the user, it'
s probably worth it. |
| 107 |
| 108 Just mentioning the super-member by name is shorter, and since the required supe
r-types are specified elsewhere, it should be sufficient, but it's not as readab
le as a full declaration. |
| 109 |
| 110 With any of these syntaxes, the mixin declaration explicitly declares which supe
r-calls it uses, so the user can be aware of it. |
| 111 |
| 112 On the other hand, that means duplication (you already make the super-call, now
you also repeat it as a declaration) and it still locks you into not being able
to do more super-calls in the future without breaking things. |
| 113 |
| 114 If the constraints are handled entirely structurally, and don't need to be linke
d to a declared required superclass constraint, it would allow mixins to be used
on arbitrary objects that satisfy the constraint, but that would also be the on
ly place in Dart where we have a structural constraint on classes. I would recom
mend only allowing references to members of the already required superclasses. |
| 115 |
| 116 |
| 117 The second and third options are the more permissive ones, but that also comes w
ith a cost of maintainability and usability. If a mixin adds a new super-invocat
ion, then it may break existing mixin applications. It's not possible to see the
actual requirements of the mixin from its type signature alone - in the third o
ption, a new syntax is introduced to represent the requirement. |
| 118 |
| 119 If the requirement is just that the superclass is non-abstract (first option), t
here are no hidden or fragile constraints in the relation between the mixin and
the superclass. For that reason, I recommend we pick the that approach (require
that the superclass of a mixin application is not abstract) and potentially loos
en it later if necessary - adding explicit super-requirements would then allow a
bstract superclasses that satisfy the requirements, not writing anything still w
orks with a non-abstract superclass. |
| 120 |
| 121 We should check whether that is a problem for existing code that has an abstract
superclass for a mixin application. |
| 122 |
| 123 In either case, this requirement is new. The current specification doesn't have
it, instead it just silently accepts a mixin application on an abstract supercla
ss that doesn't actually implement the super-member, and the call will fail at r
untime. |
| 124 |
| 125 |
| 126 ### Potential future changes |
| 127 |
| 128 |
| 129 #### Deprecating derived mixins |
| 130 |
| 131 In the future, preferably already in Dart 2.0, we'll remove the ability to deriv
e a mixin from a class declaration. |
| 132 |
| 133 The requires existing code to be rewritten. The rewrite is simple: |
| 134 |
| 135 |
| 136 ```dart |
| 137 class FooMixin extends S implements I { |
| 138 members; |
| 139 } |
| 140 ``` |
| 141 |
| 142 becomes |
| 143 |
| 144 ```dart |
| 145 mixin FooMixin requires S implements I { |
| 146 members; |
| 147 } |
| 148 ``` |
| 149 |
| 150 If a class is *actually* used as both a class and a mixin, the mixin needs to be
extracted: |
| 151 |
| 152 ```dart |
| 153 class Foo extends S implements I { // Used as mixin *and* class |
| 154 members; |
| 155 } |
| 156 ``` |
| 157 |
| 158 becomes |
| 159 |
| 160 ```dart |
| 161 class Foo = S with FooMixin { |
| 162 public static members |
| 163 } |
| 164 mixin FooMixin requires S implements I { |
| 165 instance members (references to statics prefixed with "Foo.") |
| 166 } |
| 167 // All uses of "with Foo" changed to "with FooMixin". |
| 168 ``` |
| 169 |
| 170 Apart from public static members (which are rare) this is basically a two line r
ewrite locally, and then finding the uses of the class. |
| 171 |
| 172 Optionally, we can also allow mixins to be used as classes (instead of the other
way around), so `class C extends Mixin { … }` is equivalent to `class C extends
Object with Mixin { … }`. |
| 173 |
| 174 |
| 175 #### Forward const constructors |
| 176 |
| 177 A mixin application forwards generative constructors as non-const, even if the s
uperclass constructor is a const constructor. That makes some use-cases for mixi
ns impossible. |
| 178 |
| 179 We could make the forwarding constructors const as well when it is safe. An appr
oximation of that could be when the mixin declares no fields. This is not necess
arily a good idea, since it would break getter/field symmetry and prevent a mixi
n from changing a getter to a final field. |
| 180 |
| 181 |
| 182 #### Extending mixins |
| 183 |
| 184 With separate syntax for mixins, we are open to adding more capabilities without
needing it to also work for classes. |
| 185 |
| 186 Options are: |
| 187 |
| 188 * Composite mixins (mixin can `extend` another mixin, application applies both
). |
| 189 * Constructors (mixin constructors don't forward to the superclass, only to a
super-mixin). If a mixin has generative constructors (and even const ones), ther
e will be no automatic constructor forwarding because the mixin-application clas
s would need to call the mixin constructor explicitly. It can be omitted if the
mixin has a no-arguments constructor, which it will then have by default. |
| 190 |
| 191 |
| 192 ### Revisions |
| 193 |
| 194 v0.5 (2017-06-12) Initial version |
| 195 |
| 196 v0.6 (2017-06-14) Say `mixin` must be built-in identifier. |
OLD | NEW |