-
Notifications
You must be signed in to change notification settings - Fork 213
Comments on tagged strings #1983
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
Comments
+1, fwiw I am not personally concerned about polluting the scope with simple identifiers. We have solutions for that already. Appending
Big +1 from me on this as well, it seems like we should support generics basically as you have suggested.
I also agree with this. The user can already opt in by simply passing a function into the template, and its a lot more explicit/obvious (granted the template has to support it). This is what the logging framework today does - you can pass a function for the message which will be lazily invoked if the log is actually converted to a string. Existing string interpolations are immediately evaluated, and I think it would be unexpected (and error prone) to do something different for tagged templates. |
dart2js uses a JavaScript AST library that supports 'compiling' the template to a instantiator function. If I have the tagged string I'd like to see a better example than the implementation of Is there a nice way to use a class name as a tag? I think
is confusing, since it is not an
This would be especially nice if tagged strings could be constants somehow. |
This feedback is great!
The hard question is about thunking the interpolated expressions. If we don't support this at all, it significantly limits the feature. I think there are valuable use cases around conditionally executing some expressions, catching exceptions, etc. On the other hand, it would be a little odd to support it here but not for other kinds of parameters. Maybe the right answer is to have a general For using this syntax in the macro authoring API, eager evaluation is actually fine, which is an indication that maybe thunking these was too adventurous on my part. On the other hand, the ancient Dash string template feature (remember that?) did make them closures... I actually spent quite a lot of time trying to figure out a design for this that made the tagged strings be expression macros that are processed at compile time. This would let a particular tag processor decide whether to evaluate the expressions eagerly or lazily. But we don't currently have a plan for expression-level macros and I wasn't able to come up with a simple enough proposal to add them. In a perfect world, that would still be my preferred choice. But without expression-level macros, let expressions, etc. it's really hard to make tagged strings do anything useful at compile-time. And adding all of those is a lot of additional surface area for an already very large feature. |
Agreed. That's an excellent example of an API that should be able to use this. That would let you write: body = jsStatement 'return ${_emitter.prototypeAccess(superClass)}.$methodName.call(this, $targetArguments);');
I'm sorry, I didn't follow all of this. |
OK, I've updated the proposal (22ef7f2) to make it eager and to allow more tightly typed interpolated expressions. The remaining issues is:
The proposal still has that. I am concerned about polluting the scope with simple identifier. I definitely do not want the tag processor function to be in the top level lexical scope with a name like Appending
I liked this idea at first, but then I realized it doesn't actually make sense. The problem is that there is no string instance to call this on. There's no We could require: extension WhatNot on String {
static R operator foo(List<String> s, List<T> i) { ... }
} But now we're inventing some completely new "static operator" concept which feels pretty weird. I also think it's somewhat dubious to allow any user-defined identifier after We can't make an extension constructor on String because (1) extensions can't have constructors and (2) it may not construct a string. I don't love suffixing the name, but I don't have any better ideas either. |
Why not make it an extension on a class named |
I'd actually prefer to not allocate two lists (and a The #1478 proposal instead uses a builder pattern where the "tag" has |
What if we just made them Iterables instead? Then they are already immutable (statically too), and they can be lazy, and avoid the list allocations. The main issue I see with that is it can be pretty awkward to iterate over all the elements in order (alternating between the two iterables). You end up needing to use the iterators directly, or just converting them to lists. |
Using iterables is an interesting idea. It could even lazily evaluate the expressions when you read I generally don't like the two-seperate-iterables/lists approach. Compare this to C#'s collection initializers: So, in short, not happy about the two separate lists, not overly fond of a |
Good point re: the same problems as thunking potentially. I also agree that I dislike the two lists/iterables approach, it is just very awkward to deal with even when they are lists (and it requires useless empty strings in places etc if tagged string starts or ends with a thunk). I think I agree with you that the builder approach is better.
|
If this discussion is driven by performance concerns (which it seems like it started as?) I'm fairly skeptical that using iterables and builders is going to be more performance than simply allocating a list and calling a statically known function. You're adding a lot of code indirections that the compiler has to figure out, instead of just inlining a function and deconstructing a list literal. Maybe you have concrete strategy in mind that is optimizable, but I'd like to see some evidence. |
There is some evidence from the previous JSON explorations, which is a similar situation. Avoiding the intermediate lists/maps during parsing and going directly to objects there was significantly faster in my testing, although I don't remember the exact numbers. But it shouldn't be that difficult to write a benchmark for this specific case as well to see how things compare and we should definitely do that :). |
That's a neat idea. I'm not crazy about requiring allocating an instance of that wrapper class, but maybe it could get inlined and eliminated. Does that feel significantly less magical or hacky to you?
I did consider that. In fact, because Iterables are implicitly lazy, that would even potentially give us a way to evaluated the interpolated expressions lazily—we could specify that each interpolated expression is only evaluated when the iterator reaches that position. But, in practice, I think the code the compiler would have to generate to implement these iterables would be quite complex and likely slow. I don't want to rely on a Sufficiently Smart to get tolerable performance.
Going with a builder style is definitely interesting. I look at this as mainly a question of internal versus external iteration. If we use a builder protocol, then the string literal drives the tag processor. It decides which There's no free lunch here. Whichever way we choose makes it easier to write code for one side at the expense of the other. If we use a builder API, it gets harder to a tag processor to do things like:
Since the compiler will be writing the code to call the processor, I think it makes sense to put the hard work there and make it easier for a human to implement the tag processor. Giving them two complete lists is pretty simple and easy to work with. Also, as noted, the string list can be const in this case. Heck, the compiler might even be able to make interpolated value list const in some cases. In general, doing a builder pattern will make it really hard to play nice with immutability.
That made sense for C# at the time because immutability wasn't really a thing and its existing data structures were always created empty and them imperatively filled up. But going in this direction for Dart feels like a step backwards to me. Users prefer objects that are created in a fully initialized state and are rarely mutated afterwards. Dart has Adding syntactic sugar that explicitly desugars to mutation seems like it runs counter to all that. |
I guess the class TaggedStringLiteral<T> {
int get valueCount;
int get stringCount;
int get length;
bool isValue(int index);
T valueAt(int index);
String stringAt(int index);
} which hides the implementation and doesn't require strict interleaving of strings and values. |
Re. https://github.com/dart-lang/language/blob/master/working/tagged-strings/feature-specification.md
at all, but on the string. Not hacking the lexical scope is better.
identifier
cannot ber
, which is taken for raw strings.Object?
. That's too JavaScript-y.R Function(List<String>, List<Y Function()>)
should only allow expressions with typeY
in interpolations.R Function<X>(List<String>, List<X Function()>)
tag should infer theX
from the interpolation expressions (andR
can then depend on it).R Function(List<String>, List<T Function()>
for some typeT
, then an interpolation expression of typeT
is auto-thunked for you (and an interpolation expression of typeT Function()
is not).$`expression`
instead of${expression}
to have it thunked.async
. There was a reason we stopped desugaring into lambda expressions in the spec, it was just wrong most of the time after introducingasync
.List<Y>
, notList<Y Function()>
.async
.async
function, then:List<Future<T> Function()>
orList<FutureOr<T> Function()>
, and then the function is thunked using anasync
method if necessary (has typeT
, or has typeFuture<T>
and containsawait
),await
.The text was updated successfully, but these errors were encountered: