tc39 / proposal-pipeline-operator

A proposal for adding a useful pipe operator to JavaScript.

Home Page:http://tc39.github.io/proposal-pipeline-operator/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Bikeshedding the Hack topic token

js-choi opened this issue · comments

This issue is for bikeshedding the spelling of the topic token in Hack pipes, branching off from #75 (comment). For more context, see the Hack pipes proposal and the wiki home page.

The table has its own editable page on the wiki. Please read this table first before contributing to this issue.

Please also keep discussion on topic: bikeshedding the topic token for Hack pipes. For other topics, search for other existing issues. Thank you!

Old obsolete questions

These currently are the most topical bikeshedding questions that I see now:

  1. What is the optimal tradeoff in writability (easily typed ASCII soup, e.g., ?, or easily typed privileged valid variable, e.g., $) versus readability (less easily inputted non-ASCII syntax, e.g., )?

  2. Related to question 2: Can non-ASCII Unicode syntax characters be considered for the pipe placeholder, or must they be categorically excluded?

    A list of all possible Pattern_Syntax Unicode characters is available.

  3. If question 3’s answer is that non-ASCII Unicode syntax may be excluded, which non-ASCII syntax character would be visually understandable and/or less difficuldif­fi­cultt?

    Many non-ASCII symbols are easily inputted in certain OSes. For instance, in macOS, several dozen typographic characters are directly typable using Option or Option + Shift (image of keyboards with various active modifier keys via Macworld article. It may be worth determining if there is an intersection of these easily typable non-ASCII characters across the default keyboard layouts of many OSes.

  4. For nullary operators vs. valid variable identifiers: How important is it that the placeholder be statically analyzable?

    A nullary operator can be always be statically recognized in the RHS. In contrast, a valid identifier can be statically recognized only if the rule is: “Tacit function calling may occur only when the RHS is a bare identifier, rather than allowing the RHS to be any arbitrary expression without a placeholder.”

  5. Operator vs. identifier again: How often would someone want to use an identifier from an outer lexical context of the same name as the pipe placeholder?

    If the placeholder is $, how often would a programmer want to use jQuery’s $ or another externally defined $ in a pipe’s RHS, as well as the pipe placeholder? How often would they be surprised if they could not, without defining a dummy variable for the outer $? How bad of a footgun (i.e., bug by programmer-unexpected behavior) would this be?

  6. How much should the pipe placeholder stay compatible with @rbuckton’s higher-order functional operators, which possibly would use {…}?

  7. @rbuckton proposed a partial-application placeholder that may be explainable by multiple topic placeholders. In other words, the pipeline syntax might be completely unifiable with the PA syntax with the right enhancements.

    Assuming a future shorthand “pipe-function operator” like +> (which would be an abbreviation for x=>x|>), then @rbuckton’s proposal’s f(?, 3) would instead be +> f(?, 3), which in turn would be shorthand for x=>x|> f(?, 3).

    Creating non-unary functions could be done
    by adding numbers to placeholders,
    such as ?0, ?1, ?2, etc.
    For instance, example.sort(+> ?0 - ?1)
    would mean example.sort((x, y) => x - y).
    (?0 would be equivalent to plain ?.)

    Which placeholders would have any problems with staying forward compatible with such a future proposal?

As of 2022-07-11, I like ^^ then more distantly $_, @@, %%, and #_.

There's a few choices that use ? but might avoid some of the confusion:

  • <?>
  • {?}

Also, I find ^^ confusing in that what it does is not related to what ^ does.

FWIW, I really like ^^. I find it clever, and illustrative in that it points up, as though at the previous expression which is likely to be the LHS of the pipeline operator, reminding you where the value comes from.

Pipelines will be single-line just as often as multiline; so "points up" and "points at the previous" won't consistently be the same.

I take it that, if we go down the Hack-style path, we're not trying for any kind of path for the placeholder to be subsumed by @rbuckton's partial application proposal, and therefore should avoid using the same token, is that right?

My 2c: I personally really like $ as the placeholder variable binding. Because it's a valid identifier, it feels like an actual variable, which it certainly is (albiet smaller in scope). It also prevents the Hack-style proposal from claiming another operator that might be useful for future proposals.

I think the "jQuery problem" is probably overblown for these reasons:

  • How many libraries out there actually expose a global $? I would imagine very few.
  • The pipeline operator does not impede using these libraries. Rather, using these libraries impedes using the pipeline operator, which is not a big deal.
  • jQuery would hardly benefit from the pipeline operator anyway since it uses heavy method chaining.
  • If you're not using global scripts and using modules instead, then you can easily rename your import / require variable to something other than $.
  • _ would still available, and is much more commonly used for utility functions (lodash, underscore, rambda, etc.), although becoming less so due to static ES module imports.

Is it at all possible to use $ in the partial application proposal? If we're trying to leave open the door for a partial application proposal in the future, we could end up with two different syntaxes for partial application / placeholders, one inside pipelines and one outside.

@mAAdhaTTah I don't think it's possible for partial application to use $ unless there's a token which is used to start off the partial application expression. The current partial application proposal does not have such a token.

@littledan: I take it that, if we go down the Hack-style path, we're not trying for any kind of path for the placeholder to be subsumed by @rbuckton's partial application proposal, and therefore should avoid using the same token, is that right?

From what I could tell, syntactic partial application could use the same placeholder as Hack style iff syntactic partial application were forbidden in every placeholder-using pipe’s RHS. This could be a footgun—it creates a hidden context dependency between two very different results, which may cause the programmer to accidentally commit a mode error—but if we logically proceed anyway:

Proposal 2: Hack Style Only and Proposal 4: Smart Mix would forbid syntactic partial application in the RHS of all pipes—or, rather, the parser would always interpret the presence of a RHS placeholder as a pipe placeholder, not a partial-application placeholder. For instance, if |> is always a Hack pipe and ? is the placeholder for both |> and partial application, then f(?, 3) would be partial application, and x |> f(?, 3) would be f(x, 3).

Proposal 3: Split Mix would forbid syntactic partial application in the RHS of Hack pipes. It could also forbid it in the RHS of F-sharp pipes but it could also allow them. If |> is an F-sharp pipe, |: is a Hack pipe, and ? is the placeholder for both |: and partial application, then f(?, 3) would be partial application, x |> f(?, 3) would be f(?, 3)(x) (or it could be a syntax error), and x |: f(?, 3) would be f(x, 3).

If that analysis is correct, I’ll add it to the original post’s ? option. The problems of hidden contextual dependency—and of visual confusion with nullish coalescing and optional method chaining (assuming that syntactic partial application would stick with ?)—would remain.

I would be very opposed to using any valid identifier - including $. It is a very common pattern to do var $ = document.querySelectorAll as well, for example - it’s not just about globals.

Shadowing bindings is fine when users explicitly choose the identifier - it would not be ok with me that a magic implicit binding would appear that shadows my code just for using an operator.

I prefer personally ?, but I think it's worth unpacking 'visually confusing'. I think the statement that it is visually confusing in relation to the two other stage 1 proposals is misleading — those statements are more visually confusing with each other and with the ternary operator than anything else. Even if both of those get through, the ? placeholder proposal should not be held back on their account.

The ambiguity only creeps in when placeholders are nested in further expressions — @gilbert's original example being a placeholder in a ternary expression: ? ? : foo. This is the kind of scenario where the other two stage 1 operator proposals become problematic, when ?? can be read as either an optional chain on the placeholder.

It's a truism that nested ASCII operations are difficult to parse, but I think the placeholder is a special case inasmuch as it already bears the application-space cognitive weight of a deferred value within a special lexical scope of a higher order operation (the pipeline). So the situation only becomes complicated when we allow that the placeholder be used as an operand within a further expression nested inside a pipeline. Might it be worth outlawing that particular condition? — that is to say, a placeholder must be standalone, invocable only with ( or , either side of it?

In passing, I am firmly against the idea that an otherwise valid generic reference (like _ or $) be special cased. There is a special kind of semantic ambiguity introduced in scenarios like that of this in class arrow methods, when lexical parsing rules change based on higher order context. To do that with something which is otherwise up to user semantics is by all accounts a really imposing proposal (I foresee eslint rules to try and retro-ban them to compensate for this, invalidating previously legitimate and self-explanatory code).

What about parameters with a name ?
for example:

anArray
  |array> pickEveryN(array, 2)
  |array> array.filter(...)
  |filteredArray> makeQuery(filteredArray)
  |query> await readDB(query, config)
...

Then one could write something like this:

anArray
  |$> pickEveryN($, 2)
  |$> $.filter(...)
  |$> makeQuery($)
  |$> await readDB($, config)
...

I think this would improve code readability, as there may be a lot of transformations applied. If I want to read one particular line, I won't know what's passed to the function and what's for.

Other proposal, reuse arrow functions : [edit: actually, I noticed this is proposal 1]

anArray
  |> array => pickEveryN(array, 2)
  |> array => array.filter(...)
  |> filtered => makeQuery(filtered)
...

I like this proposal because it can be used with destructuring and stuff like this, while being readable.

There are probably a lot of issues with those proposals, but my main idea is that variables like "$" or "^^" lack of readability and are not really elegant (from my point of view).

Whichever token we choose is going have an effect on the placeholder syntax, either by producing two different placeholder syntaxes (which I'd prefer to avoid), by superseding the placeholder syntax (effectively killing it), or by forcing its hand (requiring it to use the same token). We should probably decide which approach we're taking and keep that in mind as we bikeshed the token for use in the pipeline.


@bisouduperou I think we ran into problems with a similar proposal for await, e.g. this:

x |$> pickEveryN($, 2)

could be parsed as:

x | ($ > pickEveryN($, 2))

Reusing arrow functions’ arrow works very well for most purposes, but not for await.

Honestly I just really hope any form that has a placeholder variable of any shape or spelling gets adopted, because it adds tremendous power and flexibility to an operator that would otherwise be much more limited and constrained... Thanks for letting me add my 2c.

In #84 (comment) I used a ## nullary operator as a pipe placeholder. It looks nice and it doesn’t mess up GitHub’s current JavaScript syntax highlighting, but it probably would be quite visually confusing with private fields, so I don’t actually think it’s a good idea, heh.

Ok, if $ is too commonly used in user-land, then how about $$, like the actual Hack pipe operator?

@gilbert I was also thinking about $_ as another possibility.

Let's try to not end up with smileys please $_$

Ah, $_ brings back dormant memories of Perl…

For what it’s worth, Perl 6 actually turns Perl 5’s $_ into an impressively unified concept of a lexical “topic variable”, which may be lexically bound using a given block or a for block and which is implicitly used by many other types of statements, operators, and functions, making for rather pithy tacit code. Think of it like linguistic topics. And in general, Perl 6’s design (fun fact: they actually released 6.0 some time back) is worth checking out you want to dive into the heart of some ingeniously mad and madly ingenious PL design.

I am in no way suggesting that an implicit topic variable be a thing in JavaScript beyond Pipe Proposal 2/3/4, though it’s pretty fun to think about.

@gilbert tbqh, i very strongly would object to any valid identifier there - it’s not just about “commonly used” for me, it’s about “ever used”.

@ljharb I think such a strong stance to take would make sense if $$ were made globally available, but here it's only on the right-hand side of a pipeline – a very small scope, of which a programmer consciously introduces by writing |> in the first place.

Magically introducing an implicit binding that shadows something defined in an outer scope is with-like behavior, and after we all worked so hard to remove with from strict mode, I’d think we’d be loath to reintroduce it.

I agree it's implicit, but I would not equate to with. Yes we'd be loath to reintroduce with, but this is not remotely the same thing.

PowerShell also uses $_ as a topic variable in a number of places:

// pipes
Get-Files | ForEach-Object { Out-Host $_ };

// catch blocks
try { ... }
catch [System.Exception] {
  Out-Host $_.Exception.GetType().FullName; 
}

// filters/functions
function Foo {
  begin { ... }
  process { Out-Host $_; }
  end { ... }
}

Of course, that's because every variable in PowerShell is prefixed with $...

Ah, $_ brings back dormant memories of Perl…

PowerShell also uses $_ as a topic variable in a number of places:

FWIW, Ruby uses this too. If JS were to adopt $_ as a topic variable or something like it, there's lots of precedent.

there's lots of precedent.

Node's also set precedent with _ being the last evaluated expression, but if it's defined in-scope it the variable you set it to.

There is also Safari Web Inspector’s use of $1, $2, … to denote previously evaluated expressions in its console REPL, I suppose.

I used to be very against using a valid identifier for a pipe placeholder, but being reminded of Perl and Ruby made me realize that it might not be that weird. Then again, that there is an implicit binding at all in a Proposal-2,3,4 pipe is relatively unprecedented in ES. with was very bad especially because of its mucking up of static optimization, which Proposal-2,3,4 pipes do not have at all. But could using a valid identifier make static analysis of a pipe’s RHS—e.g., to determine whether there is no placeholder in the RHS—more difficult?

I plan to have my Babel plugin for Proposal 4 support configuration of what its placeholder is, to encourage experimentation (if its tokenization permits it). What its default placeholder should be, I don’t yet know. Maybe a non-ASCII placeholder by default might be interesting after all, haha.

I don't think it's about weirdness but about origin. That you don't know about what an operator means is ok. You may don't know what "yield" or "await" means, because those are operators. But a valid identifier is, by default, a variable previously defined somewhere. By adding valid identifiers like "$_", you will need to ask yourself "am I in pipe ?" "does it come from pipe" "is it a global / simple variable?". I deeply encourage to avoid such (little) confusion.

While I personnally prefer meaningful placeholders ("$_" is as readable as "let thing = stuff()"), why not using @ as placeholder?

anArray
  |> pickEveryN(@, 2)
  |> @.filter(...)
  |> makeQuery(@)
  |> await readDB(@, config)
...

@bisouduperou: I agree that too much context sensitivity is undesirable. I am not a big fan of shadowing $; I might want to use an outer $ in a pipeline’s RHS, although I suppose that I could always just reassign it to another variable. I’m gradually warming up to $ vis-à-vis Perl/Ruby, although at least one TC39 member has expressed strong disapproval of $ for reasons with which I agree.

In any case, @ is probably impossible as a pipe placeholder due to its use as a prefix by the stage-2 decorators proposal.

@rbuckton wrote a useful list of current operator uses in tc39/proposal-partial-application#21 (comment). There are some ASCII characters here that have not yet been considered for pipe placeholders. ~~ particularly comes to mind as an option, although your mileage may vary with the flavor of ASCII soup it might bring. In any case, I’ll try to add ~~ to the original post and the wiki later today.

In light of some possible confusion that has occurred around the phrase “Hack style” (Proposal 2 only versus Proposals 2,3,4), I’m renaming this issue. I may do the same in the wiki, hopefully in a way that doesn’t break previous anchor links.


There’s been quite a bit of discussion in #89 on the possible interactions between Proposals 2,3,4 and other proposals, particularly @rbuckton’s syntactic partial application (PA) and HOF operators. I’ll try to update the issue original post and wiki later today with points from the discussion.


[@littledan, #93 (comment)] I'd prefer if we can minimize the number of tokens we introduce to make the feature set easier to learn. From that perspective, the optimal solution would be one token for pipeline, and one token for partial application and/or pipeline placeholder.

[@zenparsing, #89 (comment 1), #89 (comment 2)]:
Yep, partial application is a red herring with respect to pipelines.…I think [this quote] expressed the idea better than I could have:

Trying to explain pipelines (which are fundamentally unary) in terms of partial application (which are n-ary) seems to me to be a fundamental mismatch in their models.

Putting these here from other issues because they’re TC39-member opinions relevant to whether the pipe placeholder should have the same token as syntactic PA.


As far as priorities go, we should talk about whether readability is more important to optimize than writability, particularly now that ASCII operators have largely been exhausted. “Most code will be written once then read many times,” after all. In particular, non-ASCII Unicode syntax characters may be useful. A reserved keyword might also be worth considering, although perhaps not.

Let’s take a stark example of the writability/readability: a non-ASCII syntax character. may be more visually readable than the ASCII soup ~~, but the latter is far easier to input. optimizes for readability at the expense of writability; ~~ optimizes for writability perhaps at the expense of readability. To what extent the trade off is worth one way or the other is difficult to ascertain.

For what it’s worth, Perl 6, Julia, and Scala support and idiomatically use non-ASCII Unicode operators (there are also some amusing responses in a Reddit thread from 2016).

Of course, there is infamously also APL, which required encoding of numerous special operators in Unicode; its successor, the J language, eschews those non-ASCII operators in favor of ASCII digraphs—whether the resulting soup is readable enough to be worth it is up to you.

A long conversation over non-ASCII operators’ trade offs in Julia occurred in 2012. Julia’s REPL and IDE offer text “expansion” (contraction, rather) of LaTeX-style backspace-delimited abbreviations for non-ASCII characters, such as \dot for ·.

Perl 6 tries to accommodate both readability and writability by offering both non-ASCII Unicode operators and ASCII equivalents of the same operators, e.g., vs. !=: TIMTOADY / TIMTOWTDIBSCINABTE versus TOOWTDI. Perl 6 also offers advice on actually inputting Unicode characters in general.

There is also the precedent of FiraCode by @tonsky, a monospace font with numerous ligatures for improved visual display of many common operator spellings, and which sidesteps the writability tradeoff completely, in exchange for a one-time installation cost. Relatedly, the same author offers a clojure-unicode library for operators like λ, , etc., instead of fn, not=, ->, etc.—although in Clojure, being a Lisp, operators are literally the same as variable symbols.

Another aspect of non-ASCII is editor/IDE support for display, but it is already a fait accompli that ES supports non-ASCII variable names and Unicode characters in general. JavaScript’s application is the web, after all, as well as Node.js, both of which are already strongly coupled to Unicode.

Of course, all is this is moot if it is decided that $ is okay after all, although at least one TC39 member has above expressed strong disapproval of $.


In any case, I’ll try to update the issue’s and wiki’s list of possible placeholders sometime today with ~~, $_, $_$ (kidding), the from #89 for illustrative purposes, the elsewhere-suggested (?), and ? shared with PA. And whatever reserved keywords are available, I guess. private, anyone? I’m joking.

These currently are the most topical (ha) bikeshedding questions that I see now:

  1. To what extent must the pipe placeholder visually resemble @rbuckton’s PA placeholder: not at all vs. visually similar vs. identical?

    Relatedly, how much should the pipe placeholder stay compatible with @rbuckton’s HOF operators, which possibly would use {…}?

  2. What is the optimal tradeoff in writability (easily typed ASCII soup, e.g., ~~, or easily typed privileged valid variable, e.g., $) versus readability (less easily inputted non-ASCII syntax, e.g., )?

  3. Related to question 2: Can non-ASCII Unicode syntax characters be considered for the pipe placeholder, or must they be categorically excluded?

    A list of all possible Pattern_Syntax Unicode characters is available.

  4. If question 3’s answer is that non-ASCII Unicode syntax may be excluded, which non-ASCII syntax character would be visually understandable and/or less difficult to input?

    Many non-ASCII symbols are easily inputted in certain OSes. For instance, in macOS, several dozen typographic characters are directly typable using Option or Option + Shift (image of keyboards with various active modifier keys via Macworld article. It may be worth determining if there is an intersection of these easily typable non-ASCII characters across the default keyboard layouts of many OSes.

  5. For nullary operators vs. valid variable identifiers: How important is it that the placeholder be statically analyzable?

    A nullary operator can be always be statically recognized in the RHS. In contrast, a valid identifier can be statically recognized only if the rule is: “Tacit function calling may occur only when the RHS is a bare identifier, rather than allowing the RHS to be any arbitrary expression without a placeholder.”

  6. Operator vs. identifier again: How often would someone want to use an identifier from an outer lexical context of the same name as the pipe placeholder?

    If the placeholder is $, how often would a programmer want to use JQuery’s $ or another externally defined $ in a pipe’s RHS, as well as the pipe placeholder? How often would they be surprised if they could not, without defining a dummy variable for the outer $? How bad of a footgun (i.e., bug by programmer-unexpected behavior) would this be?

    Note that shadowing does apply to both identifiers and operators for nested pipelines, e.g., … |> f(y => do { const x = ■; x |> ■ + x }) or perhaps … |> f(y => ■ |> ■ + x).

To what extent the trade off is worth one way or the other is difficult to ascertain.

Although i think a non-ascii token is a nonstarter, i also think that code is read far, far more often than it is written, so i always prefer to optimize for readability over writability.

How often would someone want to use an identifier from an outer lexical context of the same name as the pipe placeholder?

If the answer isn’t “never”, then it’s precisely identical to “always”. Again, I will strenuously object to any advancement of this proposal if it magically and implicitly shadows any identifier (including any potential global identifier).

If the answer isn’t “never”, then it’s precisely identical to “always”.

Can you explain why you take such an absolute stance? The pipeline operator is going to have some implicitness, whether F-sharp (invocation) or Hack style (binding). What makes the latter "magic" and not the former?

The pipeline operator is going to have some implicitness, whether F-sharp (invocation)

An operator with the sole purpose of calling its right operand with its left operand as an argument doing just that is about as explicit as it gets, no?

I agree, and would say the same thing about Hack-style. Just as |> would be an explicit indicator of invocation, |> would be an explicit indicator of a scoped variable binding.

In any case, @ is probably impossible as a pipe placeholder due to its use as a prefix by the stage-2 decorators proposal.

Since decorators aren’t arbitrary expressions, I don’t think there’s a conflict. (With regard to readability: it’s unlikely for a decorator to be found in a pipeline.) It still doesn’t solve the relationship with the partial application proposal, though.

If @ is problematic, was @@ already suggested as a placeholder?

@charmander arg |> new @classDecorator class { constructor(...args) {} }

@ljharb new $ classDecorator doesn’t parse, so it’s unambiguous.

@charmander I was replying to "it’s unlikely for a decorator to be found in a pipeline", illustrating why @ is indeed a conflict.

@ljharb So what you’re saying is that it is not unlikely to find this type of expression

arg |> new @classDecorator class { constructor(...args) {} }

in a pipeline? To the extent that someone would be likely to confuse the two uses of @? That was my point about readability – I don’t think anyone is going to mistake a placeholder use of @ as a decorator.

I'm saying that it's possible, and that "likely" is entirely irrelevant.

@ljharb I disagree. There are plenty of confusing expressions that it’s possible to create in JavaScript. They’re not strong arguments against a given implementation by themselves.

When we have the choice of something that would work for 100% of use cases (an unused token, and/or an invalid identifier), why would we settle for less than 100%?

@ljharb @ works in 100% of use cases. It would be a character with an overloaded meaning because of decorators; I was mentioning that the impact of the overloaded meaning doesn’t seem significant. Thanks to the private fields proposal, there is no ASCII candidate left without an overloaded meaning, so readability of typical expressions is quite relevant when exploring single-character non-identifier placeholders that can appear anywhere.

If there's nothing left without an overloaded meaning that's a single token, then obviously a single token is unavailable - multiple tokens must be considered.

I finally updated the issue’s original post. Possible tokens are now laid out in a table format. There is a column for opinions expressed by TC39 members with citations; I did my best, but if anyone feels that their opinion is misrepresented, please let me know and I’ll fix it very quickly. There are also now two new categories of tokens: prefixed keywords and called keywords, as inspired by syntactic PA’s ?this and temporary bindings’ const(…) respectively.

I’ll try to update the wiki with the same, sometime in the next days.

FWIW in the current decorators proposal, you can only decorate a class declaration, not a class expression.

FWIW in the current decorators proposal, you can only decorate a class declaration, not a class expression.

It will probably need to be in a future proposal then. We still get feedback regularly on TypeScript that we don't support decorators yet on class expressions or object literal members.

Edited @ to note ambiguity only if class-decoration expressions become a thing, which does seem likely.

Added # as a possible placeholder. I’m becoming a fan of it; its use would usually be quite visually distinct from this.#x. See Freenode #tc39 2018-02-10:

<jschoi>: I’m wondering if # might be an option for nullary pipe placeholder; visual noise aside, would it actually be syntactically ambiguous with .# private fields? After all, a pipe placeholder can never occur after a ..

<+littledan> I don't think there's any syntactic ambiguity, but as far as intuition, I can see how people would want to avoid reusing # as it already has another meaning

of course, so does ?, so that's not really a tiebreaker

even if we adopt wycats's private shorthand syntax, there would still not be any ambiguity since that has to be followed by an IdentifierName

Edited @ to note ambiguity only if class-decoration expressions become a thing, which does seem likely.

There is no ambiguity even if class-decoration expressions become a thing (assuming “class-decoration expressions” means “class expression decorations”). @ is about the same as #.

@charmander I agree; it seems like @ and # are both viable options alongside ?. The question isn't really about syntax, but about what would make the most sense semantically (given that all three tokens mean other things).

Somehow I had removed the entry for @ completely. I added it back, noting the comments above. I am currently using # for my draft, but @ would be fine too. Both make equal thematic sense to me as a “topic variable”.

In light of the changes of nullish coalescing to ??: and optional chaining to ??.…, ??[…], and ??(…), I’ve updated the table’s entry for ? as a placeholder. See tc39/proposal-nullish-coalescing#23, tc39/proposal-optional-chaining#48, and tc39/proposal-optional-chaining#34.

The idea of multiple placeholders referring to multiple lexical topics may be necessary to explain partial application into n-ary functions. They may also be generally useful. To stay forward compatible with such a future add-on proposal, it is also worth considering how to name multiple topic placeholders, e.g., #, ##, ###. I’ve added a column to the table and a new question in the list of questions.

Personally, I have been liking # the most, followed by @. Both can refer to secondary and tertiary topics by repeating the symbol, which looks better than using number indexes like Clojure’s #(…), %/%1, %2, … would have you do.

Whatever will be chosen we will need the last expression case covered. _ is the goto suffix in that case. Just to be clear let's say $ was chosen, I am talking about $_. Which means that any multi-token proposal containing _ should be avoided—e.g. $_ which would spawn $__.

Please consider international keyboards when considering Typing of a Token.

For example, ^^ is difficult to write in any language that has accent marks. Think that there are people that write more frequently â than just ^. In those keyboards, you need at least 5 keystrokes to write ^^: shift + (^ , space , ^ , space)

Somehow I had removed the entry for @ completely. I added it back, noting the comments above. I am currently using # for my draft, but @ would be fine too. Both make equal thematic sense to me as a “topic variable.”

About @ It would be worthy to look to JSONPath. XPath applied to Javascript inspire JSONPath syntax. It uses @ to refer to itself.

JSONPath is just a way to specify which element do you want to access of a JSON object. It is a string that looks like Javascript expression: "$.store.book[0].title" or "$['store']['book'][0]['title']".

It also supports some capabilities by using @ as a symbol to refer to the current object, for example:

"$.store.book[(@.length-1)].title".

It looks like a function constructed like the one at x |> @.length-1. That function returns the index that wants to access.

Does it have to be a symbol? Why not use a predefined name, e.g. it, like Kotlin has with lambda expressions?

It’d have to be a predefined name that wasn’t already a valid identifier, i suspect.

Anything requiring Alt+Keycodes, eg. ■, is going to lead to Repetitive Strain Injury, imo.

What about empty backticks? `` representing "ditto"? As template literals and tagging functions would never use empty literal, it would be viable?

@aubergine10 no, as it won't be backwards compatible as `` is definitely a valid construct today and I have even seen them in the wild (styled.div`` and similar).

Double colon? :: -- and for extra refs, number between colons :0: etc?

commented

:? treated as a single token?

  • base:?.prop
  • base:?()
  • base:?['prop']

Special variables

Special variables can be terse, but choosing one would be an unprecedented privileging of a valid variable name in syntax. A special variable would lexically clobber any outer-scope variable of the same name, which may be an unexpected footgun. At least one TC39 member, @ljharb, has expressed strong disapproval of variable identifiers for this reason. They cannot be statically validated at compile time.

This paragrhaph misses something very important: the arguments variable inside a function body

I'm not aware of any strong case against it's usage:
on MDN

Note: If you're writing ES6 compatible code, then rest parameters should be preferred.

But this refers more to how a ...rest function parameter is an instance Array and not just arraylike like arguments
And having a function with a trailing ...rest param is nothing out of the ordinary.

function (paramA, ...restParams) {
 someListOrLog.add(arguments)
 // ...code
}

// can **not** be destructred
function x (....rest = [paramA, ...restParams]) {
  //! throws a SyntaxError
}

So there is a clear predence which is a core feature of JS/ES with wide use and usecases.

I also don't understand how that would make it less statically analysable than any other placeholder

The existingarguments is certainly statically analysable to a degree for sure. And what is the difference with using any other symbol? i mean an analizer has to be context aware anyways. Any analyzer should know what approximate scope he is in or constants declared outside of a function would also not be analyzable

I see no reason to disregard Special Variables by default and they need to be discussed

They have to be addressed more, and that hasn't really happend besides using $

Obvious ones would be arg, param or value, but since they are common names for function params, this propably isn't great.

My first thought is to look for terms with similar meaning in another language like Lating or Greek, since they would be much less common.

One i do actually really like is the latin word for 'thing': res (res itselve is obviously too commonly used as short for 'result'). Its accusative form: rem is an interesting idea though: (For the non-germans: accusative refers to an object of an action (i mean how much better can it get), eg. I'm giving this value to the function. Or its genitive rei (a genitive indicates possession or close association), eg. This value is that functions parameter

Personal opinions

  • I'm very stronly against any form that is already used for an operator, so no ?, ?? % etc
  • I'm not very much in favour of the non-alphanumeric valid variable names, so no $ or _
  • I kindof dislike #, and i'm not sure why, i don't think they are ambiguous with private field identifiers. Just personal
    preference i guess
  • i dislike very small or thin symbols like ^^, : or :: because they might be easily missed in code, especially if it's badly formatted. eg. x |> :+= 'some string'
  • i very much like @, it just feels like a placeholder and i don't think they would be ambiguous, unless you try to decorate a singleton class expression that's returned by a pipe, but i think there is no way to do that in a syntatically valid way anyways, and it's allready really weird to use a pipe that way without a decorator:
    // i mean ewww...
    x |> new class { constructor (piped) {this.piped = piped} }
  • after thinking about it, i acdually do really prefer rem or rei. they are smart, should not be too common, terse, easy to remember and actually easier to write than even #, @ or $ (keep international keyboards in mind)

I might be a bit biased because of the re:Zero character Rem, though

commented

Maybe I missed this but has {} been suggested? It's used as a sort of placeholder in other languages (like Rust). Any issues I'm unaware of?

Wouldn't that be an empty object literal?

commented

Wouldn't that be an empty object literal?

Whoops. Didn't have my JS hat on for that suggestion 🤦

@littledan feels that using ? as a placeholder might reduce hesitancy about Hack pipes.

The new Hack-pipes proposal uses ? for this reason. (The smart-mix proposal has been withdrawn in favor of simple Hack pipes.)

Obviously, there still can be future bikeshedding about the placeholder token later, but it’ll be worth it only if people on TC39 find Hack pipes in general palatable enough to advance.

? feels a bit overloaded, if you want to do something like conditionally using the placeholder it will get really confusing

double(5)
  |> doSomething(?0 % 2 === 0 ? 0 : ?0)

Also ? feels alright until you use optional chaining, then it becomes weird IMO :

x |> ?.property

x |> ??.property

I agree; ? is quite visually overloaded, and it is not my personal favorite choice.

My current personal choice is %, which is only also used for modulo, and which also resembles printf format strings. My second choice would be @ followed by #.

However, there’s a good chance that many members of TC39 would become more amenable to Hack pipes if ? were used; it might reduce the “yuck” factor for them. So the new Hack pipes proposal currently presents the token as ?. The precise character can be bikeshedded later, if TC39 becomes amenable to Hack pipes enough to advance them.

@aadamsx This isn’t the right issue to discuss that question, but, if you wish, you can read more about the trade-offs between F# and Hack pipes that TC39 is considering in this recent essay by Tab Atkins. But other issues are more appropriate for such discussion.

An alternative to the $ would be to rely on another currency like the or £ or even the ¤ symbol itself.

 x |> .property // on qwerty intl: alt gr + 5
 x |> £.property // on qwerty intl: alt gr + 4 + shift 
 x |> ¤.property // on qwerty intl: alt gr + 4
Token Typing Terseness Readability Multiples Notes TC39 members
Easy. Very terse. Very dis­tin­guish­able. €, €€, €€€; €0, €1, … demo
£ Clunky. Very terse. Very dis­tin­guish­able. £, ££, £££; £0, £1, … demo
¤ Easy. Very terse. Very dis­tin­guish­able. ¤, ¤¤, ¤¤¤; ¤0,¤1, ... demo

If you want to experiment with a character, I throw around a quick demo: https://aloisdg.github.io/PlaceholderBikeshedding/

image

What about the keyword val as an alternative of it? demo

What about the keyword val as an alternative of it? demo

Would conflict with people having a val variable in preexisting code

@KristjanTammekivi it was the same with await, wasn't it? We could fear that making val a keyword would impact more code base though.

@aloisdg await is only usable as a keyword inside async functions and its behavior as a keyword is different from the placeholder (which is closer to a variable). You can't shadow a variable named await inside an async function the way it or val could (it's a syntax error).

Big thank you for the demo! Very cool.

@mAAdhaTTah thank you for the comment. It is clearer now. For the demo, glad to be helpful :)

Edit: just saw that you were one of the person behind prismjs! This demo works thanks to your contributions. Thank you!

@mAAdhaTTah But isn't this pretty much exactly like the case with async/await? I mean, you can have old (or new) code like this

function foo() {
  const await = 5
  return await
}

and it still works because await is only a keyword inside async functions. Similarily, it/val would only be keywords in RHS of the pipeline, which is always new code so it's not a breaking change.

I can see how new keywords would be unsuitable to use with the standalone partial application proposal since that would make code like

const fooIt = foo(it, 5) // Call foo with "it" or partial application of foo?

ambiguous, but AFAIK this issue is only about the Hack-proposal, where the paceholder usage is limited to RHS of pipeline and thus new code only.

const fooIt = foo(it, 5) // Still calls foo with "it" as always
const bar = fooIt |> baz(it) // Same as baz(fooIt)

@noppa

Similarily, it/val would only be keywords in RHS of the pipeline, which is always new code so it's not a breaking change.

The problem isn't a question of "breaking changes" but ambiguity. If you make a function async and use await as a variable, the program throws a SyntaxError, making it very clear to you why const await is a mistake. If you had a variable named it, and you tried to refactor it (pun definitely intended :p ) into a pipeline, you could end up having it point to something completely different than intended because the program will run but have different semantics. It's a refactoring hazard to introduce a new variable binding in the pipeline like that.

Feels to me that good tooling could mitigate that and in the long term, I think people would sheer away from using the token as a variable name. But fair enough, I appreciate the sentiment.

If you make a function async and use await as a variable, the program throws a SyntaxError

  const await = 1
- function a() { return await + 1 }
+ async function a() { return await + 1 }

woops 😛 (ok this was tongue-in-cheek, prob more of an example of the dangers of unary + than anything else)

If I may chime in, I think @KilianKilmister makes a number of good points.

The topic variable represents a value, and it would be really weird if it was written with characters used for operators, like ?, %, ::, ^^, etc. Having the appearance of an operator would also make larger expressions less readable.
Someone would eventually write code like this just to give it a name: ... |> (topic => /* do stuff with topic */)(%)

In my opinion it should be an identifier, or at the very least resemble one, and the syntax should allow choosing a custom identifier. Similar to how in Perl certain constructs bind implicit $_ but you can choose a different name:

while(<>) { print $_; }
while(my $line = <>) { print $line; }

So, in the spirit of @aloisdg suggestion, how about re-purposing the var keyword? As far as I know, it cannot appear in an expression, so there would be no risk of breaking existing code with refactoring. The following 3 statements would then be functionally equivalent:

console.log(42)
42 |> console.log(var) // implicit binding to var
42 |<foo> console.log(foo) // explicit binding to foo

I intended to end here, but seeing that last example I wrote sprouted another crazy alternative. It's a bit off topic, but maybe you could have both the F#-style and Hack-style syntax, with no need for a placeholder token:

"F#-style" |> console.log // rhs is automagically called unary function
"Hack-style" |<foo> console.log(foo) // rhs is expression with named topic

@ljharb Those are statements, not expressions. You can't do for example String(var).

It's a bit off topic, but maybe you could have both the F#-style and Hack-style syntax, with no need for a placeholder token:

"F#-style" |> console.log // rhs is automagically called unary function
"Hack-style" |<foo> console.log(foo) // rhs is expression with named topic

If we require an explicit topic name for the Hack proposal, than we can have both by just using the F# proposal:

"F#-style" |> console.log
"Hack-style" |> foo=> console.log(foo)

@nicolo-ribaudo Almost, but not quite. differences
Hack-style allows await from the get go:

promise |<hack> await hack

Whereas in F#-style, this just won't do:

promise |> async foo => await foo

There have been long discussions about ways to support await in F#-style, maybe they'll come to a consensus but it probably won't be without carefully crafted grammar rules. If both operators were supported, then F#-style |> could disallow await/yield to keep the grammar simple. Those use cases would still be possible with Hack-style |<id>.

Why not () or has it been mentioned before?

Token Typing Terseness Readability Multiples Notes TC39 members
() Easy. Terse. (), ()(), ()()(); (0), (1), … demo

Because that’s far too likely to be a bug rather than intentional, and is far too subtle of a distinction.

Although the language grammar can certainly distinguish % for remainder from % for pipeline, the grammar rules could be simpler if a unique symbol is used, right?

How about %%?

I personally find ^^ really nice and would like to push for it.

  • I find it very aesthetically pleasing
  • It creates a nice visual way to remember its purpose for beginners (i.e. "The arrows point 'up' at the previous value" is similar to how many people were taught < and >)
  • It's (more) distinct from existing tokens (at least more than % or ? for example, which are existing tokens, and far more commonly typed tokens)
lookAtHowCuteThisIs
  |> smilingEyes(^^)
  |> joy(^^)

@jamiebuilds: I think these are some good points.

What would you think about just nullary ^ on its own? It's not like many people are using bitwise xor frequently. It’s in a similar situation to %, except it’s even more uncommon.

cuteThing
  |> smilingEyes(^)
  |> joy(^)

Pattern Matching is currently planning to use that as the "pin operator", altho there's the possibility it'll change to a different syntax.

@ljharb: Yeah, there are only four ASCII symbols left (%, ^, `, ~) that don’t yet have overloaded meanings, so they’re a scarce resource. I think pattern matching should consider reserving ^ for another use anyway, since pattern matching has the luxury of greater control over syntax inside its block; it has the luxury of being able to use a keyword ${ } instead. CC @tabatkins too.

A keyword will not work, because the pin operator can be deeply nested, and requiring parens await-like is basically a nonstarter. The only option for pattern matching is to use a syntactic token. However, another option is ${ }.

Oh, yeah, sorry, I had meant ${ }. I had discussed this with Tab before, and he mentioned ${ } too, but I forgot.

Yeah I'm a partisan for ${} now, since it serves an identical purpose to the syntax in template strings - breaking out of a restricted specialized syntax (pattern, strings) to allow general expressions, then lifting the result back into the specialized context (becoming a matcher, stringifying)

To me, the syntax ${} only makes sense for template strings because it has to distinguish itself from an arbitrary set of characters within the string. That's not the situation we're in though, we only have to distinguish it from surrounding syntax which is tightly controlled.

I also don't think many people who aren't language designers would see any sort of relationship between template strings and pipelines.

@jamiebuilds: I also don't think many people who aren't language designers would see any sort of relationship between template strings and pipelines.

To clarify, we’re not talking about using ${} for the pipe operator. Instead, ${ } refers to a possible “pinning” syntax from the pattern-matching proposal match ( ) { }.

We’re talking about match ( ) { } because match already uses ^ for match’s “pinning” syntax, but match pinning could instead use a different syntax (i.e., ${ }). This would free up ^ for the pipe operator (or something else).

In any case, I do find ^^ for the topic reference nice too (I agree that your example in #91 (comment) is happy and adorable; I like it a lot). But ^^ is also two characters long, when ^ also could work; ^ is not yet overloaded and is currently rarely used. So I think we should strongly consider ^ as an alternative topic token to %.

I had initially proposed % as a topic token because of its precedent from printf and Clojure—and then # and ? got disqualified due to their excessive overloading. But ^ is a good alternative that I had forgotten about. It points upwards to the “previous” step…and, perhaps, is happier.

@acutmore also pointed out on Matrix that ^ would give us ^-^ (topic minus topic), which is adorable.

I’m seeing a lot of enthusiasm for ^, with no love lost for %.

So I changed the Hack explainer and specification to use ^ instead of %.

^ and % are the two top contenders for the topic.