Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Less punctuation? #3

Open
munificent opened this issue Mar 4, 2015 · 27 comments
Open

Less punctuation? #3

munificent opened this issue Mar 4, 2015 · 27 comments

Comments

@munificent
Copy link

The || and : seem strange and arbitrary to me. I don't think a random Dart user could easily intuit what they do, especially since their meaning is different from how those operators are understood in other contexts.

However, there is already a language feature that:

  1. Has a series of constant expressions.
  2. Tests them in order.
  3. Evaluates some other code on the first on that matches.
  4. Allows a default when nothing matches.

How about we leverage that?

import case dart.platform == "browser": "some_uri.dart"
       case dart.platform == "standalone": "other_uri.dart"
       default: "default_uri.dart"
    deferred as foo show x hide y;

What do you think?

@lrhn
Copy link
Owner

lrhn commented Mar 4, 2015

The syntax was chosen to not match existing Dart expression syntax too much, because it isn't an expression and we don't want users to think that it is.
That ruled out "if" and "?:" notations.

This suggestion is better. It is slightly non-Dart'ish in that you can use expressions in the case part, but it's still readable, and it matches the colon well.
I don't mind the extra typing - most users should never have to write an import like this.

@munificent
Copy link
Author

The syntax was chosen to not match existing Dart expression syntax too much, because it isn't an expression and we don't want users to think that it is.

This is always the tricky choice. Do we make it similar to an existing construct to highlight the similarities or different to highlight the differences?

In this case, I think we can do better than using || and :. Those will imply some kind of expression semantics to the user, but whatever they imply will probably just confuse them. Using case is a bit squishy here because, like you note, the clause isn't a statement, but I think the behavior is close enough to a normal switch statement to make the similarity have positive value.

@sethladd
Copy link

sethladd commented Mar 4, 2015

  import "some_uri.dart" when dart.platform == "browser"
             or "other_uri.dart" when dart.platform == "standalone"
             otherwise "default_uri.dart";

Benefits:

  • Reads like someone would say it outloud
  • Doesn't look too much real code, so it's not confused to be real code

(do I really need the or there?)

@munificent
Copy link
Author

I like the idea of the condition being "postfix", after the import. I considered something like when, but I think it reads weird when the condition is just a constant, which I think will be the most common usage:

  import "some_uri.dart" when dart.feature.html
             or "other_uri.dart" when dart.feature.io
             otherwise "default_uri.dart";

The or isn't strictly needed, but it could look really weird on a one-line import without it:

  import "a.dart" when dart.feature.html "b.dart" when dart.feature.io;

@austincummings
Copy link

How about something like this?

browser import "a.dart";
local import "b.dart"; // I'm unsure about `local`

@munificent
Copy link
Author

The set of configurations (browser, local, etc.) isn't fixed in the language grammar, so we can't treat then as if they were reserved words. In particular, this proposal allows user-defined configuration constants, so we need to make sure the grammar can handle that.

@tatumizer
Copy link

The syntax was chosen to not match existing Dart expression syntax too much, because it isn't an expression and we don't want users to think that it is. That ruled out "if" and "?:" notations.

All variants proposed above, including original one, look foreign and artificial in a context of dart IMO. I think the choice of ?: would be preferable, just because it's familiar and natural. Syntax of it can be restricted in this directive. But that doesn't mean one needs completely different notation.
It's a problem of lesser evil; adding more and more eclectic notations is an easy choice, but it destroys the style.

If you do want to choose a different notation, please think of other examples where new syntax can be reused in the future. If you can find a pattern, it will be a good argument.

@tatumizer
Copy link

Another approach to deriving this syntax.
Let's focus on parts that we already know:
import ... deferred as foo show x hide y;
Syntax obviously generalizes as import (unknown part) [keyword [value]]*
I think it comes to mind to try to express unknown part in the same manner - as [keyword [value]]*

Following the pattern, we get something like this
import select "some_uri.dart" when dart.platform=="browser"
select "other_uri.dart" when dart.platform=="standalone"
select "default_uri.dart"
deferred as foo show x hide y;
Here we just introduce new keywords ("select" and "when"), but there's no new syntax per say.

@tatumizer
Copy link

BTW, if we are unwilling to use ?: just because it looks like expression, same argument applies to ==.
Following this logic, someone may think this is a general logical expression that entitles him to use wide range of logical operators, e.g.
import dart.platform == "browser" || dart.platform=="standalone" : "some uri.dart"
deferred as foo show x hide y;

But it turns out, || means something completely different in a new syntax.

@zoechi
Copy link

zoechi commented Mar 6, 2015

Any arguments against

import 'package:somepackage/somelib_[browser|standalone].dart';

which imports in case of platform=='browser'

import 'package:somepackage/somelib_browser.dart';

in case of platform=='standalone'

import 'package:somepackage/somelib_standalone.dart';

in case of platform=='other'

import 'package:somepackage/somelib_default.dart';

This just uses some convention like

  • platform needs to be such that it can be part of a import path (no spaces, ...)
  • [|] can't be used as normal characters of an import path (no idea whether they are valid now)
  • default is applied as platform part when none of the other choices apply.
  • It is less flexible because it can't import 'package:foo/somelib.dart'; in case of browser and instead import import 'package:bar/someotherlib.dart'; in case of standalone but I see this as an advantage, it would be too confusing otherwise.

I don't think there are situations where "don't import at all for a specific platform" makes sense.

It is probably useful to have an indication (at the beginning?) of the import whether there is a platform selection involved to know if the import path needs to be parsed for platform part.
I have no idea what would be helpful for the involved tools.

If this is not an option I like @tatumizer s approach better than the original proposal.

@tatumizer
Copy link

Won't it be better to just use package-spec (see another proposal) for this? One may have 2 or 3 package specs (e.g. browser spec, standalone spec, test spec) and configure stuff there like
homebrew=../../libs/homebrew/lib/homebrew_standalone.dart
In import, just refer to it by alias "homebrew"

Another concern: we currently know 2 options for certain: standalone and browser. If people start adding custom options, this will lead to a mess: libraries can use options no one knows about.

On the other hand, if options are fixed, syntax of package spec can be extended slightly so that we can specify the choices in a single package spec, e.g, (just making it up):
[standalone]
homebrew=../../libs/homebrew/lib/homebrew_standalone.dart
...
[browser]
homebrew=../../libs/homebrew/lib/homebrew_browser.dart

Names of sections can be predefined, to avoid collisions in conventions across different libraries
In this case, there will be no combinatorial explosion of options. BTW, do we know use cases that require something beyond [standalone, browser, test ] ?

I just feel that the complexity of proposal and all variants discussed above is not proportionate to the problem (in other words, we are shooting sparrows from cannon)

@zoechi
Copy link

zoechi commented Mar 6, 2015

I think it is more common to just have different libraries for each platform within one package instead of a package for each implementation.
I would allow/support only this variant.

@tatumizer
Copy link

Another variant for the case we go with naming conventions:
instead of
import 'package:somepackage/somelib_[browser|standalone].dart';
write
import 'package:somepackage/somelib_${platform}.dart';

It uses existing string interpolation syntax.

@zoechi
Copy link

zoechi commented Mar 6, 2015

I thought of this as well and like it but it doesn't allow to specify which platforms are supported. Not sure if this is necessary though.

@tatumizer
Copy link

If you want explicit choices hardcoded, I think the variant of "select" can be simplified as
import
select "package:some/some.dart" for "platform:server"
select "package:some/other.dart" for "platform:browser"
All standard choices of discriminant have to be predefined. User can still specify "platform:foo", but it would be good only for his own local testing, no one else will ever know about "foo".
This way, it doesn't look like expression, and doesn't introduce new syntax beyond [keyword [value]]*
Also, notation "rhymes" with "package:some" thing (syntactically, I mean). Patterns always look nice.

@zoechi
Copy link

zoechi commented Mar 6, 2015

I'm convinced. Maybe for is enough and select can therefore be omitted?

@tatumizer
Copy link

Maybe. Though it won't fit into pattern "every clause starts with keyword".
Note sure. Would be good to know what Lasse thinks about the debate so far.
There are other threads where people look at the problem from different perspectives, but didn't follow closely, maybe they are talking about something that should be factored in.

@zoechi
Copy link

zoechi commented Mar 7, 2015

I was a bit tired when I wrote my last comment...
What I meant was that the import keyword might fit for platform dependent imports as well and then only the for platform makes the difference from normal imports.
It would be easier to discuss with more knowledge about the requirements of the involved tools.

@tatumizer
Copy link

If we remove "select", then comma wants to be inserted between variants:
import
"package:some/some.dart" for "platform:server", // comma here
"package:some/other.dart" for "platform:browser"
(now "import" serves as a keyword itself, so we have a comma-separated list of values).

@zoechi
Copy link

zoechi commented Mar 8, 2015

I still interpreted your approach wrong but I think I got it now.

@tatumizer
Copy link

The central question for this discussion, IMO, is this: is the following a valid program?
main() {
if (false) {
abracadabra(); // calling non-existent function
}
}
Currently, analyzer flags the whole "if" block as dead code, but additionally, complains that abracadabra doesn't exist. Though program executes just fine.
If condition statically evaluating to false could tell compiler to ignore the body, then we could simply say
if (imported("html.dart")) {
//... something using html
}

Logically, condition "if (false)" is equivalent to "if (false==true)", but if false==true, then any statement is true, so it's even true that "abracadabra" exists - why complain about its non-existence?

@munificent
Copy link
Author

That isn't actually the problem. Because of the way Dart is speced, code that isn't executed can refer to things that don't exist just fine.

The problem is that the import of a library like "dart:html" on the standalone VM statically prevents the program from running at all. You don't even get to main().

@tatumizer
Copy link

I understand that. But suppose you define a simplest form of conditional import, like this:
import "package:some/some.dart" for "platform:server"
"package:some/other.dart" for "platform:browser"
Then you have full access to the entire library some.dart (or other.dart, depending on platform), without the need to create abstractions (e.g. in the form of common interfaces). All you need for this is to guard access to library with
if (imported("some.dart")) {
// access anything you want from some.dart
}.
if (imported("other.dart")) {
// access anything you want from other.dart
}
If "imported("other.dart")" is false, then the whole block (body of "if") just skipped, thus no problem for analyzer.

What I'm trying to understand is this: why this is not a good idea?

@lrhn
Copy link
Owner

lrhn commented Mar 19, 2015

I'm leaning towards:

import "something" if foo.bar == "baz"
       "somethingElse.dart" if foo.flag
       "somethingDefault.dart" show/hide/as etc;

for now. Extra punctuation isn't really necessary.

@tatumizer
Copy link

Two questions:

  1. are you leaning also towards
    if (imported("foo.bar")) {
    //
    }
    Or something else?
    I think "import" - "imported" form a nice pair, no?
  2. how rich the syntax of "if" condition can be in "import "something" if foo.bar == "baz""?
    Any constant logical expression? (e.g. with ||, &&, (...) etc)?
    [I guess the answer is yes, just want to verify]

@tatumizer
Copy link

Oh, sorry, you kind of answered question 2 in another thread.

@tatumizer
Copy link

There's a problem with "if (imported..." though.
The expression here can be any compound const expression, e.g.
if (imported("some.dart") && somethingElse || "foo"!="bar") {
// ...
}

The syntax of this expression is not easy to restrict. So analyzer has to be able to evaluate arbitrary constant expressions. (In which case there's no point to restrict the syntax of condition in "import "some.dart" if condition" either).

No idea if analyzer can easily handle arbitrary const expressions. It's all equivalent to full-blown preprocessor. Plus, maybe some extra hint is needed in the code, e.g.

if (const(imported("some.dart") && somethingElse || "foo"!="bar")) {
//...
}
Not sure it all makes sense, just fantasizing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants