--
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
To unsubscribe from this group and stop receiving emails from it, send an email to misc+uns...@dartlang.org.
--
You received this message because you are subscribed to the Google Groups "Dart Core Development" group.
To unsubscribe from this group and stop receiving emails from it, send an email to core-dev+u...@dartlang.org.
Regarding "Config specific code":> Now the big missing piece is analyzer support. Before we can even try it out, we at least need phase 0 support—it needs to be able to parse the syntax without freaking out.Not sure if one of the Analyzer folks is signed up already, but I don't mind helping out with this either.> Personally, I think it should not warn. I think it's OK for the static analysis to not cover all things users can express dynamically. I don't think we'll get good end user feedback that configured types are important if the tools try to scare them away from even trying them.> Florian and Gilad feel it's safer to start conservatively. If the warning is getting in their ways, users can let us know and we'll consider that a signal that configured types are desired.Okay, I'm letting you all know: the warning will get in my way. The very first time I try and use it. :)
package:html needs configurable classes, not just functions.I've mentioned this numerous times before. But I'm happy to continue brining this up. JavaScript frameworks can easily do server side rendering, Dart frameworks basically cannot. It's a quite popular feature, too (http://angularjs.blogspot.com/2015/09/angular-2-survey-results.html).
A warning that is basically "Warning: The Dart team hasn't implemented this yet." isn't helpful to users.
But the user would still be wondering why their IDE isn't warning them about stuff.
It's disconcerting when static analysis mysteriously fails; it makes the IDE look flaky and/or broken.
There is some risk. Maybe I mess up, and the command line configuration of HTML doesn't work for someone, because I botched an API somewhere. Is that really what we should be afraid of? Right now, the command line configuration of HTML doesn't work at all. How is that better for anyone?The risks here are entirely to the package's authors and its users. If they are willing to accept the risk, because the value it provides, then that should be their choice.
Here's an illustration that might be useful: If configurable imports are allowed to import types (and _especially_ if they are totally unchecked for "sameness") then the mechanism is similar to the following construct in C:```#ifdef defined(CONFIGURABLE_IMPORT_A)#include <alternativeA.h>#elif defined(CONFIGURABLE_IMPORT_B)#include <alternativeB.h>...#endif```and it is going to be hard work to make it behave much better than that. ;-)
The main reason for exporting classes is to extend "Element" from dart:html. Extending a class requires a lot of checks:- are the invoked constructors generative? That is, if a subclass invokes super.foo, then there better be a generative constructor of that name. Otherwise the subclassing won't work.
- we have to check that every member has a correspondent member in the config-class, and that the signatures are identical. Even minor changes like adding or changing optional arguments could yield a compilation error if they clash with subclasses.
- when using exported types as mixins we furthermore have to know what the superclass of the exported type is.
I'm not sure I completely understand the analogy with inheritance, but I think that the rules for type-exports are more difficult (or stricter) than anything we have currently in the system.
The main reason for exporting classes is to extend "Element" from dart:html. Extending a class requires a lot of checks:- are the invoked constructors generative? That is, if a subclass invokes super.foo, then there better be a generative constructor of that name. Otherwise the subclassing won't work.
- we have to check that every member has a correspondent member in the config-class, and that the signatures are identical. Even minor changes like adding or changing optional arguments could yield a compilation error if they clash with subclasses.
- when using exported types as mixins we furthermore have to know what the superclass of the exported type is.
These are just some things of the top of my head. There are probably others.
It's unfortunate that these rules differ from the normal subclassing rules, but they do make it easier to implement, and allow a kind of closed-form typechecking that is not possible in the general case. If that is the intent, we should specify these tighter rules and implement them.
Here's an illustration that might be useful: If configurable imports are allowed to import types (and _especially_ if they are totally unchecked for "sameness") then the mechanism is similar to the following construct in C:```#ifdef defined(CONFIGURABLE_IMPORT_A)#include <alternativeA.h>#elif defined(CONFIGURABLE_IMPORT_B)#include <alternativeB.h>...#endif```
Here's an illustration that might be useful: If configurable imports are allowed to import types (and _especially_ if they are totally unchecked for "sameness") then the mechanism is similar to the following construct in C:```#ifdef defined(CONFIGURABLE_IMPORT_A)#include <alternativeA.h>#elif defined(CONFIGURABLE_IMPORT_B)#include <alternativeB.h>...#endif```As I understand the proposal, this is the wrong analogy to C entirely. A closer C analogy is:
interface.h// Define the interface hereimplementation.c#ifdef CONFIGURABLE_IMPORT_A#include "alternativeA.c"#else#ifdef CONFIGURABLE_IMPORT_B#include "alternativeB.c"#endif#endifThere is a single interface specified,
with different implementations of that interface selected via configuration.
It's true that the runtime semantics do allow you to bypass the static checking (just as in C you can bypass the header and access the underlying exported symbols directly)
- I think this is a BAD IDEA, but then I would think that, wouldn't I... :)
Unless I'm missing something, this is very similar to C++ class declarations. You declare a class in a header file, and provide implementations for the methods in the .cc file. There's nothing deeply hard about checking that the implementation correctly implements the interface.
At this point I usually say "But when we want configuration-independent code it should have configuration independent types to work with", so we are trying to compensate for bad design: It should not be necessary to repeat all those types in an implementation-free and configuration independent setting, 'dart:html' should have been written like that in the first place.
Checking for an inheritance-ish relation among unrelated classes is already a new concept, but it could certainly be spelled out. The relationship between the "library interface declaration" T and the "implementation declaration" I (where I needs to conform to T in some sense) would be similar to the relationship between an instance and a class, but not identical:If we want to enable configurable import of types then we would need to create support for typed first class classes, i.e., we would need to define a way to specify class-types such that any given class would conform or not conform to that class-type. The library interface would need to contain a set of types (ordinary types corresponding to top-level functions/getters/setters, and class-types corresponding to classes), and each of the configuration alternatives would be checked for conformance to that library interface.The requirements on class-types depend on the intended usage. (1) If they are only intended to allow the first class classes to be used as type annotations then we could use an ordinary class C as the class-type and 'D conforms to C' for any given first class class D would simply mean 'D is a subtype of C'. For greater flexibility, we could introduce a notion of structural equivalence, and then we could say that it means something like 'D is a structural subtype of C' which might mean "it would not cause any warnings to add `D` to the `implements` clause of C". With the former approach we would be able to trust checked mode data transfers (assignments to variables / passing of parameters / returning of values) to succeed except for the failing downcast that we can always have; so if you use a "proper value" in these cases, you'll be OK. With the latter approach (structural subtyping), checked mode would fail even in the cases where we provide an instance of the declared type, because the actual type will not be related to the declared type at all, it is just "similar" in structure. So we might want to introduce structural subtyping into the dynamic semantics of checked mode execution if we were to use the structural approach for checking library interface conformance.
(2) If class-types are intended to allow the first class classes to be used in creation of new instances then 'D conforms to C' must also imply that the set of constructors defined in D enable at least the same set of instance creation expressions as the ones implied by the class-type C. For instance, D must have a set of constructors that covers the same set of names, and the parameter list of each of them must "cover" the parameter list of the corresponding constructor in the C. For this purpose it may not be necessary to require that the constructors have any particular kind (generative or not).(Here, "call-compatible" basically means that any correctly typed call on the original's signature is also a valid call on the replacement function, and the return type is com).
(3) If class-types are intended to allow the first class classes to be used as supertypes then we need negative constraints: The set of abstract members in any D must not exceed the set of abstract members in C, and D can only itself be abstract if C is abstract. Unless we settle for "exactly the same set of declared names and signatures" we would need to find a way to say (in a class-type declaration) that certain members do not exist.(4) If class-types are intended to allow the first class classes to be used as superclasses then we need to ensure that super-initializers in constructors in a given subclass of D will be well-formed, which means that C must specify not only the constructors and their argument lists, but it must also constrain some of them to be generative.(5) If class-types are intended to allow the first class classes to be used in class-types in another library ... etc.etc. ;-)
It should be noted that the potential non-conformance between any given class D and a class-type C extends to the transitive closure of types referred by the interface of D, which means that each of those types will have to have their own class-type and their own conformance check.If we decide that this all gets a tiny bit involved we may try to plug the hole early, and simply require that "it's all identical".But if we require that you can only make a choice between several identical libraries then it ain't doing much to solve the configurability problem. ;-)As far as I can see, this brings us back to a model that hasn't been discussed for a while: We could settle for an approach where the configurability support is exclusively concerned with filling in missing method implementations (that is, something very much like the current patch files).That would, by the way, be a kind of natural extension of the "import only values" (such as functions, but not types) proposal that I've been advocating for a while: We would be configurably importing a number of functions (that is: method implementations).On Thu, Oct 22, 2015 at 7:10 PM, Brian Slesinsky <skyb...@google.com> wrote:Regarding configurable imports and classes, here's my take on it:This is a feature that allows us to substitute one class for another. The feature it's most similar to is inheritance, which allows us to substitute one *instance* for another. They aren't the same, but to the extent possible, the rules for inheritance and class substitution should be similar. We should be able to explain class substitution with an analogy to inheritance.Some differences:- no super calls or mixins- we also substitute constructors, static methods and static fields. (This is probably the biggest reason why we couldn't just use top-level factory functions and inheritance. The big win is that we don't need to deal with a weird factory API or dependency injection.)- there is no equivalent of noSuchMethod() for static members.It seems like the goal for v1 should be to do static checking that's roughly similar to what we can do when checking a subclass against a superclass in Dart. It may not be perfect, but it shouldn't be worse.This isn't such a high bar since Dart's static checks for inheritance are pretty loose. Perhaps we can use similar techniques?- Brian--Erik Ernst - Google Danmark ApSSkt Petri Passage 5, 2 sal, 1165 København K, DenmarkCVR no. 28866984
--
You received this message because you are subscribed to the Google Groups "Dart Core Development" group.
To unsubscribe from this group and stop receiving emails from it, send an email to core-dev+u...@dartlang.org.
I guess is that the basic rule is that "replacing the original library with the replacement library will not introduce any static warnings/errors". If we can describe a relation between the original library and the replacement library that guarantees this, and which can be explained to humans, then I think we are set.I don't think that's possible in general, so we should probably make one assumption:- We assume that the replaced library is replaced everywhere in the program and with the same replacement (so we don't replace the library in one import, but import it directly in another, and then assume that the types match). That is, *all* imports/exports of a library are conditional with the same tests and replacements libraries - which has the special case of not being conditional at all - no tests and no replacements.This allows us to go from a local comparison of the relation between the original and the replacement to a global property of the program where the replacement happens. We are actually replacing *the library* with a replacement everywhere in the program.
// warn.dartif (dart.library.io) 'warn_io.dart' // intentional lack of semicolonif (dart.library.html) 'warn_html.dart';String warn(String message, {bool severe}) {if (severe) message = message.toUppercase();showMessage(message);}void showMessage(String message) {}
// warn_io.dartimplements 'warn.dart';import 'dart:io' as io;void showMessage(String message) {io.stderr.writeln(message);}
(5) If class-types are intended to allow the first class classes to be used in class-types in another library ... etc.etc. ;-)Yeah, it's a rat-hole of details.I guess is that the basic rule is that "replacing the original library with the replacement library will not introduce any static warnings/errors".
- We assume that the replaced library is replaced everywhere in the program and with the same replacement (so we don't replace the library in one import, but import it directly in another, and then assume that the types match).
import 'foo.dart' if (some.config) 'foo_other.dart' as foo_config;import 'foo.dart' as foo;import 'foo_other.dart' as foo_other;main() {foo_config.Foo f1 = new foo.Foo();foo_config.Foo f2 = new foo_other.Foo();foo.Foo f3 = new foo_other.Foo();}
So, the requirement to not introduce warnings induces some strict requirements (instance members are "identical", modulo type replacement, no new public static members) derived from the static warnings required by the language specification. It's not "it's all identical", but it is for instance members.It might actually be too much, but it would be a start.
It's not clear what we should do for programs where we do import the same library both conditionally and unconditionally, I guess it's a case of caveat-coder.
I think that the root of this problem is that we're defining a special kind of library (the interface library) without doing anything explicit in that library to mark it as being special. All of the information that it even is an interface library lives in another library.
But that isn't necessary. We could move the information about the configuration of the interface library into the interface library itself, making it impossible to violate this global property. I'm not proposing specific syntax, but for the purpose of illustration, this is what it might look like to do that (using the example from the DEP). The interface library would look something like the following:// warn.dartif (dart.library.io) 'warn_io.dart' // intentional lack of semicolonif (dart.library.html) 'warn_html.dart';String warn(String message, {bool severe}) {if (severe) message = message.toUppercase();showMessage(message);}void showMessage(String message) {}and one of the configuration specific libraries would look like:// warn_io.dartimplements 'warn.dart';import 'dart:io' as io;void showMessage(String message) {io.stderr.writeln(message);}
Tools could know that1. warn.dart was a library with configuration specific implementations,2. there are exactly two such implementations, and3. warn_io.dart should not be imported from anywhere other than 'warn.dart'.
// a.dartsomeFunction(oneArg) { ... }// b.dartsomeFunction(two, args) { ... }
import 'a.dart' if (some.config) 'b.dart';
import 'a.dart' if (config.a) 'a_and_b.dart' show a;import 'b.dart' if (config.b) 'a_and_b.dart' show b;
On Mon, Oct 26, 2015 at 2:35 AM, Lasse R.H. Nielsen <l...@google.com> wrote:(5) If class-types are intended to allow the first class classes to be used in class-types in another library ... etc.etc. ;-)Yeah, it's a rat-hole of details.I guess is that the basic rule is that "replacing the original library with the replacement library will not introduce any static warnings/errors".I think it's important to be clear that the proposal does the configuration at the import (or export) level, not the library itself. My earlier "external libraries" proposal tried to make the library itself configured, and I think that's actually a lot hairier.- We assume that the replaced library is replaced everywhere in the program and with the same replacement (so we don't replace the library in one import, but import it directly in another, and then assume that the types match).I believed we discussed this case on another thread a while back. My belief is that with the current proposal, you can directly import a library that is imported configurably elsewhere. The proposal doesn't go out of its way to prevent that.
Here's an example that, I think, covers all the cases:import 'foo.dart' if (some.config) 'foo_other.dart' as foo_config;import 'foo.dart' as foo;import 'foo_other.dart' as foo_other;main() {foo_config.Foo f1 = new foo.Foo();foo_config.Foo f2 = new foo_other.Foo();foo.Foo f3 = new foo_other.Foo();}
- Line f1 has no static warning. It works in checked mode only if some.config is false.
- Line f2 has a static warning. It works in checked mode only if some.config is true.
- Line f3 has a static warning. It does not work in checked mode.
It might be nice if line f1 had a static warning too. I think it would be possible to change the proposal to allow that by saying that a reference to a library somehow takes the configuration URIs into account when canonicalizing so tha the first two imports here are not considered the same.
But I'm also OK with not doing that and keeping things simpler. This is a powerful feature and is mainly aimed at library/framework authors. I'm fine with it having a few pointy corners if it leads to something that is both simple and expressive.So, the requirement to not introduce warnings induces some strict requirements (instance members are "identical", modulo type replacement, no new public static members) derived from the static warnings required by the language specification. It's not "it's all identical", but it is for instance members.It might actually be too much, but it would be a start.I'm totally fine with the rules being strict and simple at first. We can widen them if we need to but I don't want to speculatively.It's not clear what we should do for programs where we do import the same library both conditionally and unconditionally, I guess it's a case of caveat-coder.Exactly right."Doctor, it hurts if I do this.""Well, don't do that!"
However, I talked to Florian Loitsch and his idea of how to handle conditionally imported libraries does not have this problem.Basically, it's a "worst case" analysis - for any fixed library, the configured import *can* be different from that library, so assume that it is different. Each conditional library import/export is considered a unique library for static type checking. The types introduced by that import/export are not compatible with any other types, not even from another (conditional or not) import of the same libraries. It is as if the "conditional library choice" introduces a new library with the same "library signature" as the default library.
In practice, most conditional library choices will probably have a single conditional export in a library that everybody imports, keeping the configuration to a single place.
With the above approach, this is a static warning. The "foo_config" library is distinct from both "foo" and "foo_other" - because in practice it *can* be different from either, so the safe approach is to assume it is different from both. That will not introduce a new warning if we change the content to the "foo_other.dart" library because we already maximize the number of warnings.
However, I talked to Florian Loitsch and his idea of how to handle conditionally imported libraries does not have this problem.Basically, it's a "worst case" analysis - for any fixed library, the configured import *can* be different from that library, so assume that it is different. Each conditional library import/export is considered a unique library for static type checking. The types introduced by that import/export are not compatible with any other types, not even from another (conditional or not) import of the same libraries. It is as if the "conditional library choice" introduces a new library with the same "library signature" as the default library.
In practice, most conditional library choices will probably have a single conditional export in a library that everybody imports, keeping the configuration to a single place.
With the above approach, this is a static warning. The "foo_config" library is distinct from both "foo" and "foo_other" - because in practice it *can* be different from either, so the safe approach is to assume it is different from both. That will not introduce a new warning if we change the content to the "foo_other.dart" library because we already maximize the number of warnings.
Lasse,However, I talked to Florian Loitsch and his idea of how to handle conditionally imported libraries does not have this problem.Basically, it's a "worst case" analysis - for any fixed library, the configured import *can* be different from that library, so assume that it is different. Each conditional library import/export is considered a unique library for static type checking. The types introduced by that import/export are not compatible with any other types, not even from another (conditional or not) import of the same libraries. It is as if the "conditional library choice" introduces a new library with the same "library signature" as the default library.Just to check, rather than assume...If a single library contains both a conditional import and a conditional export of the same library with the same conditions, would they be considered to be the same library?
(Or even two conditional imports with hide/show combinators and prefixes?) I'm expecting that the answer is "yes",
and that we might even want to say that it's a static warning if the same URI appears in more than one conditional import/export (in a single library) and does not have the same conditions.
In practice, most conditional library choices will probably have a single conditional export in a library that everybody imports, keeping the configuration to a single place.If every conditional import/export effectively introduces a new URI, then I think the pain caused by having multiple conditional exports will stop anyone from doing so. :-)
With the above approach, this is a static warning. The "foo_config" library is distinct from both "foo" and "foo_other" - because in practice it *can* be different from either, so the safe approach is to assume it is different from both. That will not introduce a new warning if we change the content to the "foo_other.dart" library because we already maximize the number of warnings.The biggest disadvantage I can see is that the point at which the problem is detected can be very distant from the point at which the real problem occurred. I don't see any good alternative, though, and I think that analyzer can figure out the root cause of the problem so that we can report something useful (instead of just "the type Foo cannot be assigned to the type Foo").