proposal-pipeline-operator
proposal-pipeline-operator copied to clipboard
Optional Hackpipes proposal
The optional hackpipes proposal |?> caught my eye.
A short-circuiting optional-pipe operator |?> could also be useful, much in the way ?. is useful for optional method calls.
Instead of using |?> in any subsequent pipe in order to take care of undefined branch I would propose two constructs:
value |?> expr
value |*> expr
The rules
value |?> expr
Expresion would execute only if value us not undefined. If the input value is undefined It would skip the expression and continue to the next pipe.
value |*> expr
The second one would abort pipe if undefined value is passed thus leaving the final value of the composed structure undefined.
example:
value
|?> one(^) // execute expression only if the input value is not undefined, otherwise skip to next pipe
|*> two(^) // execute expression only if the input value is not undefined, otherwise abort pipe
|> three(^) // this would execute only if the pipe is not aborted.
the above could be extended to get more granular control over pipes
value
|?![null, false, myVar]> one(^) // execute only if passed value does not match any in the list (in addition to undefined)
|*![null, false, myVar]> one(^) // the same as above but abort the pipe
|?[myVar, 34]> one(^) // execute only if value matches the listed values, otherwise skip
|*[myVar, 34]> one(^) // execute only if value matches the listed values, otherwise abort the pipe
This seems much more complex than something like:
x |> (^ == null ? no(^) : yes(^))
I could see a use case for bailing out of pipeline early.
// pseudo-syntax
let value = a()
|> ^ == null ? BAIL_KEYWORD : b(^)
|> c(^)
I don't know if anything like that exists in other languages around pipelines or similar constructs.
The closest we have to a "bail-out option" is optional-chaining itself, where null/undefined serve that purpose and short-circuit the rest of the property/method chain. With optional-chaining existing for property access (both dot and bracket) and function calls, it makes sense to me to spread that syntax to pipes, so that a null/undefined topic would immediately bail on the rest of the pipeline and just return null/undefined, identical to a.?b.c.d when a is null/undefined. This is already covered by #159.
The new thing introduced by this thread is the idea of a pipe operator that just skips a single pipeline step if the topic is null/undefined and continues to the next. I'm moderately opposed to this, I think:
- Pipe bodies can actually do something with a null/undefined, so this definitely could make sense (unlike the same in other optional-chaining cases, where you can't get any key off of a null/undefined, so skipping one won't make the next suddenly work).
- But it's still a mild variation on optional-chaining behavior, and small, relatively subtle variations in behavior make for code that's harder to read and reason about. Unless there's a strong use-case for the slight variation, it's usually better to just stick with the one behavior and let people use slightly less convenient syntax for cases it doesn't quite cover. (It also means that's one less combination of ASCII chars we can use for something more distinct.)
- As @ljharb points out, skipping a single pipeline step is easy to do with existing syntax - you might even be able to just leverage optional-chaining itself, depending on what you're doing:
void 0 |> ^.?foo |> console.log(^)will successfully logundefined. This isn't true of the one that acts more like optional-chaining, where you'd have to nest the later steps inside the conditional to achieve it without a special operator.
This seems much more complex than something like:
x |> (^ == null ? no(^) : yes(^))
What ever happen to not making decisions unless we come to a consensus?
@aadamsx your inappropriately snarky response not withstanding, consensus means "zero people must object to a proposal advancing", which also means "a single person can block any change".
In this case, I said "seems" and wasn't making any decisions, and github threads aren't TC39's plenary meetings, so your comment is even less on topic than that.
I wasn’t actually talking about your comment here, but over the years you and I have had conversations where you had said TC39 and the community in general, doesn’t do anything without consensus… just thinking it’s ironic that we needed up going hack without consensus after all.
@aadamsx The committee needs to come to consensus. The community does not. The committee came to consensus on advancing to stage 2.
This entire side thread is off-topic. Please keep your comments in this issue to commenting on the issue.
This seems much more complex than something like:
x |> (^ == null ? no(^) : yes(^))
Quick question: Would that inline the topic twice, or assign it to an engine-internal temp variable? Assuming the latter?
@sdegutis that'd be up to the engine needs unobservable by the user.
I just realized that the |?> example in the explainer doesn’t short-circuit. I meant for it to short-circuit, and I’ll fix that later.
@sdegutis that'd be up to the engine needs unobservable by the user.
The semantics of it are pretty important. If topic is simply expanded, and the computation is expensive (e.g. an API call), then inlining topic multiple times would be a serious problem and a show-stopper in many cases. If it's necessary to guarantee single execution of LHS regardless of topic instances in RHS, this should be in the spec.
@sdegutis obviously it wouldn’t be permitted to run any computation more than once, in any scenario. It’s not a macro.
Got it, thanks. To me it wasn't exactly obvious, so clarification was appreciated.
Yeah, the LHS is evaluated, and the result is then bound to the placeholder.
The explainer does indeed not make this quite clear; I'll fix. The proposed spec text is clear, tho.
I'm a bit concerned about the font ligature breaking with the question mark between the "|>" characters
