Discussion:
[dart-misc]
'Florian Loitsch' via Dart Misc
2017-08-25 15:27:15 UTC
Permalink
Github link:
https://github.com/dart-lang/sdk/blob/8482c00ec3f44bb43af182eb0410d371bb81de6b/docs/newsletter/20170825.md

Earlier newsletters:
https://github.com/dart-lang/sdk/tree/master/docs/newsletter

Dart Language and Library Newsletter

Welcome to the Dart Language and Library Newsletter.
<https://gist.github.com/floitschG/fec37b8650bef684a8b072d441adc9c2#under-active-development>Under
Active Development

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 in a previous newsletter.
<https://gist.github.com/floitschG/fec37b8650bef684a8b072d441adc9c2#better-organization>Better
Organization

Goal: collect and organize the documents (proposals, ...) the Dart language
team produces.

Since last time, a proposal for an improvement to mixins has been added:
https://github.com/dart-lang/sdk/blob/master/docs/language/informal/mixin-declaration.md

See below for a summary of this proposal.
<https://gist.github.com/floitschG/fec37b8650bef684a8b072d441adc9c2#mixins>
Mixins

In Dart every class that satisfies some restrictions can be used as a
mixin. While this approach is quite elegant, it is not how developers think
of mixins. This leads to errors, makes normal classes abnormally brittle
(since, in theory, minor changes could break their mixin shape) and makes
it hard to lift restrictions (such as compositions and requirements on
super interfaces).

We are therefore looking at making mixins separate from normal classes.
Instead of introducing a mixin with class, one should use mixin instead:

Old:

class M1 {
int foo() => 499;
}

New:

mixin M1 {
int foo() => 499;
}

Since mixins now have their own syntax, we can fix some of the problems we
encountered 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:

class A {
int foo() => 499;
}
// Old style mixin:class M1 extends A {
int foo() => super.foo() + 1;
}
class B extends A with M1 {}

The extends approach has multiple problems:

1. it's unintuitive for our users,
2. A.foo has to be concrete (since M1.foo uses it with a super.foo call).
3. M1 can only depend on one supertype,
4. there is no obvious way to compose mixins (since extends is already
taken).
5. there is no enforcement, that the class that mixes in M1 actually has
a non-abstract implementation of A.
6. requiring supertypes that have constructors is not possible, since
mixins currently must not have constructors.

The following code snippets illustrate some of these problems with examples:

abstract class A {
int foo();
}
class M1 extends A {
int foo() => super.foo() + 1; // ERROR: super.foo() is abstract.
}

abstract class A {
int foo();
}abstract class B {
String bar();
}
// C does *not* know AB (below), nor M1 (also below).class C implements A, B {
...
}
// Workaround class to make it possible to require multiple super
classes.class AB implements A, B {
int foo() => null;
String bar() => null;
}
class M1 extends AB {
int foo() => super.foo() + 1;
String bar() => super.bar() + "_string";
}
// Intermediate workaround class that adds `AB` on top of C.class _C
extends C implements AB {}
// D needs to extend _C since M1 requires AB as superclass.class D
extends _C with M1 {
...
}

class A {
int foo() => 499;
}
class M1 extends A {
int foo() => super.foo() + 1;
}
abstract class B implements A {}
class C extends B with M1 {} // No error, since `B` *implements* A.

All of these problems are easily solved with the specialized mixin syntax.
Instead of using extends to specify the supertype requirements, mixin
declarations
use requires, which can take multiple supertypes.

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 does the language check that the required interfaces are fully
implemented (or at least the methods that are used by the mixin).

abstract class A {
int foo();
}
class B {
String bar() => "bar";
}
abstract class C extends B implements A {
}

mixin M1 requires A, B {
int foo() => super.foo() + 1;
String bar() => super.bar() + "_string";
}
class D extends C with M1 {} // ERROR: foo is not implemented.
class C2 extends B implements A {
int foo() => 42;
}
class D2 extends C2 with M1 {} // OK.
}

The current proposal doesn't add any additional features yet, but it lends
itself to allowing composition with extends, and supporting constructors.
<https://gist.github.com/floitschG/fec37b8650bef684a8b072d441adc9c2#corner-cases>Corner
Cases

A lot of the work the language team does is to discuss and solve corner
cases that most users never encounter. In this section we show two of the
more interesting ones.
<https://gist.github.com/floitschG/fec37b8650bef684a8b072d441adc9c2#inference-vs-manual-types---part-1>Inference
vs Manual Types - Part 1.

Dart 2.0 strongly relies on type inference to make programming easier. At
the same time, Dart still allows programmers to write types by hand. This
section explores some interesting interactions between written and inferred
types.

The following example demonstrates how explicitly writing a type can break
a program; or make it work.

void foo(List<int> arg) { ... }
var x = [1, 2]; // Inferred as `List<int>`.List<Object> y = [1,
2];print(x.runtimeType); // => List<int>.print(y.runtimeType); // =>
List<Object>.foo(x);foo(y); // Error.
x.add("string"); // Error.
y.add("string"); // OK.

In the case of x the type inference infers that [1, 2] is a list of int.
The 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
representation.

Both cases are useful: the more precise List<int> is used to call foo,
whereas, y (being a List<Object>) can store objects with types different
than int.

Writing a type, different than the one that type inference finds, can thus
change the behavior in significant ways (and not necessarily in bad ways).
Interestingly, 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 someExpr, leads to a different program.

I have split this section into two parts, so that developers familiar with
strong mode inference have the opportunity to search for one of these
expressions. The second part is just after the next section.
<https://gist.github.com/floitschG/fec37b8650bef684a8b072d441adc9c2#function-types-and-covariant-generics>Function
Types and Covariant Generics

To make life easier for developers, Dart allows covariant generics. In
short, Dart says that a List<Apple> is a List<Fruit>. As long as the value
is only read out of the list, that is perfectly fine. However, after
assigning a List<Apple>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.

List<Apple> apples = <Apple>[new Apple()];List<Fruit> fruits = apples;
// OK because of covariant generics.
fruits.add(new Banana()); // Statically ok, but dynamic error.

In practice this works fine, since most generic types are used as "out"
types. The exceptions, such as Converters (where the first generic is used
as input) are fortunately very rare (although quite annoying when users hit
them).

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 conceptually compiled to:

void add(Object o) {
if (o is! T) throw new TypeError("$o is not of type $T");
/* Add `o` to the list. */
}

Some of these checks are easy to find, but some are much trickier:

class A<T> {
Function(T) fun;
}
main() {
A<int> a = new A<int>();
a.fun = (int x) => x + 3;
A<Object> a2 = a; // #0
var f = a2.fun; // #1, static type: Function(Object).
print(f('some_string')); // #2
}

At #0 the A<int> is assigned to an A<Object>. Because of covariant
generics, 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 must not succeed, and Dart must insert checks to ensure
that the x + 3 is never invoked with a String.

The safest and easiest choice would be to insert a check for int in the
closure (int x) => x + 3. However, that would be too inefficient. Every
closure would 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.

There isn't any place to add checks inside A. Clearly, users must be able
to access the member fun, and A itself can't know how (and as which type)
the returned member will be used.

This only leaves the assignment in line #1. Dart has to insert a check here
that 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 ensures 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 to dynamic Function(Object) (the inferred type
of f), the line #2 is never reached.

Afaik this is the only case where a declaration of the form var x = expr; fails
dynamically when assigning the evaluated expression to the value of the
*inferred* 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 soundness.

Note that this check hasn't been added to DDC, yet.
<https://gist.github.com/floitschG/fec37b8650bef684a8b072d441adc9c2#inference-vs-manual-types---part-2>Inference
vs Manual Types - Part 2

In part 1 of this section we finished with a claim that there are
expressions for which writing the inferred type at their declaration point
yields a different program than if we had left it off.

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.

var x = [[499], [false]]; // Inferred to be a
List<List<Object>>.List<List<Object>> y = [[499], [false]];

x[0].add("str"); // Error.
y[0].add("str"); // OK.

For x, the nested list [499] is inferred as List<int>, and [false] is
inferred 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>>.

For y, the List<List<Object>> type is used in the downwards inference to
type 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 provides the type that this expression should have:
List<Object>. All entries of the outer list are forced to be List<Object>.

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 value, whereas the one that was forced to be
List<Object> succeeds.
--
For other discussions, see https://groups.google.com/a/dartlang.org/

For HOWTO questions, visit http://stackoverflow.com/tags/dart

To file a bug report or feature request, go to http://www.dartbug.com/new
---
You received this message because you are subscribed to the Google Groups "Dart Misc" group.
To unsubscribe from this group and stop receiving emails from it, send an email to misc+***@dartlang.org.
tatumizer-v0.2
2017-09-01 14:20:05 UTC
Permalink
Mixins: For historical perspective, previous discussion:
https://groups.google.com/a/dartlang.org/d/msg/misc/RBV4wvG7DcU/CLsUqjevDAAJ
--
For other discussions, see https://groups.google.com/a/dartlang.org/

For HOWTO questions, visit http://stackoverflow.com/tags/dart

To file a bug report or feature request, go to http://www.dartbug.com/new
---
You received this message because you are subscribed to the Google Groups "Dart Misc" group.
To unsubscribe from this group and stop receiving emails from it, send an email to misc+***@dartlang.org.
Loading...