Splat operator #1050

Closed
mixonic opened this Issue Jun 30, 2015 · 43 comments

Projects

None yet

10 participants

@mixonic
mixonic commented Jun 30, 2015

I've been fielding several requests for a splat operator in Ember templates. Specifically, when using Ember components. For example given:

{{! presuming myHash === { keyA: 'val', keyB: context.someProp } }}
{{my-component *myHash}}

It would de-sugar to:

{{my-component keyA="val" keyB=someProp}}

This proposal is limited in scope.

  • It does not propose a way to de-sugar params from an array, only properties from an object into the hash. I find it useful to consider the params case in designing this API though, presuming that it would use the same syntax but simply vary behavior at runtime based on the contents of the splatted variable.
  • I propose that only a single splat be allowed.

Grammer proposal

OPEN helperName param* hashSplat? hash? CLOSE

Visually, it makes sense to put the splat before any hash arguments. This would aid in the understanding that hash arguments should take precedence. For example given:

{{! presuming myHash === { keyA: 'val', keyB: context.someProp } }}
{{my-component someParam *myHash keyB="I take the crown!"}}

The hash would be { keyA: 'val', keyB: 'I take the crown!' }. This is actually runtime behavior, and could vary according to implementation, but seems a good justification for the above ordering.

It is also suggested that subexpressions would also accept a splat. For example:

{{my-component someParam=(translate *translateOptions)}}

Alternatives

It has been suggested by @wycats that we consider ...myHash, as it aligns more closely with JavaScript splatting. This seems reasonable, I'd like some more thoughts.

/cc @kpdecker @mmun @krisselden @wycats @chrislopresto

@mmun
Contributor
mmun commented Jun 30, 2015

I like this. It would be useful for Ember components. I prefer *. Perhaps ... for params.

@kpdecker
Collaborator
kpdecker commented Jul 2, 2015

Being a bum on vacation right now but quick comment:

Allowing this for hash and not array parameters feels weird and I suspect that people are going to ask for array as well. The only thing that I can think of to support that would be having a separate syntax for one vs. the other:

{{foo someParam ...restParams *hashParams myKey=myValue}}
@mmun
Contributor
mmun commented Jul 2, 2015

Some more context for the discussion: https://github.com/sebmarkbage/ecmascript-rest-spread

tl;dr: The JS syntax { x, y, ...z } has been proposed and looking like its going to make it in.

@kpdecker
Collaborator
kpdecker commented Jul 2, 2015

That doesn't help us since {} vs () is unambiguous but our parameter constructs aren't. We need to have an explicit way for hash rest to be defined vs array params rest since we can't trust runtime type of or similar (I.e. What do we do with an iteratable? Is that a hash rest or an array rest? At what point will this surprise the hell out of users or limit optimization?)

@mmun
Contributor
mmun commented Jul 2, 2015

I agree it doesn't help (it hurts). Just mentioning.

@kpdecker
Collaborator
kpdecker commented Jul 2, 2015

Some of this is probably bike shedding. If someone were to implement this changing the exact tokens used is pretty trivial. Concept is great, we just need to sort out the "ui" and make sure the performance isn't negative (which i don't expect since this seems like it should be isolated to users of the feature)

@wycats
Owner
wycats commented Jul 2, 2015

This feels like a decent feature but we have relatively little room for new syntax at this point without falling off the complexity cliff, so I agree with @kpdecker. Let's tread carefully.

@mixonic
mixonic commented Jul 3, 2015

Seb's rest spread is an interesting idea, though not quite certain to become reality afaik.

In JS, we have the context of usage to differentiate array from property spread. [ ...someItems ] is presumed to be a spreadable array, { ...someItems } would be presumed to be an object.

Handlebars has no equivalent context. Given {{foo ...someItems}} there is no way to know if params or hash are being spread. I believe this means we require two syntaxes (one for params, one for hash).

My suggestion is to reserve ... for params and introduce * for hash.

Happy to take any next steps that seem appropriate.

@wycats
Owner
wycats commented Jul 3, 2015

@mixonic the more direct precedent from Python and Ruby is * for params and ** for hash 😄

@workmanw

👍

Functionally speaking, I believe this concept is need to fully replace Ember.ContainerView with {{each}} and {{component}}. @amk221 concisely highlights the concerns here: emberjs/ember.js#11377 (comment).

More details also in this discourse discussion: programmatically-rendering-ember-components

@mmun
Contributor
mmun commented Jul 10, 2015

My vote is ** for hash, * for params. They should be allowable anywhere inside the respective hash/params. Basically

param
  : helperName -> $1
  | sexpr -> $1
  | ONE_ASTERISK ID -> ...
  ;

and

hashSegment
  : ID EQUALS param -> new yy.HashPair(yy.id($1), $3, yy.locInfo(@$))
  | TWO_ASTERISKS ID -> ...
  ;
@mixonic
mixonic commented Jul 11, 2015

I have warmed to **. Taking the lead from Ruby feels better than trying to ride the edge of an uncertain wave with ...

@mmun
Contributor
mmun commented Jul 12, 2015

The only thing left to figure out is where splats can appear. Since they aren't ambiguous, I think they should be allowable anywhere where they make sense (as described in the grammar in my last comment).

@mixonic mixonic referenced this issue in emberjs/rfcs Jul 12, 2015
Merged

Contextual components RFC #64

@kpdecker
Collaborator

I think there are a few questions that need to be answered:

  1. If multiple instances are allowed, what are the semantics? For *, would concat occur? For ** extend?
  2. Tied to that, what is the expansion pattern for different values passed to it? I.e. what happens with {{foo *object **array}} vs. {{foo *array **object}}
  3. Anyone volunteering to implement this? :)
@kpdecker kpdecker added the feature label Aug 1, 2015
@kpdecker kpdecker added this to the Backlog milestone Aug 1, 2015
@BenjaminHorn

Is any news on this? I see it went to Backlog. Will it be implemented soon? I would gladly help.

@kpdecker
Collaborator
kpdecker commented Sep 9, 2015

I personally don't have time to implement this. If you want to take a crack at it, I'd be glad to help if there are questions.

@Frozenfire92

+1 this would be a great feature

@mmun mmun referenced this issue in emberjs/ember.js Nov 27, 2015
Closed

Spread helper #12658

@machty
Contributor
machty commented Nov 28, 2015

There's been some recent discussion in Emberland about whether it would be preferable to leverage ES2015 ... splat syntax instead of * and **, which are more Ruby-ish. Most of the work for splats has been completed in #1128, but it's probably worth quickly exploring ... as an alternative before it gets merged for good.

Here is one ...-based proposal to kick off this hopefully quick discussion:

Simple example:

{{some-helper ...positionalParams (...)=hashParams}}

More complex

{{some-helper p0 ...args0 p1 p2 ...args3 (...)=defaults f=123 (...)=overrides}}
  • positional params would be p0 + unwound args0 + p1 + p2 + unwound args3
  • hash would essentially be Object.assign({}, defaults, {f: 123}, overrides) (difference from current PR'd impl: we allow for multiple hash splats to progressively merge into a single hash)
  • we still maintain the syntax that positional params and hash params be separate

/cc @wycats

@machty
Contributor
machty commented Nov 28, 2015

Perhaps {...} would be more distinct from sexprs than (...), e.g.:

{{some-helper ...positionalParams {...}=hashParams}}

Note that with this syntax, the {...} curlies never touch the outer mustache curlies, nor can they touch sexpr parentheses, which would likely be unbearable ugly.

@wycats
Owner
wycats commented Nov 28, 2015

I like {...}=values. What about ...=values? Too ugly?

@waynedpj

if we are going for the ES2015 splat syntax, are there reasons why the same ... syntax (i.e. w/o curlies?) cannot be used? since ES2015 is going that way, i think it makes sense to follow suit, especially for newcomers, unless there is a major technical/performance hurdle.

@machty
Contributor
machty commented Nov 28, 2015

@waynedpj: I'm not 100% sure what you mean but I think @kpdecker addressed this here; the Handlebars syntax on its own needs to be able to distinguish splatting arrays vs hashes.

@wycats {{some-helper ...positionalParams ...=hashParams}} seems too ugly / indistinct.

@wycats
Owner
wycats commented Nov 28, 2015

@machty I am warning up to {...}=values but <my-list {...}={{values}} /> is a little weird no?

@mmun
Contributor
mmun commented Nov 29, 2015

I think ...=hashParams is fine. {...} is many characters. Also, would { ... } also match? It would have to by the principle of least surprise.

@mmun
Contributor
mmun commented Nov 29, 2015

I doubt {{some-helper ...positionalParams ...=hashParams}} will come up often, especially in app code.

I suspect {{some-helper ...=hashParams ...=overrides}} to be more common (though still rare), and the multiple {...} would be an eyesore.

@machty
Contributor
machty commented Nov 29, 2015

After talking over with @mmun last night (he summarized a lot of what we discussed above), I'm leaning in favor of ...=hashParams without the curlies.

@waynedpj

@machty sorry for the vagueness: i was trying to suggest using the same ES2015 ... syntax for arrays and hashes, but as you pointed to in the ref (thanks), there is indeed some tech issue with using said syntax for both. however, IF i am following this discussion correctly (note the BIG IF) it seems like the ES2015 ... syntax might actually be usable for both? FWIW, my vote is for keeping the syntax as close to ES2015's as possible, preferably w/o additional curlies.
thanks.

@mmun
Contributor
mmun commented Nov 29, 2015

@waynedpj You're right, but it doesn't work here. In ES2015 { ...foo } is expecting foo to be an object, because it is inside of an object literal. Similarly, [ ...bar ] is expecting bar to be an array because it is inside of an array literal.

What would you have {{foo ...bar}} do? Is it spreading params or hash? Leaving that behaviour to be decided at run time (depending on whether it is a hash or an array) is seriously asking for trouble (ES2015 doesn't do this).

So we need to come up with a static solution that disambiguates the two cases. @matchy's proposal is for {{foo ...bar}} to assume bar is an array and to spread over params, and for {{foo ...=baz}} to assume baz is a hash and to spread over the hash.

@waynedpj

@mmun thanks for taking the time to clarify this. i had been missing the = as the differentiator between Array and Objects/hash splats. my JS understanding is basic, but i was assuming that we could sometimes treat Arrays and Objects similarly, e.g. the way a for ... in statement can be used to iterate "keys", regardless if they are Integers for Arrays or Strings/Symbols for Objects. i believe this is just like the each helper works as well? of course, i am prob missing many things about this discussion!

i think the additional = is a nice compromise. though now using {...} for Object splats makes more sense, but only if something like [...] was used for Arrays splats to be consistent. and it does add more whiskers to an already 'stache heavy syntax.
thanks again.

@mixonic
mixonic commented Nov 30, 2015

{{some-helper ...positionalParams ...=hashParams}} seems nice. Would like to hear from @wycats and @kpdecker again.

@rwjblue rwjblue referenced this issue in emberjs/ember.js Dec 2, 2015
Closed

Dynamic parameters for dynamic components #12671

@machty
Contributor
machty commented Dec 2, 2015

One possible drawback with ... is that it's only a single character away from Handlebars ../ syntax, which does a completely different thing. Is that going to be problematic?

@kpdecker
Collaborator
kpdecker commented Dec 3, 2015
{{some-helper ...positionalParams ...=hashParams}}

Feels like the clearest to me given all of the constraints that we have, which I think this discussion has covered well.

@machty Someone would have to actually implement this but I don't think it will be an issue from the parser standpoint. right now .. is just a literal match and ... shouldn't be ambiguous for any existing productions.

@machty
Contributor
machty commented Dec 3, 2015

@kpdecker Do we want to support splatting ../some_array? Would it look like {{some-helper foo bar ...../parent_array baz}}, or in the case of hashes {{some-helper ...=../parent_obj}}?

@kpdecker
Collaborator
kpdecker commented Dec 3, 2015

@machty Good point, I did not think of that case. For {{foo ...../bar}} I believe the array case will tokenize to '...', '..' which while ugly and error prone (compile syntax error, not mysterious runtime error due to too many . chars) should work.

I think that we should allow for spaces between the ... and anything else so ... ../foo should work and be clearer. We should have UT coverage for both cases.

I think with the hash case, it's clear enough as the = character provides a delineator. i.e. ...=../foo

(Edit: I even had the wrong number of . in my example above so stringing them together is clearly not ideal)

@machty
Contributor
machty commented Dec 6, 2015

I have begun work on a followup PR to #1128 based on the latest feedback in this thread.

@waynedpj
waynedpj commented Dec 7, 2015

@machty thank you and everyone else!

@nathanhammond

@machty Are you branching off of #1128 or starting fresh? Can you work in the open so we can contribute?

@BenjaminHorn Would you be willing to grant access to your fork to people? Haven't seen you since magically showing up with a PR on 11/2. :)

@BenjaminHorn

@nathanhammond I would gladly add you (or anyone who ready to add those modifications) as collaborator. Just let me know.

@nathanhammond

@BenjaminHorn Add me and @machty, pretty please. :) Just trying to update #1128 to bring it inline with current discussion.

@machty
Contributor
machty commented Dec 10, 2015

@nathanhammond I just branched off of his PR, rebased onto master, and I'm partly through the work to get it done. There's not much to show right now (half the tests aren't passing), so there's not much as far as WIP to share, but I should have a PR in this weekend that I'll ping you on.

@machty
Contributor
machty commented Dec 10, 2015

@nathanhammond et al please check out #1149 which has most of the work done; the only thing remaining are positional param splats and subexpression splats.

@kpdecker kpdecker modified the milestone: Backlog, 5.0.0 Dec 12, 2015
@kpdecker
Collaborator

Closing in favor of #1149

@kpdecker kpdecker closed this Dec 12, 2015
@jamesarosen jamesarosen referenced this issue in jamesarosen/ember-i18n Dec 17, 2016
Closed

Update helper to allow binding hash to be passed #423

@jamesarosen jamesarosen added a commit to jamesarosen/ember-i18n that referenced this issue Dec 20, 2016
@k-fish @jamesarosen k-fish + jamesarosen t helper supports context object
Previously, the `{{t}}` helper, like the `i18n.t` utility, accepted a
translation key and a context hash. This worked when the Handlebars template
had the individual keys and values (or value bindings) for the context,
but didn't when there was a pre-built object that represented the context.

Now it accepts a second ordered (non-hash) argument that represents the
context as an object. Hash context properties override those from
the context object.

```hbs
{{t 'some.key' someObject prop=value}}
```

is approximately the same as

```js
i18n.t('some.key', Object.assign({}, someObject, { prop: value }))
```

This is a workaround for the fact that Handlebars does not yet have a syntax
for splatting an object into hash arguments.

See wycats/handlebars.js#1050
See wycats/handlebars.js#1128
See wycats/handlebars.js#1149
Closes #423
0854a3f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment