OLD | NEW |
(Empty) | |
| 1 # Dart Language and Library Newsletter |
| 2 2017-08-25 |
| 3 @floitschG |
| 4 |
| 5 Welcome to the Dart Language and Library Newsletter. |
| 6 |
| 7 ## Under Active Development |
| 8 This section provides updates to the areas we are actively working on as part of
long-running efforts. Many of these sections have a more detailed explanation i
n a previous newsletter. |
| 9 |
| 10 ### Better Organization |
| 11 Goal: collect and organize the documents (proposals, ...) the Dart language team
produces. |
| 12 |
| 13 Since last time, a proposal for an improvement to mixins has been added: |
| 14 https://github.com/dart-lang/sdk/blob/master/docs/language/informal/mixin-declar
ation.md |
| 15 |
| 16 See below for a summary of this proposal. |
| 17 |
| 18 ## Mixins |
| 19 In Dart every class that satisfies some restrictions can be used as a mixin. Whi
le this approach is quite elegant, it is not how developers think of mixins. Thi
s leads to errors, makes normal classes abnormally brittle (since, in theory, mi
nor changes could break their mixin shape) and makes it hard to lift restriction
s (such as compositions and requirements on super interfaces). |
| 20 |
| 21 We are therefore looking at making mixins separate from normal classes. Instead
of introducing a mixin with `class`, one should use `mixin` instead: |
| 22 |
| 23 Old: |
| 24 ``` dart |
| 25 class M1 { |
| 26 int foo() => 499; |
| 27 } |
| 28 ``` |
| 29 |
| 30 New: |
| 31 ``` dart |
| 32 mixin M1 { |
| 33 int foo() => 499; |
| 34 } |
| 35 ``` |
| 36 |
| 37 Since mixins now have their own syntax, we can fix some of the problems we encou
ntered with the `class` syntax. Specifically, invoking `super` methods inside a
mixin was a problem. Take for example, a mixin that wants to be mixed in on top
of a class that implements `A`, so that it can wrap calls to `A`'s `foo` method.
The current specification (which has only been implemented in the VM) uses the
`extends` clause to enforce this requirement: |
| 38 |
| 39 ``` dart |
| 40 class A { |
| 41 int foo() => 499; |
| 42 } |
| 43 |
| 44 // Old style mixin: |
| 45 class M1 extends A { |
| 46 int foo() => super.foo() + 1; |
| 47 } |
| 48 |
| 49 class B extends A with M1 {} |
| 50 ``` |
| 51 |
| 52 The `extends` approach has multiple problems: |
| 53 1. it's unintuitive for our users, |
| 54 2. `A.foo` has to be concrete (since `M1.foo` uses it with a `super.foo` call). |
| 55 3. `M1` can only depend on one supertype, |
| 56 4. there is no obvious way to compose mixins (since `extends` is already taken). |
| 57 5. there is no enforcement, that the class that mixes in `M1` actually has a non
-abstract implementation of `A`. |
| 58 6. requiring supertypes that have constructors is not possible, since mixins cur
rently must not have constructors. |
| 59 |
| 60 The following code snippets illustrate some of these problems with examples: |
| 61 |
| 62 ``` dart |
| 63 abstract class A { |
| 64 int foo(); |
| 65 } |
| 66 |
| 67 class M1 extends A { |
| 68 int foo() => super.foo() + 1; // ERROR: super.foo() is abstract. |
| 69 } |
| 70 ``` |
| 71 |
| 72 ``` dart |
| 73 abstract class A { |
| 74 int foo(); |
| 75 } |
| 76 abstract class B { |
| 77 String bar(); |
| 78 } |
| 79 |
| 80 // C does *not* know AB (below), nor M1 (also below). |
| 81 class C implements A, B { |
| 82 ... |
| 83 } |
| 84 |
| 85 // Workaround class to make it possible to require multiple super classes. |
| 86 class AB implements A, B { |
| 87 int foo() => null; |
| 88 String bar() => null; |
| 89 } |
| 90 |
| 91 class M1 extends AB { |
| 92 int foo() => super.foo() + 1; |
| 93 String bar() => super.bar() + "_string"; |
| 94 } |
| 95 |
| 96 // Intermediate workaround class that adds `AB` on top of C. |
| 97 class _C extends C implements AB {} |
| 98 |
| 99 // D needs to extend _C since M1 requires AB as superclass. |
| 100 class D extends _C with M1 { |
| 101 ... |
| 102 } |
| 103 ``` |
| 104 |
| 105 ``` dart |
| 106 class A { |
| 107 int foo() => 499; |
| 108 } |
| 109 |
| 110 class M1 extends A { |
| 111 int foo() => super.foo() + 1; |
| 112 } |
| 113 |
| 114 abstract class B implements A {} |
| 115 |
| 116 class C extends B with M1 {} // No error, since `B` *implements* A. |
| 117 ``` |
| 118 |
| 119 All of these problems are easily solved with the specialized `mixin` syntax. Ins
tead of using `extends` to specify the supertype requirements, `mixin` declarati
ons use `requires`, which can take multiple supertypes. |
| 120 |
| 121 Furthermore, since mixins are separate from classes, it is perfectly fine to do
`super` invocations (inside the mixin) to abstract methods. Only at mixin time d
oes the language check that the required interfaces are fully implemented (or at
least the methods that are used by the mixin). |
| 122 |
| 123 ``` dart |
| 124 abstract class A { |
| 125 int foo(); |
| 126 } |
| 127 |
| 128 class B { |
| 129 String bar() => "bar"; |
| 130 } |
| 131 |
| 132 abstract class C extends B implements A { |
| 133 } |
| 134 |
| 135 mixin M1 requires A, B { |
| 136 int foo() => super.foo() + 1; |
| 137 String bar() => super.bar() + "_string"; |
| 138 } |
| 139 |
| 140 class D extends C with M1 {} // ERROR: foo is not implemented. |
| 141 |
| 142 class C2 extends B implements A { |
| 143 int foo() => 42; |
| 144 } |
| 145 |
| 146 class D2 extends C2 with M1 {} // OK. |
| 147 } |
| 148 ``` |
| 149 |
| 150 The current proposal doesn't add any additional features yet, but it lends itsel
f to allowing composition with `extends`, and supporting constructors. |
| 151 |
| 152 ## Corner Cases |
| 153 A lot of the work the language team does is to discuss and solve corner cases th
at most users never encounter. In this section we show two of the more interesti
ng ones. |
| 154 |
| 155 |
| 156 ### Inference vs Manual Types - Part 1. |
| 157 Dart 2.0 strongly relies on type inference to make programming easier. At the sa
me time, Dart still allows programmers to write types by hand. This section expl
ores some interesting interactions between written and inferred types. |
| 158 |
| 159 The following example demonstrates how explicitly writing a type can break a pro
gram; or make it work. |
| 160 |
| 161 ``` dart |
| 162 void foo(List<int> arg) { ... } |
| 163 |
| 164 var x = [1, 2]; // Inferred as `List<int>`. |
| 165 List<Object> y = [1, 2]; |
| 166 print(x.runtimeType); // => List<int>. |
| 167 print(y.runtimeType); // => List<Object>. |
| 168 foo(x); |
| 169 foo(y); // Error. |
| 170 x.add("string"); // Error. |
| 171 y.add("string"); // OK. |
| 172 ``` |
| 173 |
| 174 In the case of `x` the type inference infers that `[1, 2]` is a list of `int`. T
he variable `y`, on the other hand, is typed as `List<Object>` and that type is
*pushed down* to the expression `[1, 2]`. Those two list-literals have thus two
completely different dynamic types despite having the same textual representatio
n. |
| 175 |
| 176 Both cases are useful: the more precise `List<int>` is used to call `foo`, where
as, `y` (being a `List<Object>`) can store objects with types different than `in
t`. |
| 177 |
| 178 Writing a type, different than the one that type inference finds, can thus chang
e the behavior in significant ways (and not necessarily in bad ways). Interestin
gly, writing the *exact same type* may also lead to different behavior. That is,
given a variable declaration `var x = someExpr`, there are cases where `Type x
= someExpr`, with `Type` being the type that would have been inferred for `someE
xpr`, leads to a different program. |
| 179 |
| 180 I have split this section into two parts, so that developers familiar with stron
g mode inference have the opportunity to search for one of these expressions. Th
e second part is just after the next section. |
| 181 |
| 182 ### Function Types and Covariant Generics |
| 183 To make life easier for developers, Dart allows covariant generics. In short, Da
rt says that a `List<Apple>` is a `List<Fruit>`. As long as the value is only re
ad out of the list, that is perfectly fine. However, after assigning a `List<App
le>` to a `List<Fruit>` we cannot simply add a `Banana` to the list. Statically,
adding a Banana is fine (after all, it's a `List<Fruit>`), but dynamically Dart
adds a check to ensure that this can't happen. |
| 184 |
| 185 ``` dart |
| 186 List<Apple> apples = <Apple>[new Apple()]; |
| 187 List<Fruit> fruits = apples; // OK because of covariant generics. |
| 188 fruits.add(new Banana()); // Statically ok, but dynamic error. |
| 189 ``` |
| 190 |
| 191 In practice this works fine, since most generic types are used as "out" types. T
he exceptions, such as `Converter`s (where the first generic is used as input) a
re fortunately very rare (although quite annoying when users hit them). |
| 192 |
| 193 As mentioned above, Dart adds checks whenever the input might not be the correct
type to ensure that the heap stays sound. For example, the `add` method is conc
eptually compiled to: |
| 194 |
| 195 ``` dart |
| 196 void add(Object o) { |
| 197 if (o is! T) throw new TypeError("$o is not of type $T"); |
| 198 /* Add `o` to the list. */ |
| 199 } |
| 200 ``` |
| 201 |
| 202 Some of these checks are easy to find, but some are much trickier: |
| 203 |
| 204 ``` dart |
| 205 class A<T> { |
| 206 Function(T) fun; |
| 207 } |
| 208 |
| 209 main() { |
| 210 A<int> a = new A<int>(); |
| 211 a.fun = (int x) => x + 3; |
| 212 A<Object> a2 = a; // #0 |
| 213 var f = a2.fun; // #1, static type: Function(Object). |
| 214 print(f('some_string')); // #2 |
| 215 } |
| 216 ``` |
| 217 |
| 218 At `#0` the `A<int>` is assigned to an `A<Object>`. Because of covariant generic
s, this assignment is allowed. The static type of `a2.fun` is `Function(Object)`
, which is inferred for `f` at `#1`. However, the function that was stored in `a
.fun` is a function that only takes integers. As such, the call at line `#2` mus
t not succeed, and Dart must insert checks to ensure that the `x + 3` is never i
nvoked with a String. |
| 219 |
| 220 The safest and easiest choice would be to insert a check for `int` in the closur
e `(int x) => x + 3`. However, that would be too inefficient. Every closure woul
d need to check its arguments, even if it is never used in a situation where it
might receive arguments of the wrong type. Dart implementations could cheat, and
store a hidden bit on closures that enables argument checks or not, but things
would get complicated fast. |
| 221 |
| 222 There isn't any place to add checks inside `A`. Clearly, users must be able to a
ccess the member `fun`, and `A` itself can't know how (and as which type) the re
turned member will be used. |
| 223 |
| 224 This only leaves the assignment in line `#1`. Dart has to insert a check here th
at ensures that the closure that is returned from `A.fun` has the correct type.
This is exactly, what Dart 2.0 will do. A dynamic check for the assignment ensur
es that the dynamic type of `a2.fun` matches the inferred type of `f`. Since, in
this example, `int Function(int)` (the type of the closure) is not assignable t
o `dynamic Function(Object)` (the inferred type of `f`), the line `#2` is never
reached. |
| 225 |
| 226 Afaik this is the only case where a declaration of the form `var x = expr;` fail
s dynamically when assigning the evaluated expression to the value of the *infer
red* type. It's not that the type inference did a bad job, but that there was no
earlier place to inserts the checks that Dart has to do to ensure heap soundnes
s. |
| 227 |
| 228 Note that this check hasn't been added to DDC, yet. |
| 229 |
| 230 ### Inference vs Manual Types - Part 2 |
| 231 In part 1 of this section we finished with a claim that there are expressions fo
r which writing the inferred type at their declaration point yields a different
program than if we had left it off. |
| 232 |
| 233 The easiest way to get such an expression is to use the fact that Dart uses down
and upwards inference, but doesn't iterate the inference process. |
| 234 |
| 235 ``` dart |
| 236 var x = [[499], [false]]; // Inferred to be a List<List<Object>>. |
| 237 List<List<Object>> y = [[499], [false]]; |
| 238 |
| 239 x[0].add("str"); // Error. |
| 240 y[0].add("str"); // OK. |
| 241 ``` |
| 242 |
| 243 For `x`, the nested list `[499]` is inferred as `List<int>`, and `[false]` is in
ferred as `List<bool>`. The least upper bound of these two types is `List<Object
>` and the surrounding list (and thus `x`) is inferred to be `List<List<Object>>
`. |
| 244 |
| 245 For `y`, the `List<List<Object>>` type is used in the downwards inference to typ
e the right-hand side of the assignment. The whole outer list is typed as `List<
List<Object>>` (similar to the one for `x`) without even looking at the elements
. The type is then continued to be pushed to the entries of the list. Instead of
using up inference to infer that `[499]` is a `List<int>` the context already p
rovides the type that this expression should have: `List<Object>`. All entries o
f the outer list are forced to be `List<Object>`. |
| 246 |
| 247 When the program later tries to add a string (`"str"`) to the first list-entries
, the one that was inferred to be a `List<int>` has to dynamically reject that v
alue, whereas the one that was forced to be `List<Object>` succeeds. |
| 248 |
OLD | NEW |