simple idea for configured imports

181 views
Skip to first unread message

tatumizer-v0.2

unread,
Aug 27, 2015, 11:11:54 PM8/27/15
to Dart Misc
Here's a raw idea - we discussed something similar before, but I think prior proposals missed one simple variant.
.
The following fragment is taken from Lasse's original proposal. Exact syntax doesn't matter though.
import dart.platform == "browser"    : "some uri.dart"
    || dart.platform == "standalone" : "other uri.dart"
    || "default uri.dart"
    deferred as foo show x hide y;

I think the mistake here is an attempt to create a union of different libraries, which most likely have incompatible interfaces.

Instead, we better keep them separate by writing separate import statements and assigning different name to each library variant:
import "some uri.dart" as foo when dart.platform=="standalone";
import "other_uri.dart" as bar when dart.platform=="browser"

When we work in IDE, analyzer will (internally) load all library variants.

In the code, access to the stuff coming from conditionally imported library should be guarded by
if (imported(foo)) { // "imported" is an intrinsic function
   // use foo.anything
}
...
if (imported(bar)) {
   // use bar.anything
}

Unguarded access is an error.

Naturally, when compiler really generates the code, it ignores all libraries that don't meet import conditions, and removes corresponding (dead) parts of code.
However, in IDE, every feature continues to work, because all variants are loaded (internally), . 

Any counterexamples?
BTW, the idea is quite simple, maybe it was considered and rejected - I'd like to know why.


Erik Ernst

unread,
Aug 28, 2015, 3:50:38 AM8/28/15
to Dart Misc
This approach is indeed pleasantly simple to explain.

But I think the proposal has one crucial problem: It requires the canonical library (the one that contains the configurable `import .. || .. || ..` directive) to explicitly depend on all the possible configurations, and it causes the configuration dependent pieces of code to be completely tangled into each other. You'd have to edit many bits and pieces of that library to make it work again if you add an extra configuration.

Because of that, I'd prefer an approach where each configuration dependent library ('some_uri.dart' and 'other_uri.dart') must conform to a common library signature (different proposals have different notions of 'library signature'), and the canonical library relies on that signature in order to use the configuration dependent features. This will allow for a high degree* of encapsulation of the configuration dependencies, and it is one of the well-established principles in software that encapsulation based on shared and explicit interfaces/signatures/contracts is good for comprehensibility, maintainability, reusability, you-name-it-ility.

* Footnote: Of course, you can always break the encapsulation semantically, e.g., by implementing a toplevel `isBrowser` function that returns true with 'other_uri.dart' and false with 'some_uri.dart', and then do different things in canonical code based on that. But you _can_ get a high degree of encapsulation if you try. ;-)

  best regards,

--
Erik Ernst  -  Google Danmark ApS
Skt Petri Passage 5, 2 sal, 1165 København K, Denmark
CVR no. 28866984

Lasse R.H. Nielsen

unread,
Aug 28, 2015, 3:54:35 AM8/28/15
to mi...@dartlang.org
On Fri, Aug 28, 2015 at 5:11 AM, tatumizer-v0.2 <tatu...@gmail.com> wrote:
Here's a raw idea - we discussed something similar before, but I think prior proposals missed one simple variant.
.
The following fragment is taken from Lasse's original proposal. Exact syntax doesn't matter though.
import dart.platform == "browser"    : "some uri.dart"
    || dart.platform == "standalone" : "other uri.dart"
    || "default uri.dart"
    deferred as foo show x hide y;

I think the mistake here is an attempt to create a union of different libraries, which most likely have incompatible interfaces.

Instead, we better keep them separate by writing separate import statements and assigning different name to each library variant:
import "some uri.dart" as foo when dart.platform=="standalone";
import "other_uri.dart" as bar when dart.platform=="browser"

When we work in IDE, analyzer will (internally) load all library variants.

In the code, access to the stuff coming from conditionally imported library should be guarded by
if (imported(foo)) { // "imported" is an intrinsic function

That's unnecessary (and "being guarded by" is likely to be a Turing complete problem to decide exactly, so we'll have some approximation only).
We already have a way to talk about a library that may or may not be loaded - it's a deferred library. In this case it'll just not have a loadLibrary function because it's always loaded from the start or never loaded at all.
The restrictions on using a deferred library are those that are necessary to ensure you don't depend on something that isn't there (don't use the types as types or constants as constants), so we'll likely need the same restrictions here anyway.

 
   // use foo.anything
}
...
if (imported(bar)) {
   // use bar.anything
}

Unguarded access is an error.

Too complex. Just say that using the prefix when it isn't loaded, is an error, like deferred libraries.
You can do the same test as the import if you want to guard access.
 

Naturally, when compiler really generates the code, it ignores all libraries that don't meet import conditions, and removes corresponding (dead) parts of code.
However, in IDE, every feature continues to work, because all variants are loaded (internally), . 

Any counterexamples?
BTW, the idea is quite simple, maybe it was considered and rejected - I'd like to know why.

It has been considered. but isn't universally loved.
The arguments against it is that:
- the code for both versions have to go in the same library, so there is no platform separation. 
- The libraries will be treated as deferred loaded (that's one choice, but it keeps things simple). That means you can't extend the types of the library, nor can you use them as type assertions.

I personally like the idea because it's simple and uses only existing concepts. However, the existing concept it depends on is deferred loading which I think is an ugly wart on the language that should die a thousand deaths, so ... maybe it's not that good an idea anyway.

/L
--
Lasse R.H. Nielsen - l...@google.com  
'Faith without judgement merely degrades the spirit divine'
Google Denmark ApS - Frederiksborggade 20B, 1 sal - 1360 København K - Denmark - CVR nr. 28 86 69 84

Benjamin Strauß

unread,
Aug 28, 2015, 4:10:07 AM8/28/15
to Dart Misc
It's really a shame that we don't have first class classes (or rather top level classes as libraries). That would simplify the whole subject. I have nothing against these proposals, but to me it looks like all these solutions will just clutter up the language and make it more complex for the developers. :(

tatumizer-v0.2

unread,
Aug 28, 2015, 12:27:30 PM8/28/15
to Dart Misc

@Erik: If I understand you correctly, the main concern is that the proposed solution doesn't "scale" well for N variants where N is large. I agree with that. But if typical value of N is 2, we don't care much. And 2 is exactly what we have in the main motivating example (browser vs standalone).

But what is the alternative? Implement common denominator? If libraries were not designed to conform to common "interface" (I'm using the term loosely), is it realistic to be able to shoehorn them into common interface, even for N=2? And in a (theoretic) case of larger N - what happens with common denominator?  It might quickly become empty, so we end up with having to define a kind of "union" (instead of denominator) where some methods in concrete variants will just throw "not implemented". On top of that, slight differences in underlying libraries can make even common methods incompatible. And/or it becomes exponentially more difficult to define such common interface to begin with. Etc...

Again, this is all "in theory". In practice, we may not have large N.

> ... you can always break the encapsulation semantically, e.g., by implementing a toplevel `isBrowser` function
> ...But you _can_ get a high degree of encapsulation if you try. ;-)

Well... you try, then try harder, then you have terrible headache and decide to resort to "isBrowser" logic, and now all your critique applies anyway, just one step down the road?

@Lasse: yeah, "error on unguarded access" was not a good idea, I take it back :)
And I forgot about "deferred", it's really painful to think about combination of deferred and conditional import.

Anyway, my point is that there might be a simple solution that covers some common use cases. For alternative more complicated solutions nothing much can be said at the moment, except that they are more complicated :)

 






 

Erik Ernst

unread,
Aug 28, 2015, 1:31:32 PM8/28/15
to Dart Misc
On Fri, Aug 28, 2015 at 6:27 PM, tatumizer-v0.2 <tatu...@gmail.com> wrote:

@Erik: If I understand you correctly, the main concern is that the proposed solution doesn't "scale" well for N variants where N is large. I agree with that. But if typical value of N is 2, we don't care much. And 2 is exactly what we have in the main motivating example (browser vs standalone).

The main point is encapsulation and well-defined interfaces, which doesn't take a large N to matter. It does get even worse with a large N because the tangled code gets even more tangled, but already two variants give rise to code where two mindsets must be kept ready-to-mind at all times. I do think that the quality of the thinking and of the resulting software will be higher if the amount of tangling (syntactically and semantically) can be kept down.

But what is the alternative? Implement common denominator?

Create an abstraction (library signature) which is powerful enough to enable the configuration independent code to be expressed, and then implement that interface in each configuration dependent setting. It's a common denominator, and it's the lowest one that will work, but no lower than that.

Abstraction often yields more concise and readable code, but sometimes it will give rise to duplication (a method body `doSomething(); if (isBrowser) {../*1*/..} else {../*2*/..}` becomes `doSomething(); ../*1*/..` in the browser variant and `doSomething(); ../*2*/..` in the other variant, which duplicates `doSomething()`). But then we'll just have to use standard techniques for eliminating code duplication (e.g., we could have a configuration independent method calling `doSomething(); m(..);` where `m` is a new method whose implementation is configuration dependent, effectively `../*1*/..` resp. `../*2*/..`, depending on the chosen variant).

That's basically a matter of introducing more abstraction, such that the parts which are actually configuration independent are allowed to be so also technically. I wouldn't be surprised if the resulting software turned out to have a better structure than it used to have.

If libraries were not designed to conform to common "interface" (I'm using the term loosely), is it realistic to be able to shoehorn them into common interface, even for N=2?

I do think that the cases where you can just write `if (isConfig1) {..} else if (isConfig2) ..` will be rather easy to improve such that the canonical code is highly configuration independent, and even when you do need to do a bit of shoehorning it's for a good purpose.

Conversely, do you really think that it's good from a software engineering point of view to avoid shoehorning entirely, i.e., to let every little configuration dependent quirk be visible in your canonical library?

And in a (theoretic) case of larger N - what happens with common denominator?  It might quickly become empty, so we end up with having to define a kind of "union" (instead of denominator) where some methods in concrete variants will just throw "not implemented". On top of that, slight differences in underlying libraries can make even common methods incompatible. And/or it becomes exponentially more difficult to define such common interface to begin with. Etc...

If no abstraction (toward a common library signature) is possible then you simply have N completely different programs. No need to try to shoehorn them into the same codebase. ;-)

Again, this is all "in theory". In practice, we may not have large N.

> ... you can always break the encapsulation semantically, e.g., by implementing a toplevel `isBrowser` function
> ...But you _can_ get a high degree of encapsulation if you try. ;-)

Well... you try, then try harder, then you have terrible headache and decide to resort to "isBrowser" logic, and now all your critique applies anyway, just one step down the road?

I expect that such an `isBrowser` condition in the code would be rare, especially if the code has been structured well. ;-)

tatumizer-v0.2

unread,
Aug 28, 2015, 3:39:20 PM8/28/15
to Dart Misc
> Conversely, do you really think that it's good from a software engineering point of view to avoid shoehorning entirely, i.e., to let every little configuration dependent quirk be visible in your canonical library?

No, God forbid! I definitely see your point. I am just not sure how it will work in practice.
Maybe I should have asked more questions before proposing anything :)
E.g. there's a question of "library interface". What is it? Language doesn't define such thing. Library cannot say "I'm implementing interface Foo". So compiler will somehow derive "library interface" by analyzing alternatives in "import" statement? How exactly? What if some top-level definitions are present in one, and missing in another? Or have different signatures? Is it an error? Otherwise, how IDE will compute suggestions and detect errors?

Finally, if "library interface" gets defined, then we might want to treat those interfaces generically, as first-class constructs (e.g. we could pass references to libraries around etc). The thing is: right now, it all looks like a riddle. "Library interface" is either a worthy notion, or it is not. If it is, then we have to define it before conditional import (which is just an application of a concept).

Thanks,
Alex


 


 

Erik Ernst

unread,
Aug 28, 2015, 4:26:53 PM8/28/15
to Dart Misc
On Fri, Aug 28, 2015 at 9:39 PM, tatumizer-v0.2 <tatu...@gmail.com> wrote:
> Conversely, do you really think that it's good from a software engineering point of view to avoid shoehorning entirely, i.e., to let every little configuration dependent quirk be visible in your canonical library?

No, God forbid!

;-)
 
I definitely see your point. I am just not sure how it will work in practice.
Maybe I should have asked more questions before proposing anything :)
E.g. there's a question of "library interface". What is it? Language doesn't define such thing.

I'm sorry, I should have defined some terms more explicitly (there are several places where similar topics are being discussed). There is no notion of a library interface in Dart, but I'd argue that it should be considered, some approach to that issue should be chosen (for me, preferably: library signatures should be well-defined, simple, and explicit), and then that should be the basis for the treatment of configuration dependencies.

A very simple approach would be to say that a library interface is a set of toplevel non-type entities, typed with types that are available from outside the configuration dependent libraries. (That's what I have proposed for a long time; see https://github.com/eernstg/dep-configured-imports/blob/master/DEP-configured-imports.md).

Another approach would be to say that a library signature is (essentially) a library S that contains no implementation. Any library L that has an implementation "but is reduced to S when the implementation is erased" conforms to S. It's a long, long, long discussion whether or not that's simple. ;-)

You could of course also introduce an entirely separate notion of a library signature as in SML for structs and signatures, where functors create the need for first class libraries such that explicit signatures are unavoidable.

But nobody is really in love with the idea that we'd bury Dart in a lot of extra typing machinery in order to get support for `dart:io` and `dart:html` in a shared piece of code. ;^)

Library cannot say "I'm implementing interface Foo".

It could, if we were to make those interfaces explicit.
 
So compiler will somehow derive "library interface" by analyzing alternatives in "import" statement? How exactly? What if some top-level definitions are present in one, and missing in another? Or have different signatures? Is it an error? Otherwise, how IDE will compute suggestions and detect errors?

That's also a very long discussion. Try to check out https://github.com/dart-lang/dart_enhancement_proposals/issues for other proposals.

My motivation for preferring a no-types-exported approach is exactly that any kind of structural equivalence that would make types exported from one variant interchangeable with types exported by another variant is an alien element in a language where type equivalence is otherwise nominally based. Without that element it's much simpler. Surprisingly, you can still do a lot. ;)

Finally, if "library interface" gets defined, then we might want to treat those interfaces generically, as first-class constructs (e.g. we could pass references to libraries around etc). The thing is: right now, it all looks like a riddle. "Library interface" is either a worthy notion, or it is not. If it is, then we have to define it before conditional import (which is just an application of a concept).

Sure, but that's not for free: The complexity of the language and the conceptual framework that a programmer would need to build around the language and its type system would go up, and not trivially so... 

tatumizer-v0.2

unread,
Aug 29, 2015, 11:18:24 AM8/29/15
to Dart Misc
Went through proposals/comments. The impression is that people can't even agree on a problem we are trying to solve.
Some want configurable implementation of known interface. Others want to just selectively load libraries which they need in particular configuration, without assumption of common interfaces (e.g. when run as standalone, I want library X, otherwise some completely different Y, or even nothing at all).

Unfortunately, I can't find any mention of major use case that immediately comes to mind: Suppose dart wants to support configurable drivers - e.g something similar to JDBC drivers. Not sure any current proposal deals with the situation. Is it a different problem, or a variant of the same? Maybe if we solve it, it automatically solves other problems, too?

BTW, with JDBC, it's generally impossible to specify the libraries statically. Implementations are loaded dynamically via Class.forName() and become part of infrastructure; particular implementation is chosen based on url prefix. Maybe it makes sense to generalize this setup and see where it leads? Dart has to address this problem sooner or later anyway, why not now?

NOTE: there's no assumption that in particular run, we want just one of N drivers - in fact, we may need a subset of them.
 
 

tatumizer-v0.2

unread,
Aug 30, 2015, 10:12:58 PM8/30/15
to Dart Misc
@Erik:
Upon more thinking: maybe JDBC interface provides a universal template for the solution?
The design has a shape of lab flask with narrow neck and wide body. Initially, we retrieve Connection object. Everything else we get from it (there's a lot of stuff there). No public constructors - instead, requests to Connection  are used to create/retrieve objects.

It would be much more difficult to implement (or even understand)  were it structured differently.

If the pattern is universal (hard to prove, but it very well might be the case), this simplifies things a lot IMO.


Erik Ernst

unread,
Aug 31, 2015, 5:17:46 AM8/31/15
to Dart Misc
On Sat, Aug 29, 2015 at 5:18 PM, tatumizer-v0.2 <tatu...@gmail.com> wrote:
Went through proposals/comments. The impression is that people can't even agree on a problem we are trying to solve.

I think you are right that the problem(s) might have been stated more explicitly in those proposals. But one main motivation is to enable cross-platform software, that is, code that is configuration-independent, but capable of using (importing) configuration-dependent features such as 'dart:io' and 'dart:html' to solve "the same problem" in two different ways.

It's easy for two different programs to import the same library, which means that configuration dependencies can easily be expressed at the beginning of the dependency graph (from "main" and onward to its imports), but it is impossible (without some type of configurable imports) to have configuration independent code at the beginning of the dependency graph and configuration dependent code further down. Cross-platform software is an example of this.

Some want configurable implementation of known interface. Others want to just selectively load libraries which they need in particular configuration, without assumption of common interfaces (e.g. when run as standalone, I want library X, otherwise some completely different Y, or even nothing at all).

Different language mechanisms, same purpose.

Unfortunately, I can't find any mention of major use case that immediately comes to mind: Suppose dart wants to support configurable drivers - e.g something similar to JDBC drivers. Not sure any current proposal deals with the situation. Is it a different problem, or a variant of the same? Maybe if we solve it, it automatically solves other problems, too?

You might very well want to create a product line of slightly different programs where the choice of database driver is one of the dimensions of configuration.

However, that particular example doesn't actually require configurable imports: If you have a set of `...DatabaseDriver` classes and make the choice among them using a `myDatabaseDriver = new SomeDataBaseDriver()` somewhere in your code and then use `myDatabaseDriver` throughout your program (suitably hidden behind some nice abstractions), then you would be able to use one of many implementations of the same interface. You could also make that choice dynamically, and you could use several different implementations at the same time. If you want to avoid using space in your runtime for all those variants then you can access them as `deferred` libraries.

So, even though you might want to use configurable imports also in this case, it's not one of those cases where configurable imports are required. In general, configurable import is not required if the code that you wish to use has been made available as variants (subtypes) of a given, shared abstraction.

They are required in the case where certain libraries are available only in some cases (e.g., only on some platforms: browser, vm, sky, ..), because then your program just won't compile if your source code contains an `import` (deferred or not) of the complete set of variants.

You could provide support for 'dart:html' on the vm or 'dart:io' in the browser (this is indeed one of the proposals for configurable imports: just make sure everything is available everywhere, even if every use of 'dart:html' in the vm might throw), but that isn't very scalable. Hence the other proposals.

BTW, with JDBC, it's generally impossible to specify the libraries statically. Implementations are loaded dynamically via Class.forName() and become part of infrastructure; particular implementation is chosen based on url prefix. Maybe it makes sense to generalize this setup and see where it leads? Dart has to address this problem sooner or later anyway, why not now?

Dart does not support dynamic loading in an isolate (you can start a new one on new code), presumably because it is crucial for space efficiency that the complete set of dependencies in a program can be assessed by the compiler (in particular, dart2js) such that "tree shaking" can be performed and dead code eliminated.

But if you have a finite and known list of variants, you can still specify them as `deferred` imports and get the choice at runtime as well as the tree shaking.

NOTE: there's no assumption that in particular run, we want just one of N drivers - in fact, we may need a subset of them.

`deferred` imports wouldn't have a problem with that, either.

Bob Nystrom

unread,
Aug 31, 2015, 12:28:54 PM8/31/15
to General Dart Discussion
On Sat, Aug 29, 2015 at 8:18 AM, tatumizer-v0.2 <tatu...@gmail.com> wrote:
Went through proposals/comments. The impression is that people can't even agree on a problem we are trying to solve.
Some want configurable implementation of known interface. Others want to just selectively load libraries which they need in particular configuration, without assumption of common interfaces (e.g. when run as standalone, I want library X, otherwise some completely different Y, or even nothing at all).

Right. There seems to be a pretty linear graph where one axis is expressiveness and the other is complexity. We have proposals for very simple solutions that can't express some interesting use cases, and we have complex proposals that can express a lot. There isn't any clear hump in the graph where one proposal has an obviously better power/weight ratio.

Personally, I think Florian's "interface libraries" proposal does stand out in terms of what it buys you versus what it costs.
 

Unfortunately, I can't find any mention of major use case that immediately comes to mind: Suppose dart wants to support configurable drivers - e.g something similar to JDBC drivers. Not sure any current proposal deals with the situation. Is it a different problem, or a variant of the same? Maybe if we solve it, it automatically solves other problems, too?

For any configuration problem that you can solve at runtime, Dart already has you well covered. We have imperative code, control flow, interfaces, libraries etc. All sorts of fun features for encapsulation, dynamism, and choosing 1 of N behaviors.

The problem is that imports are not imperative. You can't dynamically control an import, and we have some magical imports ("dart:io", "dart:html", etc.) that prevent your program from even loading, much less running.

All of these proposals exist to work around that last problem. Because you can't even start main(), you don't have access to all of the existing language features for dynamism and selecting different code paths. That means we have to add some new language feature to work around those imports.

Cheers!

- bob

tatumizer-v0.2

unread,
Aug 31, 2015, 12:35:00 PM8/31/15
to Dart Misc

> If you have a set of `...DatabaseDriver` classes and make the choice among them using a `myDatabaseDriver = new SomeDataBaseDriver()` somewhere in your code and then use `myDatabaseDriver` throughout your program

That's exactly my point! A large class of use cases can be trivially reduced to this one!

Let's recall where we started.

Suppose we have 2 libraries X and Y that don't implement the same interface, but we think it's possible to find common denominator and abstract away all (or most) of the differences.
In which case we still need to write wrappers (X1 and Y1 respectively) that implement "common denominator" interface.

How do we structure the implementations of X1 and Y1? This can be done in different ways, but the simplest one is to follow the "driver" patters: X1 and Y1 each expose a single object (e.g. "driverInstance" - a singleton), which implement common interface (defined elsewhere) - and nothing else.
All top-level methods of libraries X and Y now become methods of driver instance. In practical terms of usage, it doesn't make much difference.

E.g. where you previously used to write "var st=new xlib.PreparedStatement()", you now write "var st=xdriver.newPreparedStatement()" - syntactically, it looks just as a different format of constructor invocation. After "new" becomes optional, even this difference (in principle) can be eliminated: instead of "var st=xlib.PreparedStatement", you write "var st=xdriver.PreparedStatement"  - the latter is just a function call that pretends to be constructor call (not sure this is a good style, but certainly a possibility).

Now, it all plays very nicely with conditional import (the only proposed earlier in this thread).

import "x1" when platform=="x" as x1lib;
import "y1" when platform=="y" as y1lib;
...
CommonDenominatorDriver myDriver=(x1lib.imported? xlib1.driverInstance: ylib1.driverInstance);

Similarly, if conditions of imports are not mutually exlusive (e.g. we may have N different drivers on one platform, and M different drivers on other), we build a map:

var myDrivers={};
if (x1lib.imported) myDrivers["x1"]=x1lib.instance;
if (y1lib.imported) myDrivers["y1"]=y1lib.instance;

(This can be further improved to make initialization of the map automatic, like JDBC does. But the point is clear already).

So we covered a case when 2 libraries CAN be shoehorned into same interface
Which leaves us with the case when we need to conditionally import xlib on platform X, and ylib (or even nothing) on platform Y.
Then you don't have to redesign anything, just use conditional import as above (with different library names), and then check "if (xlib.imported)"

This all doesn't require neither definition of "library interface" (which would be difficult anyway), or even the creative use of "external" (as per Bob's idea). Nothing but the simplest form of conditional import that just adds "when" clause to existing syntax, that's it.

How about that?


Alex Tatumizer

unread,
Aug 31, 2015, 12:55:11 PM8/31/15
to mi...@dartlang.org
@Bob: sorry I received your message after I hit "send", but it seems that we don't even need "library interface" or anything.
Simple concept of "driver"  plus simple conditional import (as defined above) can take us a long way.
What would be good though is to have language feature like "static" block in java which automatically executes some library code on load. We can do without it, but it would be just nicer to have it.

Bob Nystrom

unread,
Aug 31, 2015, 4:43:58 PM8/31/15
to General Dart Discussion
On Mon, Aug 31, 2015 at 9:55 AM, Alex Tatumizer <tatu...@gmail.com> wrote:
@Bob: sorry I received your message after I hit "send", but it seems that we don't even need "library interface" or anything.
Simple concept of "driver"  plus simple conditional import (as defined above) can take us a long way.

Yes, that's definitely one of the ideas that's floated around. Basically like a "deferred" import, but synchronous.

It is the simplest of all of the proposals, but also the most limited. In particular, it means you cannot extend a configuration-specific class.

The interface library proposal lets you do stuff like:

// http.dart
class Http {}

// http_io.dart
import 'dart:io' as io;

class Http {
  io.HttpClient _client;
  ...
}

// foo_html.dart
import 'dart:html' as html;

class Http {
  html.HttpRequest _request;
  ...  
}

// main.dart
// (made up syntax...)
import 'http.dart'
    if (dart.io) 'http_io.dart'
    if (dart.html) 'http_html.dart';

class MyHttp extends Http {
  ...
}

Here, when you run the program on the standalone VM, MyHttp will have a field storing a dart:io HttpClient. On the browser, it will have an HttpRequest.

Cheers!

- bob

tatumizer-v0.2

unread,
Aug 31, 2015, 11:17:56 PM8/31/15
to Dart Misc
> In particular, it means you cannot extend a configuration-specific class.
Sure, this is a valid argument, but it's not a very strong one IMO. You can always wrap another object around driver instance - e.g. using delegation. Not a big deal. There's lots of situations where you may need to do it anyway. E.g. library method returns result of type IceCream, but in your application, you never want plain IceCream - you want FancyIceCream instead. What can you do about it? Use wrapper:: 
var fcream=new FancyIceCream(foo.getIceCream());  

It's all subjective, of course, but in reference frame where I live, you need 100 arguments like this to beat simplicity of driver pattern. 
Anyway, I think I made my point, let's move on :-)





Erik Ernst

unread,
Sep 1, 2015, 8:18:56 AM9/1/15
to Dart Misc
On Mon, Aug 31, 2015 at 10:43 PM, 'Bob Nystrom' via Dart Misc <mi...@dartlang.org> wrote:
On Mon, Aug 31, 2015 at 9:55 AM, Alex Tatumizer <tatu...@gmail.com> wrote:
@Bob: sorry I received your message after I hit "send", but it seems that we don't even need "library interface" or anything.
Simple concept of "driver"  plus simple conditional import (as defined above) can take us a long way.

Yes, that's definitely one of the ideas that's floated around. Basically like a "deferred" import, but synchronous.

It is the simplest of all of the proposals, but also the most limited. In particular, it means you cannot extend a configuration-specific class.

The interface library proposal lets you do stuff like:

// http.dart
class Http {}

// http_io.dart
import 'dart:io' as io;

class Http {
  io.HttpClient _client;
  ...
}

// foo_html.dart

I guess that's intended to be 'http_html.dart'.
 
import 'dart:html' as html;

class Http {
  html.HttpRequest _request;
  ...  
}

// main.dart
// (made up syntax...)
import 'http.dart'
    if (dart.io) 'http_io.dart'
    if (dart.html) 'http_html.dart';

class MyHttp extends Http {
  ...
}

Here, when you run the program on the standalone VM, MyHttp will have a field storing a dart:io HttpClient. On the browser, it will have an HttpRequest.

[Just to make sure that nobody will run out of arguments against my preferred solution, here's a case that is hard to cover without `extends` on configuration dependent classes ;-D ]

Basically, the easy work-around that you could use if you _don't_ have support for subclassing a configuration dependent class is forwarding (that is, almost-delegation): You create a new class `MyHttpBasedOnForwarding` that implements whatever needed (presumably `implements Http` will do), and then you obtain a configuration dependent object (`new Http()`, evaluating to an instance of "http_io.Http" respectively "http_html.Http", depending on the configuration), store it in a private instance variable, and forward requests to it, adding whatever modifications you might need.

However, this setup has the well-known "SELF problem" (Lieberman, OOPSLA 1986: 'Using Prototypical Objects to Implement Shared Behavior in Object Oriented Systems'), which means that it breaks as soon as there is a "self send". For instance, if we wish to add logging (wow, new idea ;-) to a method implemented by the configuration dependent `Http` classes, then we can log the direct calls, but we will miss out on the internal calls on the configuration dependent instance. The obvious remedy would be to have a real inheritance relation rather than a forwarding relation.

  ...

Thinking about this once more, we would actually have a solution in the 'sub_canonical.dart' approach that I've already advocated a few times (where the client would create a subclass of each configuration dependent class in a configuration dependent setting, and deliver instances in a configurably imported manner).

With that approach, you would simply need to share some material among the configuration dependent subclasses, and that could nicely be done by mixin application. The mixin would be configuration independent, and its implementation would be available for each of the configuration dependent settings by mixin application (or just some of them, if not every one will work --- which is a case that direct support for inheritance from "any class in a set of configuration dependent classes" would not be able to handle).

We'd get real inheritance rather than forwarding, we get simple inheritance rather than magic near-dynamic mixin application, and it's an approach that any client could use if they need it.

[So maybe that didn't help: it wasn't such a hard case after all ;-) ]

  best regards,

tatumizer-v0.2

unread,
Sep 1, 2015, 11:36:10 PM9/1/15
to Dart Misc
It appears that I gave up too soon. I looked into definitions of HttpClient  and HttpRequest in dart API - these classes are completely different. @Bob: If you want to demonstrate the virtues of your idea, you better find another example. It's easier to find common interface between the Horse the the Cow than between these 2 classes. Common denominator will have to throw away so many fine details that remaining definitions will not be good for anything.

Which reminds me an old anecdote about math professor who defined a class of objects axiomatically and throughout his career, proceeded to prove many wonderful theorems about said objects - until, many years later, somebody showed that the axioms are contradictory to begin with, so the class is empty.

I'm back to my proposal - which I restate here in a condensed form:
1) add "when condition" clause to "import" statement
2) add method "imported()" for a library

What not to love here?

Bob Nystrom

unread,
Sep 2, 2015, 11:47:13 AM9/2/15
to General Dart Discussion

On Tue, Sep 1, 2015 at 8:36 PM, tatumizer-v0.2 <tatu...@gmail.com> wrote:
It appears that I gave up too soon. I looked into definitions of HttpClient  and HttpRequest in dart API - these classes are completely different. @Bob: If you want to demonstrate the virtues of your idea, you better find another example.

No, this is a real-world example. It's exactly what the http package does.

I just didn't clarify the details. The idea is not that you would extend HttpClient and HttpRequest. It's that you can define your own Http classes, one that wraps HttpClient and one that wraps HttpRequest. Those Http classes expose a unified interface.

The interesting bit is that if you extend Http class, at runtime your superclass is the actual configuration-specific Http wrapper class for that platform.

Your proposal, and other similar ones can't express that.

- bob

Alex Tatumizer

unread,
Sep 2, 2015, 1:20:31 PM9/2/15
to mi...@dartlang.org
> It's that you can define your own Http classes, one that wraps HttpClient and one that wraps HttpRequest. Those Http classes expose a unified interface.

I already understand that. But do they expose specific methods, too? E.g., onProgress, authenticateProxy, badProxyCertificateCallback and other 20 methods and properties that are present in one variant and missing in another? If not, the thing is not usable: in the next version, you might need "onProgress" while running in browser, and there's no such method in the common interface. If you do expose them, the abstraction is very leaky, you need a lot of "isBrowser" logic, so we are back to square 1. The fact that you can "extend" this attempted "abstraction" (which is your main argument) doesn't make it any better: whoever uses extended class, will need same "isBrowser" logic.

BTW, I'm not sure your proposal allows exposing specific methods. If it doesn't, it's a major issue: what is the point to be able to "extend" something that is no good right from the start? Or maybe you have some magic way of dealing with the problem, but you didn't explain it yet?



Alex Tatumizer

unread,
Sep 2, 2015, 2:59:16 PM9/2/15
to mi...@dartlang.org
Interestingly, the argument I'm trying to make was  already submitted as an issue: https://github.com/munificent/dep-external-libraries/issues/1

And it's the SAME argument.

Now, the whole story looks like this: somebody asked to support configuration-specific imports. Very modest request. Instead, your proposal deals with a problem: to support configuration-specific import of a variant of common interface obtained by discarding all functionality present on one platform and absent on another (which, BTW, can lead to empty common interface, but it's beside the point).

Now someone objects that it doesn't address the original issue of importing configuration-specific library. You objection is: "Sure, but look at this nice "extend" over here!!!"

Extend what?

Sending http request from browser is not the same as sending it between servers. It's not the issue of different names of functions - it's different set of features and different workflow.




Bob Nystrom

unread,
Sep 2, 2015, 3:32:08 PM9/2/15
to General Dart Discussion
On Wed, Sep 2, 2015 at 10:20 AM, Alex Tatumizer <tatu...@gmail.com> wrote:
I already understand that. But do they expose specific methods, too? E.g., onProgress, authenticateProxy, badProxyCertificateCallback and other 20 methods and properties that are present in one variant and missing in another?

Yes, it will expose its own platform-independent set of methods. For the http package, here is the Client class's API. Most of the API is supported on both platforms. In some cases, you might have a method that throws UnsupportedError on some configuration.
 
If not, the thing is not usable: in the next version, you might need "onProgress" while running in browser, and there's no such method in the common interface. If you do expose them, the abstraction is very leaky, you need a lot of "isBrowser" logic, so we are back to square 1.

Sure, real differences between platforms exist. When you can, you expose interfaces for the intersections of their features. When you can't, you either don't support the feature, or only support it on some platforms.


On Wed, Sep 2, 2015 at 11:59 AM, Alex Tatumizer <tatu...@gmail.com> wrote:
Interestingly, the argument I'm trying to make was  already submitted as an issue: https://github.com/munificent/dep-external-libraries/issues/1

Yes. Some of what Natalie's asking for—configuration-specific static analysis—we don't intend to support and almost none of the proposals can handle.

I believe most of what she wants can be accomplished, though it sometimes requires an extra import if you want to statically see that you are calling something configuration-specific.
 


And it's the SAME argument.

Now, the whole story looks like this: somebody asked to support configuration-specific imports. Very modest request.

I will note that that "someone" was me. Natalie and I were, as far as I know, the first people to bring up configuration-specific code in Dart, well before 1.0. Possibly even before the public release but my memory fades.

Instead, your proposal deals with a problem: to support configuration-specific import of a variant of common interface obtained by discarding all functionality present on one platform and absent on another (which, BTW, can lead to empty common interface, but it's beside the point).

Now someone objects that it doesn't address the original issue of importing configuration-specific library. You objection is: "Sure, but look at this nice "extend" over here!!!" 

The original issue has always been how to write code that encapsulates the use of a "dart:" library that isn't available on all platforms in such a way that the code can be run on a platform where that library isn't available. There are a lot of different mechanisms to accomplish that. Making the import of that "dart:" library itself optional is one. There isn't a fully-written proposal for "optional imports" like this, but it's been discussed.

The feeling is that its likely too limited. Even if it isn't, it doesn't seem able to grow as well as the "interface library" proposal that's the current front-runner. Future-compatibility matters—we don't want to be stuck with a feature that's a dead end when new needs appear.

Cheers,

- bob

Reply all
Reply to author
Forward
0 new messages