Sdk: 'var' in strong mode has unexpected semantics

Created on 24 Sep 2016  Â·  46Comments  Â·  Source: dart-lang/sdk

Many Flutter developers will be coming from languages like JavaScript.

In JavaScript, var means the same as dynamic in Dart.

In Dart 1.0, var also means dynamic.

However, in Dart 2.0, var means auto, as in, take the type of the expression on the right hand side.

This means that code such as the following fails:

var widget = new Row(...);
...
widget = new Container(...);

We should either make var mean dynamic or make var get the type that is the common denominator of all the expressions that are assigned to the variable. Otherwise we are going to be putting up a barrier to entry for a large class of Flutter developers who do not care about types at all.

area-language

Most helpful comment

Personally i would love to see var to infer type of an expression instead being an alias for dynamic.
That was one of the confusing moments when i discovered they are semantically the same in Dart 1.

Even though i'm faimiliar with JS var semantics, I was expecting it to infer type as it does in statically typed languages since 1. we have dynamic and 2. Dart often referred to be highly inspired by statically-typed languages which do just that.

As of JS programmers who will get the error as in flutter/flutter#5968 i think after the first time they will adapt and won't be alienated by such a tiny thing (same way as I wasn't alienated when discovered var == dynamic, though didn't like that).

Also, i would expect JS programmers to move to React Native rather than Flutter simply because it won't force them to learn simillar yet different new language. And as soon as they did choose Flutter and have to learn Dart anyway, they also can learn the Dart's semantics of var .

All 46 comments

I don't think Dart 2 is aimed at programmers that don't care about types at all.

Are you suggesting that we change the "infer" declaration to a different name than var, e.g., auto and leave var as dynamic?

Flutter is definitely aimed at (in part) programmers who don't care about types at all. Hopefully we can find a way to address those programmers in Dart 2.0 as well. :-)

I hadn't thought of having a new keyword for the "infer" declaration, but that would make sense. It would make things easy to explain -- "var" means you don't know the type and it can change, "auto" means you don't know the type but it'll be whatever you first assign to it, "dynamic" means you know the type will vary and shouldn't be checked (var and dynamic presumably compile to the same thing).

Are you suggesting that we change the "infer" declaration to a different name than var, e.g., auto and leave var as dynamic?

This might be a better option. If the meaning of something like 'var' changes between versions, it would render a lot of earlier documentation, blog posts, tutorials etc. useless/confusing for those new to Dart (I'm one of those)

I believe the experience with strong mode has been that almost all existing code works if var means type inference. Even those who don't think about types tend to introduce new variables when they use new types, reusing a variable for something different is rare.

The one place where it breaks down is when a variable is initialized using one subclass of a general interface and it's later changed to hold another subclass. In that case, you do have to specify the interface. It's a compile-time error and noticable by the analyzer, so you are notified promptly.

That is a cost. The benefit is that the inferred variable types actually do catch type errors..
Someone more familiar with the actual use of strong mode might be able to elaborate this.

That one place is very common in Flutter code.

(or rather, very common in UI code, such as code written with the Flutter framework. It's probably not particularly Flutter-specific.)

In JavaScript, var means the same as dynamic in Dart.

JS doesn't have a type system at all, so I don't think you can claim that variable declarations "mean" any specific type. In, JS 123 also means "123 as dynamic". :)

In C#, TypeScript, Swift, Scala, Go, Kotlin, Flow, Nim, Haxe, and Vala among others, var means "infer the type from the initializer". In languages with type systems, the clear precedence is that the word var implies inference.

We could swim upstream against that, but we'd be throwing away a ton of familiarity to do it.

Note that before strong mode, var used to mean dynamic in Dart too, and we changed it in part because many users over many years kept telling us how surprised they were by that fact. It clearly went counter to their expectations. (Unsurprising given how many of them are coming from languages where var means "infer".)

I think Flutter will get far more programmers that come from JavaScript, where "var" means "dynamic", than we will from other languages. We have already heard one person complain about "var" meaning "auto" (https://github.com/flutter/flutter/issues/5968). I've never heard anyone using Flutter complain that "var" means "dynamic".

("var" in JS does mean "dynamic", in that you can assign any value to it. That JS doesn't have a type system at all is precisely the reason that some people coming from JS to Flutter will similarly expect Flutter to have no type system. If we require that they first learn about types before being able to use Flutter then we will be artificially increasing the barrier to entry. That there is no such barrier to entry is one of Dart 1.0's greatest strengths as a language for Flutter.)

Making var mean dynamic in strong mode would be a massive, massive breaking change, and it is, frankly, one that I see having negative value to most of our existing users. Doing inference for var revealed quite a few latent bugs in code that we converted to strong mode, and is a key enabler for type inference in the language in general.

I do think that having a short three letter syntax for an un-inferred variable binding would be great, and would enable the users that you are concerned about to just write their code using a dynamic coding style and never have to worry about it. My preference would be to use any for this:

any widget = new Row(...);
...
widget = new Container(...);

I think Flutter will get far more programmers that come from JavaScript, where "var" means "dynamic", than we will from other languages.

Interesting. I'm surprised, actually. I would have expected Flutter would attract a lot of Java and Objective C programmers, and that those users would be comfortable with types.

I would have expected Flutter would attract a lot of Java and Objective C programmers

Either way, I'm 100% certain TypeScript and Flow have both attracted JavaScript developers _including accepting their existing JS code_ and both of those languages define var to mean "infer the type".

That's not an equivalent comparison. TypeScript attracts specifically programmers looking for types. So obviously, they want types.

Flutter attracts programmers looking for high-performance cross-platform mobile application development. The language is incidental. Many Web developers do not want TypeScript, and would be sad if we forced them to think about types.

The Web, and hopefully Flutter, have a wide variety of programmers with a wide variety of skill sets, from professional programmers who love types to people who just dabble now and then and really don't care about types. Dart 1.0 is a great language for this kind of variety because you can easily pick any point on the spectrum from fully typed, no inference, everything explicit, to totally loose, no types, all dynamic.

It would be a huge loss to Flutter if Dart 2.0 lost this feature of Dart. Making "var" use inference is one example of doing that. We have already received complaints from people about it (https://github.com/flutter/flutter/issues/5968), and that's with the problem only being visible if they opt into the stricter analyzer rules (by default we don't push the strong mode rules on people yet).

@Hixie

I can certainly follow your reasoning that some Dart developers (including perhaps many Dart-on-Flutter developers) may consider the inferred types to be an unnecessary obstacle, especially in the case you mentioned earlier (initialize to a subtype T1 of the type T that would make it work, then mutate to some other subtype T2 of T, and get a "T2 is not a subtype of T1" error).

However, I suspect that the 'all dynamic' approach you mention will not be applicable in practice: In the situation where the highly dynamic code would be written, it typically happens in a context where a number of libraries written by professionals have been imported, and they would typically have very tight type annotations (e.g., the Flutter libraries). In other words, even the developers who don't care much about typing would have to satisfy a large number of detailed typing requirements. It's just a matter of when those requirements will be checked, not that they can be evaded entirely (because checked mode today and the upcoming Dart 2.0 will enforce the declared typing constraints, either by static proof or by dynamic checking).

If we accept the premise that much of the code will interact with tightly typed libraries, the inferred types may actually help programmers get in line with the libraries that they are using, rather than letting them stray off the beaten path in some way that won't pass the type checks anyway.

In that case the focus point of the loss you mention is exactly the cases where the inferred types are "wrong", and that isn't caused by the interpretation of var to mean "infer" in general.

For the specific T, T1, T2 scenario I mentioned above, it would be possible for an IDE to detect the situation and act on it: Whenever a variable with an inferred type T1 gets assigned a value of type T2 which is not a subtype of T1, try to recheck the relevant scope with that variable typed at the least upper bound of T1 and T2, and if it succeeds then offer a quickfix which adds an explicit T annotation to the variable; if it fails, retry with the immediate supertypes of T.

With this approach, inferred types would be available in all those (surely typical) cases where inference works well, and developers would get a very relevant kind of help in the annotation of their program in the cases where a scope-wide inference process would find a useful type.

I admit that this approach may not satisfy the die-hard dynamic developer who is actually smarter than the type checker, and who keeps hitting those overly cautious walls of typing, but I think that this may be a rarer demographic than the developer who is OK with typing, as long as most of it is something that they get for free.

I don't think we should rely on IDEs to fix things; even some core Flutter developers (e.g. me) use IDEs that aren't Flutter- or Dart- aware.

I would be fine with fixing this particular issue by having the compiler pick the appropriate type to make things work.

I don't see why using libraries that are explicitly typed would make any difference here. You can use libraries that are typed without caring about the types today. Is this going to change? If so, I think that's a problem. How is it going to change?

I think the problem being referred to is that in strong mode, a List<dynamic> is not assignable to a variable of type List<int>. This is to ensure type safety, so reading from a List<int> variable is guaranteed to provide an int, which a List<dynamic> can't promise.

That means that "dynamic" code, which might create a List<dynamic>, can't safely pass its dynamic objects into strongly typed code. You'll have to do something to adapt the type of the List<dynamic> to List<int> before you can pass it to typed code. If all pans out, that "something" will be very easy for the programmer, but probably not automatic.

There's no mention of lists or generics in https://github.com/flutter/flutter/issues/5968. It's just a case of a variable being used for two values that don't happen to have the exact same type (though they do have a common superclass that is itself below Object).

Doing better inference for var in these kind of multiple assignment cases is something I think we'd like to explore, but realistically I don't see any way for it to make the cut in the next few quarters given what we already have on our plate. It is something I'd like to come back to at some point though, to see if we could do something better but still fast.

Better inference would be interesting, but a cheap and quick solution would be to just keep var as meaning dynamic as it has so far, and introduce a new keyword (like auto) for the inference version.

Disclaimer: I am a stranger and don't know the relationship between Dart 2.0 and Strong Mode.

Even in dart 1.x, var literally stands for variable and only implicitly indicate the declared variable is dynamic.

var v;

At the same time, dart has another dedicated and explicit way to declare dynamic variables.

dynamic v;

IMHO, the semantic change of var as auto is not critical nor confusing and forward source-compatibility is worth considering.
In addition, in Strong Mode, there are many other purposeful semantic changes around type inference which are forward source-compatible.
My understanding is that this changing of semantics is the heart of Strong Mode.
Therefore, I can't find any particular reason for regarding this semantic incompatibility of var as a problem.

On the other hand, auto introduces nontrivial forward source-incompatibility.

auto v; // error in dart 1.x.

In case of Flutter for iOS, dart code must be AOT compiled for some reasons including performance.
Therefore, it is worthy that the language encourages developers to use typed variables as much as possible.
On the other hand, var as dynamic doesn't encourage developers to do so.
The JIT compiler in runtime of Flutter for Android also prefers typed variables for good performance.

By the way, I found something interesting below.
Dart Dev Summit 2016: AOT compiling Dart for iOS/Android

We're all in agreement that types are good. (Indeed the Flutter style guide goes even further and requires explicit types everywhere!)

The problem isn't that. The problem is that we risk alienating a large chunk of potential users if we make adoption harder by breaking their expectations around fundamental concepts like var.

It doesn't matter how fast we encourage people's code to be if the whole platform fails to get adoption in the first place.

We can encourage people to use types or use auto rather than using var, while still allowing new developers to the platform to write code that works without having to worry about it.

Personally I would most prefer the "var uses the tightest type it can infer" approach. That gives us the best of both worlds, and avoids having to introduce auto. (And we can have the analyzer warn whenever the inferred type is less tight than the first assignment.) If that's too expensive, then the "var as dynamic" approach allows us to move to the former approach eventually without breaking people in the meantime. I don't see why "var infers from first assignment" would be better than either of those. We have already seen that it causes problems with people writing Flutter code.

We can encourage people to use types or use auto rather than using var

I'm afraid developers keep using var as dynamic instead of auto.

It doesn't matter how fast we encourage people's code to be if the whole platform fails to get adoption in the first place.

I'm not afraid it, because Swift already succeeds much better than current Dart with var as dynamic.

How is Swift adoption amongst mobile Web developers? Is Swift's adoption coming mainly from ObjectiveC and Java developers, from JavaScript developers, or from new developers?

Yes, Swift users are from ObjectiveC, but anyway Swift succeeds.
How about TypeScript which also succeeds much better than current Dart.

This is a result of Google Trends.
https://www.google.com/trends/explore?date=2011-01-01%202016-12-31&q=%2Fm%2F0h52xr1,%2Fm%2F010sd4y3,%2Fm%2F0n50hxv

https://www.google.com/trends/explore?date=2011-01-01%202016-12-31&q=%2Fm%2F0h52xr1,%2Fm%2F010sd4y3,%2Fm%2F0n50hxv,JavaScript

On Fri, Oct 14, 2016 at 11:59 AM Cat-sushi [email protected] wrote:

This is a result of Google Trends.

https://www.google.com/trends/explore?date=2011-01-01%202016-12-31&q=%2Fm%2F0h52xr1,%2Fm%2F010sd4y3,%2Fm%2F0n50hxv

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/dart-lang/sdk/issues/27422#issuecomment-253843608,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAhpHLce2jzgaHF9nlKjd4Mnku6VMI6iks5qz6b9gaJpZM4KFh9_
.

Dart must be JavaScript?
Why Dart?

Goodness no. If Dart was JavaScript it wouldn't be Dart.

I was just trying to say that JavaScript has a great deal more programmers than either Swift or TypeScript and therefore should be given a correspondingly greater weight in our considerations.

Dart should be its own language. But that doesn't mean we have to throw additional roadblocks in the way of its adoption if we don't have to.

I totally agree to your general theory.
I just mean inferred var might not be a big roadblock.
https://www.google.com/trends/explore?date=2011-01-01%202016-12-31&q=%2Fm%2F0h52xr1,%2Fm%2F010sd4y3,%2Fm%2F0n50hxv,JavaScript,%2Fm%2F07657k

I expect the bulk of our future audience is JavaScript engineers, not C#
engineers.

Can you clarify why you would object to 'var' inferring the type from the
complete context?
Do you not think the Widget issue raised above is a valid issue?

On Fri, Oct 14, 2016 at 10:35 PM Cat-sushi [email protected] wrote:

I totally agree to your general theory.
I just mean inferred var might not be a big roadblock.

https://www.google.com/trends/explore?date=2011-01-01%202016-12-31&q=%2Fm%2F0h52xr1,%2Fm%2F010sd4y3,%2Fm%2F0n50hxv,JavaScript,%2Fm%2F07657k

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/dart-lang/sdk/issues/27422#issuecomment-253957543,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAhpHHBjtzjBRNvNyt8JWW61YujE8vXqks5q0DvvgaJpZM4KFh9_
.

My points are, ...

  • C# is not a AltJS, but is famous to be respect as well as JavaScript, anyway.
  • Young TypeScript as a AltJS already succeeds to a certain degree, and is still growing very quickly.

Sorry, I am not a heavy developer and haven't understood real pain points of inferred var.
With respect to the Widget issue, I just feel the comment of @munificent reasonable.
I am afraid developers are keep using var without recognition as dynamic.
I think introducing distinct and self-describing keyword any is a little better than keeping var as dynamic.

Can you clarify why you would object to 'var' inferring the type from the complete context?

This is an interesting idea, and one worth exploring, but it's not a sure-fire win. It does what you want in many common cases, but:

  1. In complex cases, I think the inference rules can become much harder to implement efficiently. Leaf came up with an example a while back, but I can't seem to track it down. Here's a simpler one that gives a bit of flavor for how inference can get weird if you take every assignment into account:

dart var f = 1; f = ((i) => i * 1.5)(f);

  1. A corollary to this is that it's harder for users to reason about the types of their variables. In particular, changes to how a variable is used can cause non-local errors to appear _before_ the change. Consider:

``` dart
var a = 1;
print(a.isEven);

// more lines of code...
```

Later, you add:

``` dart
var a = 1;
print(a.isEven);

// more lines code...

a /= 2; // <--
```

Now an error appears on the isEven call _before_ the line you added. Your later assignment of a double to a changed its inferred type to num all the way up at its point of definition. Since num does not have isEven, that call is now an error.

  1. This in turn can interfere with other ways we might like to use types. One example is implicitly converting number literals to their expected type. That would make this code do the right thing instead of being an error:

dart double n = 0; // Implicitly convert "0" to "0.0" at compile time.

But now consider how this interacts with the above example:

dart var a = 1; a /= 2;

Here, we infer a to be num because it gets assigned from both an int (1) and a double (the result of /). Then, in turn, the 1 literal stays an int. But if inference had chosen double instead, then the literal would be implicitly converted to 1.0 and then both assignments to a would be from doubles, also yielding a coherent story.

Which result would the user intuit?

Having said all that, it's worth experimenting with richer rules for inferring the types of locals. We could consider taking all assignments into account to select a single type. Another option is to allow the type of the local to _change_ through different assignments.

Both have a lot of pros and cons and I wouldn't be surprised if neither panned out—recall that _almost_ every language that supports inferred local variables just infers it from the initializer—but it's worth spending a little time on.

I agree that those cases are problematic.

For #1, I agree that if you're trying to resolve a type and the variable is mentioned on both sides of an expression, there's no good intuitive answer.

For #2, there's actually a lot of cases today where the analyzer gives messages in an order that leads to the "real" error being buried deep under other "side-effect" errors, and I agree it's confusing. In many of those cases, as with this one, it's not obvious to me how we could tell which was the "real" error (maybe they meant all those numbers to be floats, and just assumed that double had isEven, because, why wouldn't it...).

For #3, I don't really see how this changes anything. I mean even just var a = 1 would be consistent whether the inference decides it's int, double, num, or dynamic, if 1 can be interpreted as 1.0 when assigned to a double.

Allowing the type to change through assignment is an interesting idea.

My overall concern is just that we make sure that we're intuitive to people who don't think of types at all. This is one of Dart 1.0's big advantages. I would be sad if we lost it, especially as many of our future developers will be coming from JavaScript.

Late to the show, here's my 25 cents CAD.
Damage from breaking existing code is temporary: it would cost you a day of two of extra work.
Damage to the language by introducing extra (confusing) keywords like "any" or "auto" will be permanent and irreparable.

Changing type based on future assignment is a strange idea IMO. Compiler will infer "type", but human reader of the code won't. Utterly confusing feature.

Personally i would love to see var to infer type of an expression instead being an alias for dynamic.
That was one of the confusing moments when i discovered they are semantically the same in Dart 1.

Even though i'm faimiliar with JS var semantics, I was expecting it to infer type as it does in statically typed languages since 1. we have dynamic and 2. Dart often referred to be highly inspired by statically-typed languages which do just that.

As of JS programmers who will get the error as in flutter/flutter#5968 i think after the first time they will adapt and won't be alienated by such a tiny thing (same way as I wasn't alienated when discovered var == dynamic, though didn't like that).

Also, i would expect JS programmers to move to React Native rather than Flutter simply because it won't force them to learn simillar yet different new language. And as soon as they did choose Flutter and have to learn Dart anyway, they also can learn the Dart's semantics of var .

I don't understand the argument that Flutter would mostly be adopted by javascript devs. I'd actually think the exact opposite. I don't think that Flutter would be the tool that convinces a javascript devs to jump into the mobile space. Rather, I'd think existing mobile devs like myself who have felt the pain of re-creating the same project on multiple platforms, all with separate teams and bugs are who would find flutter appealing. I'd think the majority of flutter users would be coming from Java, Obj C, and Swift.

There are far more Web developers than Android/iOS developers. Consider e.g. this StackOverflow survey from 2016. Even if Flutter were to attract _all_ the Android/iOS developers and only a third of the Web developers, that would still be more Web developers than mobile developers. (Obviously if we got anywhere near that level of adoption I would be amazed regardless...)

Dart 1 already has var meaning dynamic, but it doesn't appeal to JS developers as much as TS does.
So, I think type inferring is not the roadblock for JS developers to use dart.
And, I think type inferring is crucial to Strong Mode and to Non-Null By Default.

We're far into the migration and the existing 2.0var/dynamic semantics are working well for us.

I'm not sure that's true. We haven't yet tried to migrate existing flutter applications, which is where I expect to see the Widget/var issue.

We're definitely not changing the semantics at this point, regardless.

we just changed them once, i see no reason why we wouldn't change them again, especially if we find the new semantics are a problem. Especially since this issue was raised over a year ago.

We haven't yet tried to migrate existing flutter applications

It looks to me like the reason the issue was filed was because it was discovered by someone migrating an existing flutter application to strong mode.

especially if we find the new semantics are a problem

Have we, in the year since this issue was filed, found any new evidence that they are a problem?

The problem described in the original description is still real. We just haven't seen it because we haven't tried to get people to use Flutter in this mode (except for some very early adopters, but they tend to be much more resilient to this kind of thing, and tend to type their code rather than use "var").

We'll know more about how much of a problem this really is in a few months, once we've enabled dart 2 by default and forced people to run into this issue for a while.

The problem described in the original description is still real.

It is real, but it's based on an idiom that was deliberately chosen in the context of a language with a different approach to types. In a different context, you might pick a different idiom and then the problem goes away.

Personally, completely independent of typing, I find the idiom this issue relates to to be pretty strange. Reusing the same variable to mean different things at different points in the body of the method is a code smell to me. The fact that the type system doesn't know what's in the variable mirrors the fact that the reader might not know what's in the variable either.

Imagine skimming this code:

Widget build() {
  var widget = new Row(...);
  ...
  widget = new Container(..., widget, ...);
  ...
  doSomething(widget);
  return widget;
}

You're trying to understand what that doSomething(widget) call is about. You look up the declaration of widget and see it's a Row. "OK, I'm passing a Row to it." You inadvertently overlook the later assignment to a completely different type of object.

I think mistakes like this are easy to make. Safer could would look like:

Widget build() {
  var row = new Row(...);
  ...
  var container = new Container(..., row, ...);

  return container;
}

Now the reader is less likely to get confused, and inference does the right thing. Of course, in cases where you deliberately want to reuse the variable because it's only assigned conditionally, then that's different:

Widget build() {
  var widget = new Row(...);
  ...
  if (itsTuesday) {
    widget = new Container(..., widget, ...);  
  }
  ...
  return new Border(widget);
}

This is, I think, reasonable code where the type inference isn't ideal. We could try to make the inference much more sophisticated, but I think that would lead to an overall worse user experience in other cases. Another solution is to choose an idiom:

Widget build() {
  return new Border(wrapOnTuesday());
}

Widget wrapOnTuesday(Widget inner) {
  if (!itsTuesday) return inner;

  return new Container(..., inner, ...);  
}

This plays nice with inference, gives a name to the interesting logic that's going on, and structures the UI top-down in the code.

It is vital for the type system to work well with the kind of code that feels natural to users. At the same time, "the kind of code that feels natural" changes based on the language and type system users are working within. There's give and take where the type system grows to fit the idioms adapt to the type system.

This may just be an idiom that's no longer a great fit for Dart. Fortunately, one of the things I really like about Flutter building UI in code is that we have the full expressiveness of the Dart language available, and I'm pretty sure we can find other equally pleasant idioms to replace it.

We'll know more about how much of a problem this really is in a few months, once we've enabled dart 2 by default and forced people to run into this issue for a while.

This is fair. I don't mind keeping this issue open in the language category if we want to keep it on our radar. It's going to be extremely hard to do anything about this in the short term, but making this better in the medium term could be approached from a number of different angles, and might be worthwhile (in part based on data we get from users).

In my mind, this is in large part yet more evidence that implicit casts and inference are a bad combination - there's just too much "play" in the type system, which means that when inference doesn't do what you expect, you don't get static errors. I agree with Bob that as a reader of code, I prefer that the declaration of the variable tell me everything I need to know about its type, either because of a type declaration or because I can rely on the initializer telling me its type. I could imagine there being a lint to not allow implicit downcasts to a variable which had its type inferred (would turn this issue into a static error).

I'm also really not averse to exploring improvements to inference in Dart 2.X releases - the main issue in my mind is how we're going to manage the breaking nature of the changes, but I think we can find a good solution to that part.

Was this page helpful?
0 / 5 - 0 ratings