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

Bikeshedding the Hack topic token #91

Open
js-choi opened this issue Feb 4, 2018 · 778 comments
Open

Bikeshedding the Hack topic token #91

js-choi opened this issue Feb 4, 2018 · 778 comments
Labels
bikeshed Discussion about naming or similar

Comments

@js-choi
Copy link
Collaborator

js-choi commented Feb 4, 2018

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 #_.

@ljharb
Copy link
Member

ljharb commented Feb 4, 2018

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.

@aikeru
Copy link

aikeru commented Feb 4, 2018

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.

@ljharb
Copy link
Member

ljharb commented Feb 4, 2018

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

@littledan
Copy link
Member

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?

@gilbert
Copy link
Collaborator

gilbert commented Feb 4, 2018

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.

@mAAdhaTTah
Copy link
Collaborator

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.

@littledan
Copy link
Member

@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.

@js-choi
Copy link
Collaborator Author

js-choi commented Feb 5, 2018

@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.

@ljharb
Copy link
Member

ljharb commented Feb 5, 2018

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.

@barneycarroll
Copy link

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).

@stephaneraynaud
Copy link

stephaneraynaud commented Feb 5, 2018

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).

@mAAdhaTTah
Copy link
Collaborator

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))

@charmander
Copy link

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

@aikeru
Copy link

aikeru commented Feb 5, 2018

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.

@js-choi
Copy link
Collaborator Author

js-choi commented Feb 6, 2018

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.

@gilbert
Copy link
Collaborator

gilbert commented Feb 7, 2018

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

@mAAdhaTTah
Copy link
Collaborator

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

@stephaneraynaud
Copy link

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

@js-choi
Copy link
Collaborator Author

js-choi commented Feb 7, 2018

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.

@ljharb
Copy link
Member

ljharb commented Feb 7, 2018

@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”.

@gilbert
Copy link
Collaborator

gilbert commented Feb 7, 2018

@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.

@ljharb
Copy link
Member

ljharb commented Feb 7, 2018

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.

@gilbert
Copy link
Collaborator

gilbert commented Feb 7, 2018

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.

@rbuckton
Copy link
Collaborator

rbuckton commented Feb 7, 2018

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 $...

@aikeru
Copy link

aikeru commented Feb 8, 2018

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.

@jridgewell
Copy link
Member

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.

@js-choi
Copy link
Collaborator Author

js-choi commented Feb 8, 2018

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.

@stephaneraynaud
Copy link

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)
...

@mybearworld
Copy link

"Hack pipes favor more common expressions" and "Hack pipes might be simpler to use" seem to apply just as well to that variant of F# pipes as well.

@nmn
Copy link

nmn commented Apr 18, 2024

@mybearworld We are no longer discussing F# pipes. I prefer them too, but Hack Pipes are more likely to actually ship and if you keep arguing we will never get any pipe operator.

Further, this issue is only about the token to be used in the case that we move forward with the Hack pipes approach. Your comment doesn't belong here.

@mybearworld
Copy link

I was responding to the comment above about extending the label syntax.

@sant123
Copy link

sant123 commented Apr 19, 2024

What about:

value |> foo(!!)?

Yeah would be cases like

value |> foo(!!bar)

But can be handled as a valid token if !! is only found. Also is not disqualified.

@mybearworld
Copy link

That would be ambiguous. foo |> !!(bar) could be foo |> (!!)(bar) or foo |> !(!bar).

@zlumer
Copy link

zlumer commented Apr 20, 2024

@nmn

if you keep arguing we will never get any pipe operator.

I'd argue that no pipe operator at all is much better than this abomination.

@nmn
Copy link

nmn commented Apr 21, 2024

@zlumer That argument doesn't belong in this issue either. Read the title. Make that argument in a different issue.

@pedro00dk
Copy link

pedro00dk commented May 11, 2024

Hello everybody.
First contribution here. This issue is very long, so I did not read through it entirely.
It seems most suggestions are around small static tokens, and a few of them allow the developer to set a variable name, but requiring it to first be declared and then used.

I'm not sure if it this was considered already, but it could be interesting to have a dynamically named variable prefixed by a token that tells it is the pipeline token, without eager declaration.

const greeting = 'fellow user'
    |> `hello ${#user}`
    |> #lower.toUpperCase()

const occurences = 'my very long string'
    |> [...#string, ...#string]
    |> Object.groupBy(#doubledChars, v => v)

In this case the token would be a combination of prefix and JS identifier (#<identifier>).
It would not conflict with private properties, which must always be accessed via dot notation.
It would also not conflict with normal variables because of the token prefix.
<identifier> is dynamically assigned, it does not need to be declared eagerly.
Different <identifier>s can be used on different stages of the pipeline.

Since _, and $ are valid identifiers, #_, #$, would also be valid (same example as above with short tokens):

const greeting = 'fellow user'
    |> `hello ${#_}`
    |> #_.toUpperCase()

const occurrences = 'my very long string'
    |> [...#$, ...#$]
    |> Object.groupBy(#$, v => v)

My suggestion would be to use # as token prefix, as it is being used for private properties, so it already denotes a variable or property vibe, and is used in some new proposals like the tuple and records. So it would not be too alien for developers like some other suggestions I saw. AFAIK it would not introduce any ASI issues too.

The two issues that I see with this approach atm are:

  • A token used multiple times in the same pipeline stage could be given multiple identifiers, but that is really a very minor, we either allow it or not.
  • Since it is a dynamic identifier, not sure if this would pose any implementation issues by the JS engines.

@shicks
Copy link

shicks commented May 11, 2024

It would not conflict with private properties, which must always be accessed via dot notation.

This is not so - you can also write #priv in obj for "brand checking", and that would conflict with using one of these identifiers as LHS of in. It's a very tiny conflict, but enough to be a problem.

@yordis
Copy link

yordis commented May 11, 2024

This issue is very long, so I did not read through it partially.

@pedro00dk trust me! We know! Dont worry, "bikeshedding" issue was created to give space to people to, well, bikeshed. In all honestly, by now, we collected enough bisons that we ran out of combinations of characters and ideas. We should start reaching for egyptian holographic.

image

@pedro00dk
Copy link

pedro00dk commented May 11, 2024

@shicks
Interesting, I did not know it could be used with the in operator. Although, I'd say # could still be a valid option. I have no data to back me up, but I suppose this brand checking is not a common pattern. When inside a pipeline:

  • Cast the in LHS to string (|> `${#token}` in obj). It is not convenient though, and some devs would not understand the need for the cast. It also would not work for symbols.
  • Always evaluate the token when used in the in LHS. Meaning brand checking can't happen in a pipeline. It is simpler overall, and the in operator kind of is already a mini monster, by accepting something that is not a valid value, better leave the complexity to it.

Another ASCII option for token prefix could be backslash \, AFAIK it is only part of the JS grammar inside strings and regex to escape characters, so it would also not introduce ASI issues, it looks clean, and kind of goes with the lines and angles theme of |>

const greeting = 'fellow user'
    |> `hello ${\user}`
    |> \lower.toUpperCase()

const occurences = 'my very long string'
    |> [...\string, ...\string]
    |> Object.groupBy(\doubledChars, v => v)

@mybearworld
Copy link

A major advantage of the pipe operator is that you don't need to come up with a name for every step. This would get into the way of that.

@pedro00dk
Copy link

@mybearworld
Since the idea is that the token would just be a prefixed identifier, you can just use a short one. \_, \$, \v, etc..., only two characters. I agree that for most use cases a short identifier would definitely suffice, but being able to name the topic, even though it is just decorative, can help code reading and serve for documentation purposes, especially in longer pipelines with more complex operations.

@mybearworld
Copy link

The majority of pipes would be pretty simple, though. If the operations done in pipes get more complex where having names for the topic would be beneficiary, there are still options without requiring a named topic:

  • Resort to using regular variables:
    const string = 'my very long string';
    const doubledChars = [...string, ...string];
    const occurences = Object.groupBy(doubledChars, v => v);
  • Alternatively, use an IIFE:
    const occurences = 'my very long string'
        |> ((string) => [...string, ...string])(%)
        |> ((doubledChars) => Object.groupBy(doubledChars, v => v))(%)

@ljharb
Copy link
Member

ljharb commented May 11, 2024

@shicks Interesting, I did not know it could be used with the in operator. Although, I'd say # could still be a valid option. I have no data to back me up, but I suppose this brand checking is not a common pattern. When inside a pipeline:

  • Cast the in LHS to string (|> `${#token}` in obj). It is not convenient though, and some devs would not understand the need for the cast. It also would not work for symbols.
  • Always evaluate the token when used in the in LHS. Meaning brand checking can't happen in a pipeline. It is simpler overall, and the in operator kind of is already a mini monster, by accepting something that is not a valid value, better leave the complexity to it.

Another ASCII option for token prefix could be backslash \, AFAIK it is only part of the JS grammar inside strings and regex to escape characters, so it would also not introduce ASI issues, it looks clean, and kind of goes with the lines and angles theme of |>

const greeting = 'fellow user'
    |> `hello ${\user}`
    |> \lower.toUpperCase()

const occurences = 'my very long string'
    |> [...\string, ...\string]
    |> Object.groupBy(\doubledChars, v => v)

its commonness is irrelevant; it would never be acceptable to prevent its usage in any otherwise valid context.

@rbuckton
Copy link
Collaborator

Another ASCII option for token prefix could be backslash \, AFAIK it is only part of the JS grammar inside strings and regex to escape characters, so it would also not introduce ASI issues, it looks clean, and kind of goes with the lines and angles theme of |>

The \ character can be used to escape unicode characters in identifiers: https://github.com/tc39/test262/blob/main/test%2Flanguage%2Fidentifiers%2Fother_id_start-escaped.js

@btoo
Copy link
Contributor

btoo commented May 12, 2024

A major advantage of the pipe operator is that you don't need to come up with a name for every step.

It would be useful to optionally name the token for handling nested pipes, lest we'd be limited to piping only the immediate pipe's values (without declaring intermediary variables)

The \ character can be used to escape unicode characters in identifiers: https://github.com/tc39/test262/blob/main/test%2Flanguage%2Fidentifiers%2Fother_id_start-escaped.js

one possible unintended ergonomic consequence of \ is having to escape them in stringified JavaScript (as would be the case in e.g. evals)

@zaygraveyard
Copy link

zaygraveyard commented May 12, 2024

What about @pedro00dk's #<identifier> suggestion but require parenthesis in some way when used with in, as to distinguish it from "brand checking"?
Like 'key' |> (#_) in obj is equivalent to 'key' in obj, and 'key' |> #priv in obj is equivalent to #priv in obj (the "brand checking").

@mybearworld
Copy link

That would be pretty unintuitive and confusing behavior, I'd say.

@ljharb
Copy link
Member

ljharb commented May 13, 2024

@zaygraveyard a private name is not just a string identifier with a # prefixed - it must always be statically present and can’t be referenced dynamically.

@zaygraveyard
Copy link

@ljharb I'm confused, where did I reference a private name dynamically?

@ljharb
Copy link
Member

ljharb commented May 13, 2024

@zaygraveyard hm, on a reread i see that you did not, my apologies. the likelihood of confusion from your proposal, then, seems high :-p

@geekley
Copy link

geekley commented Aug 24, 2024

IMO this feature would be much better with .=> syntax, which is succinct and self-explanatory.
Using . in the syntax would naturally allow a ?. for the null-safe version.
And this feature feels similar to an immediately called lambda, so it makes sense.

First, the more verbose version, where you specify a name:

expr().$ => f1($, etc);
// semantics are as if you did this (but optimized, not using actual lambdas):
($ => f1($, etc))(expr())

// $ is just an example, you can use any variable name, of course
const result = getId()?.id=> find(id, etc).obj=> add(a, b, obj);
// semantics are equivalent to this (but optimized):
const result = (id => id == null ? undefined : (obj => add(a, b, obj))(find(id, etc)))(getId());

// this syntax is generic enough to also let you not want to return a value
getObj()?.obj=> { if (!obj.valid) return; log(obj.name?.name=> uppercase(name)); };
// means:
(obj => { if (obj == null) return;
  if (!obj.valid) return; log((name => name == null ? undefined : uppercase(name))(obj.name));
})(getObj());

To make it even more succinct, make specifying the name optional.
So when the previous token is an identifier, such as a variable or field name, that same name is used by default.
Otherwise (e.g. an expression) a name of $ is used by default.

getObj().name?.=> log(name);
// equivalent to:
getObj().name?.name=> log(name);

baz(foo().=> bar($));
// equivalent to:
baz(foo().$=> bar($));
// meaning:
baz(bar(foo()))

@ljharb
Copy link
Member

ljharb commented Aug 24, 2024

The => makes it seem like it's creating an arrow function, where instead it would be immediately evaluating its righthand side.

@geekley
Copy link

geekley commented Aug 24, 2024

@ljharb Fair enough.
In any case, I still think we could have this behavior where the preceding identifier is reused if available, e.g.:

getObj().name?|> log(name); // property `name` becomes an identifier `name` for the right-hand side

We could require specifying it where a preceding identifier is unavailable, e.g.:

getObj()?|>obj: log(obj); // value becomes an identifier `obj` for the right-hand side
getObj()?.name|>x: log(x); // this form can also be used to change the default name when available

Different default identifiers simplify reusing them further, instead of having % or whatever for everything.

getObj().parent?|> parent.name|> join(parent.id, name)|>str: log(parent, str);
// properties `parent` and its `name` are both identifiers available on latter parts too

@DominoPivot
Copy link

DominoPivot commented Sep 4, 2024

we could have this behavior where the preceding identifier is reused if available

This would cause implicit shadowing and introduce all sorts of ambiguities, as previously mentioned. Can you even tell what each identifier would refer to in this example?

function cook(recipe, ingredients) {
    return (
      recipe.ingredients
      |> prepare(ingredients)
      |> measure(recipe.ingredients, ingredients)
      |> other: mix(ingredients, other)
      |> ingredients: heat(ingredients)
    );
}

I think the whole value of the Hack placeholder is that it's short and recognizable. If we can't have it, we might as well write pipelines as a series of assignments and not introduce new syntax.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bikeshed Discussion about naming or similar
Projects
None yet
Development

No branches or pull requests