-
Notifications
You must be signed in to change notification settings - Fork 26
Infer list (and map?) literals based on the data they contain #108
Comments
If we do this, we may want to disallow empty list literals - e.g., [] - without a type. |
it's basically a choice whether to optimize for typed+infer or untyped programs, in the defaults? // current behavior
<String>["hello", "world"] // List<String>
["hello", "world"]..add(42) // List<Object> vs ["hello", "world"] // List<String>
<Object>["hello", "world"]..add(42) // List<Object> my hunch would be that typed Lists are a (lot) more common than Regarding |
@jmesserly We will (with @leafpetersen's cl) infer Regarding https://codereview.chromium.org/1038063002/diff/1/lib/src/parsed_path.dart In this case, |
👍 to this proposal. In practice, I very rarely want an actual I'd also love not to have to add type annotations to my empty list literals (or anywhere in my method bodies for that matter), but I'm given to understand that the inference to make that work would be more difficult. Given that, I'd prefer to have an unannotated, un-inferred empty literal produce an error or warning. |
It is a problem if, say, a List is inferred for [2, 4] and then later On Thu, Mar 26, 2015 at 9:52 PM, Natalie Weizenbaum <
Erik Ernst - Google Danmark ApS |
Adding text to a file isn't just more effort for the author, it's more effort for the reader as well. The language shouldn't be more verbose just because that verbosity can be hidden behind an IDE. I think in practice there are going to be very few cases where the user will initialize a list with values that are more narrowly typed than they eventually use. |
I don't think that's so unlikely: An empty list has no guidance on the On Thu, Mar 26, 2015 at 10:37 PM, Natalie Weizenbaum <
Erik Ernst - Google Danmark ApS |
I'm on board with not doing inference for empty lists, but I think 95%+ of non-empty list literals will be used in a way that's consistent with their initializing values. And if not, I don't think it's dangerous; the user will just get a static error. |
Summarizing options:
Note, the empty list |
There is one thing I hate more than requiring verbosity – trying to guess user intent when it's vague and ending up with unexpected results. I don't mind |
I don't think a list initialized with values indicates a vague user intention at all. I think the intention is so clear that users will be upset when the compiler doesn't understand them.
I think you might change your mind after using a list-based DSL. Here's what a snippet of pub's test code would look like with annotations: setUp(() {
d.dir(appPath, <d.Descriptor>[
d.appPubspec(),
d.dir("web", <d.Descriptor>[
d.dir("one", <d.Descriptor>[
d.dir("inner", <d.Descriptor>[d.file("file.txt", "one")])
]),
d.dir("two", <d.Descriptor>[
d.dir("inner", <d.Descriptor>[d.file("file.txt", "two")])
]),
d.dir("nope", <d.Descriptor>[
d.dir("inner", <d.Descriptor>[d.file("file.txt", "nope")])
])
])
]).create();
});
It'll be reified either way in DDC or a under a DDC-compatibility flag in the VM. Also, right now very little is reified in the VM anyway due to the style guide and the lack of generic methods, and in practice it hasn't caused much of a debugging issue. |
Another thing just occurred to me: if we consider |
On Fri, Mar 27, 2015 at 12:04 AM, vsmenon [email protected] wrote:
|
On Fri, Mar 27, 2015 at 1:22 AM, Natalie Weizenbaum <
best regards, Erik Ernst - Google Danmark ApS |
On Fri, Mar 27, 2015 at 2:53 AM, Natalie Weizenbaum <
best regards, Erik Ernst - Google Danmark ApS |
On Fri, Mar 27, 2015 at 12:42 AM, Erik Ernst [email protected]
|
On Fri, Mar 27, 2015 at 2:44 PM, vsmenon [email protected] wrote:
|
I think @eernstg already addressed this, but |
On Fri, Mar 27, 2015 at 7:31 AM, Erik Ernst [email protected]
|
With respect to this specific example: setUp(() {
d.dir(appPath, <d.Descriptor>[
d.appPubspec(),
d.dir("web", <d.Descriptor>[
d.dir("one", <d.Descriptor>[
d.dir("inner", <d.Descriptor>[d.file("file.txt", "one")])
]),
d.dir("two", <d.Descriptor>[
d.dir("inner", <d.Descriptor>[d.file("file.txt", "two")])
]),
d.dir("nope", <d.Descriptor>[
d.dir("inner", <d.Descriptor>[d.file("file.txt", "nope")])
])
])
]).create();
}); If d.dir is typed appropriately, we might get the right types via downwards type propagation. This doesn't fix everything, but it does help. For example: // Unannotated, requires upwards inference to get the right type
var l = [[[3]]]
// Fully annotated
var l = <List<List<int>>>[<List<int>>[<int>[3]]]
// Equivalent to the previous if downwards propagation is done
var l = <List<List<int>>>[[[3]]] |
Here's DirectoryDescriptor dir(String name, [Iterable<Descriptor> contents]) =>
new DirectoryDescriptor(name, contents == null ? <Descriptor>[] : contents); @leafpetersen could we conclude |
Yes, the CL I have out isn't fully general but should already handle this case. |
That's a very big door to open (a conforming implementation of DDC is now The only thing I'm advocating is to let programmers make the choice when Inference could come up with some bounds though .. if flow analysis shows For function literals using '=>', the spec does mention that the static On Fri, Mar 27, 2015 at 4:23 PM, vsmenon [email protected] wrote:
|
Really good point. In practice, most of these are => functions already. The biggest exception that comes to mind is Future and Stream (.then, .listen), but async/await should take care of those? |
(also, couldn't we compute a static type from |
On Fri, Mar 27, 2015 at 5:41 PM, Leaf Petersen [email protected]
var l = <>[<>[<>[3]]] The innermost list would get type List because we have stated that it best regards, Erik Ernst - Google Danmark ApS |
Yes. I very much want to see something like this happen, covering both generic class arguments and generic method arguments (assuming some form of generic methods gets in). I think a key issue is making sure that the inference is specified in a way that admits a very efficient algorithm that can be implemented in the VM - I don't think this should be hard, but the details need to be nailed down. |
On Fri, Mar 27, 2015 at 6:03 PM, John Messerly [email protected]
Erik Ernst - Google Danmark ApS |
We are still iterating on the type system (static + dynamic), but there is a type system behind what we are doing, so no, such an implementation would not be a conforming DDC implementation. Technically speaking it would satisfy the same correctness criteria as DDC does with respect to semantic coherence with the rest of the platforms though. In practice, the empirical question is whether we can make the set of programs that fall into the "throws under DDC, doesn't on other platforms" small enough, and more importantly, make that set consist primarily of programs that programmers want to have rejected.
Maybe? I'm not averse to pushing on this to see if there's a different path to get where we need to get, but... we've looked pretty hard at the problem (including running lots of experiments on existing code bases) and this is the best path forward that we've found so far given the various constraints. |
If we're talking about empirical likelihood, I'm confident that empirically a List literal initialized with values is almost never mutated to contain values of a different type.
It definitely affects the reified type, but I doubt it would have wide-reaching effects. The type is statically verified everywhere it's passed to another method or returned from its originating method, so it seems very difficult for it to leak an unexpected reified type. In general, I'm struggling to see a case where the proposed heuristic causes problems in practice that aren't immediately obvious due to static analysis and trivially fixable by adding |
This discussion is awesome! Here's my perspective, coming at this as a user. I'm using Dart instead of JS because I want the static checker to catch as many errors as possible. It's a given that as a Dart user I will occasionally have to write extra bits of stuff to shut up the type checker. If I didn't want to do that, I'd use JS. Given that, I think it makes sense for DDC to default to assuming a narrow type and require a bit of annotation to widen. I am fine with an error here: var apes = ["chimp", "bonobo"];
apes.add(123); This does not mean that inferring a narrow type always leads to more type errors. If we assume the narrower type, it means I do not get an error here: var apes = ["chimp", "bonobo"];
apes.first.toUpperCase(); // <-- And I would get an error there if |
I think your general point is quite reasonable, but just to be sure we're clear:
We currently implement covariant subtyping for generics. This means you can pass off a List to a context expecting a List, with no static warnings. And that context can in turn (try to) add an int to the list. This will throw at runtime, but we won't catch it statically. I agree that code that does this is probably much rarer. If we do this kind of inference (and really, even if we don't) - I think we need to have a good story about how we communicate back to the user exactly where the reified type that is causing the error comes from. I think this can be done - it's just a matter of being careful and helpful with our error messages. We should aim to be able to report an error that looks like: "Hey, you just tried to add an int to a List that was allocated as a List of String on line XXX of file YYY. Do you really want to do that? If so, you should change line XXX of file YYY" |
@leafpetersen Good point. I do think that a function that accepts |
OK, that one I don't follow at all. If I'm given a |
I'd say in general that a function that takes a list shouldn't modify it at all, and certainly not in a way that breaks under covariance. There's room for functions that are explicitly documented to say "adds an int to the list" but they're the exception rather than the rule. Put another way, if a function declares a parameter as |
On Fri, Mar 27, 2015 at 6:34 PM, Leaf Petersen [email protected]
List)
conform to T because it involves method/getter invocation on O
you insert checks enforcing that V must conform to T (this is similar to If so, maybe you could outlaw those nasty 'reified dynamic' objects? ;-) Returning to the overall topic: I think that it is fair to claim that if best regards, Erik Ernst - Google Danmark ApS |
On Fri, Mar 27, 2015 at 10:27 PM, Natalie Weizenbaum <
best regards, Erik Ernst - Google Danmark ApS |
On Tue, Mar 31, 2015 at 4:58 AM, Erik Ernst [email protected]
Yes. In general, we prefer static errors to runtime ones. E.g., we
Let M = checked mode. P is equivalent under M and L only for non-exceptional cases. This is And, of course, we can make some exceptions much more difficult to catch.
|
On Tue, Mar 31, 2015 at 4:27 PM, vsmenon [email protected] wrote:
Aha, do you then restrict assignability to subtyping? This would bring you
Here's a case where we use different terminology: To me, Dart-checked-mode The point is that programmers can use one kind of reasoning about their What I was referring to is a less powerful kind of variation: Simple best regards, Erik Ernst - Google Danmark ApS |
On Tue, Mar 31, 2015 at 8:52 AM, Erik Ernst [email protected]
We're still experimenting on this. When we don't restrict assignability,
I don't think our users see it this way. They develop in one and deploy in The point is that programmers can use one kind of reasoning about their
|
On Tue, Mar 31, 2015 at 6:01 PM, vsmenon [email protected] wrote:
OK. I'm trying to wrap my head around the idea that we can have different I really think that semantic differences (between different ways to run the However, if it is possible to enforce that all the variants run in
If they do exactly that then they are bound to experience different [..] best regards, Erik Ernst - Google Danmark ApS |
On Wed, Apr 1, 2015 at 12:55 AM, Erik Ernst [email protected]
Exactly. This is what we mean by "catastrophic" or "non-recoverable"
This is exactly how we've been telling our users to use Dart. The names I don't think this is as bad as you seem to think. There is certainly That said, I think it's good motivation for favoring static
|
Yes yes yes.
One option would to let APIs where we think downcasts are likely to be common annotate that fact and then allow them implicitly there. So assignment would in most cases use normal subtype tests. But, for example, @implicitDowncast
Element querySelector(String selector) { ... } and then calls to that have looser assignment compatibility rules. |
On Tue, Mar 31, 2015 at 6:01 PM, vsmenon [email protected] wrote:
I think it would be nice to be able to say "the behavior of your compiled
OK, my choice of words was a bit polemic. But still, I think it's best regards, Erik Ernst - Google Danmark ApS |
On Wed, Apr 1, 2015 at 3:53 PM, vsmenon [email protected] wrote:
Very nice! To me, the crucial point is that we shouldn't have to consider
As long as there are no checked mode violations, the production mode I don't think this is as bad as you seem to think. There is certainly
Right, many languages and tools allow you to add a bunch of extra (e.g., Getting used to it. ;-) I guess my worries are focused on the notion of a "time bomb": If That said, I think it's good motivation for favoring static
Yes. A proof is a very good reason for omitting a check. ;-)
Alternatively, satisfying subtype constraints in a program that is strictly best regards, Erik Ernst - Google Danmark ApS |
I lost track of this discussion, did we reach a conclusion? @vsmenon @leafpetersen |
On Thu, Jun 18, 2015 at 11:42 PM, John Messerly [email protected]
But I think the brief version of the point that I was trying to make was Consider a program P that is compiled by a strictly spec conforming Surely I forgot something, and this property would need to be adjusted best regards, Erik Ernst - Google Danmark ApS |
merging this in to: dart-lang/sdk#25220 |
This came up in recent discussions (@nex3 @kevmoo @leafpetersen).
We currently infer l in:
as
List<dynamic>
. This causes issues if we use l in a context that requiresList<String>
. As an alternative, we could infer l as a List with the narrowest static type that accommodates its contents. In this case,would be an error. Thoughts?
The text was updated successfully, but these errors were encountered: