-
Notifications
You must be signed in to change notification settings - Fork 107
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
Question about ^ when a "child pipeline" is present. #208
Comments
This is correct: the |
Thanks, that's what I expected. It's a little confusing to read in the example above, but that's a real example. It's a shame the inner |
Actually, I'll leave this open. As it's probably worth discussing with the committee how to handle this confusion? Are there any possible ways to rename this? Any ideas around how to make this more readable? @tabatkins |
I think it's the same readability concerns whenever any chains get "too much stuff" shoved into it - the solution in my experience tends to be "de-inline stuff as needed". In this case: const mapper = () => getData$
|> catchError(err => {
console.error(err);
return EMPTY;
})(^);
clicks$
|> concatMap(mapper)(^)
|> ^.subscribe(console.log) |
Yeah, the general solution is "don't chain that much"; you're over-nesting in an operator meant to reduce nesting. ^_^ That said, if this is necessary, do-expressions will solve the problem; you can assign the outer pipe's topic to a variable inside the do-expr and then access that var in the inner pipe. I do expect do-exprs to eventually land (we've been circling them for years, but the champions are doing good work at cleaning up the remaining issues), so I don't think pipeline has to particularly worry about this issue on its own. |
That said, looking back at the example in the OP, I think it's fine? It doesn't use an arrow func to capture the piped topic at any point, so it's still relying on implicit topic-passing, and thus is directly convertable into Hack-style with the naive transformation. // original
clicks$.pipe(
concatMap(() => getData$.pipe(
catchError(err => {
console.error(err);
return EMPTY;
}),
)),
map(result => result.someData),
)
.subscribe(console.log);
// to...
clicks$
|> concatMap(()=>getData$
|> catchError(err => {
console.error(err);
return EMPTY;
})(^) // <-- close catchError, pass getData$
)(^) // <-- close concatMap, pass clicks$
|> map(result => result.someData)(^) // <-- concatMap() result
|> ^.subscribe(console.log); The extra parens aren't great to read, I'll grant you, but if the functions are written to take their data as an argument it's a lot better: clicks$
|> concatMap(^, ()=>getData$
|> catchError(^, err => {
console.error(err);
return EMPTY;
}))
|> map(^, result => result.someData)
|> ^.subscribe(console.log); And yeah, as @ljharb says, if you get serious with "reduce nesting" this reads fairly cleanly even with pure "pass topic as separate call": function logDataError() {
return getData$
|> catchError(err=> {
console.error(err);
return EMPTY;
})(^);
}
clicks$
|> concatMap(logDataError)(^)
|> map(result=>result.someData)(^)
|> ^.subscribe(console.log); But going that far isn't necessary if prefer the complexity in the first step. |
Also note that with the Hack pipelines, if you have a single step, it's probably nicer just to inline the input instead: -getData$ |> catchError(^, err => {
+catchError(getData$, err => {
console.error(err);
return EMPTY;
}) I guess the same is true for when the RHS is curried: -getData$ |> catchError(err => {
+catchError(err => {
console.error(err);
return EMPTY;
-})(^)
+})(getData$) That makes this specific example a bit less cluttered: clicks$
|> concatMap(() =>
catchError(err => {
console.error(err);
return EMPTY;
})(getData$)
)(^)
|> ^.subscribe(console.log) |
Yeah, I thought about that, but for the sake of the discussion didn't want to change the structure of the example. |
In fact, this is a good little example. The history of software development is fight against complexity, and various attempts have been done. OOP class is the one. RxJS is a relatively clean framework, and mostly because they employ clean Functional Programming, and enjoyed the robustness of the simplicity.
This is an unfortunate mention to express the fact Hack-style pipeline-operator is not robust against this shallow level of complexity. OOP class fails to prove robustness in React framework. I expect RxJS will be a very hard framework to code in future. I don't think users can follow this confusion. I've been a FP programmer for years, and F# style pipeline-operator helped me to write code so much easier and lot of freedom obtained. F# is based on math, and the pipeline-operator is also pure math operator which is essentially robust against complexity.
So, after inventing a new artificial mechanism that is not essentially math-operator, the new problem has emerged, and in order to cover up the problem we need to introduce new Please note that
Yes. microsoft/TypeScript#43617 (comment)
FP or Functional operator or Math algebra operator or F# style operator never has context. Context is the fundamental sin of the complexity that we the programmers-human never be able to handle. Bug reason. IF we smartly have a function-composition-operator such as
I can teach FP beginners in my book based on Math/algebra. In Hack, This is not math, and lose simplicity and robustness against the complexity. What is |
I don't understand why this is bad? So what if programmers can mix & match method chaining & Hack-pipelines? Some codebases mix together OO & Functional Programming. I agree mix & matching styles isn't ideal, but happens. Perhaps you could write an ESLint rule to warn against mixing chaining & pipes? This would be helpful for beginners & experts who are transcribing into pipes. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
I'm going to skip over the significant digression about math; that's not directly relevant to this topic. But:
As I said in the comment this was responding to, the point of the pipeline operator is to linearize code and reduce nesting. It is not meant as a general-purpose code-flow operator meant to chain together large amounts of code. It can be misused as such, sure, but it being somewhat painful to do is an accidental benefit; JS has many good ways to organize code in a more readable fashion, and when those are used it's easy to write pipelined code without issues. That said, it is also still true that Hack-style and F#-style pipelines are precisely identical in power (ignoring await/etc issues); if one wants to write a nested expression that uses more pipelines, and mix mention of the outer topic and inner topic together, one can do so with an arrow function, identically in either syntax. You just have to immediately invoke it in Hack-style, whereas it's implicitly invoked in F#-style (the "put a |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Perhaps we need to uncurry functions and libraries after hack pipe? So OP example would become: clicks$
|> concatMap(^,() => getData$
|> catchError(^, err => {
console.error(err);
return EMPTY;
})
)
|> ^.subscribe(console.log) I think it is now clear? Uncurring will be create a temporary turmoil in functional js ecosystem, but after some years we will probably adopt it. |
@HKalbasi Right, I gave that as one of the options in #208 (comment). And note that the majority of the JS ecosystem is already "uncurried". ^_^ @stken2050 I'm sorry, but you're wrong. The monad laws cover how the bind/return monadic operators must work. The F#-style pipe operator can be thought of as the map operator of the Function functor. It is absolutely not the monadic bind operation, over the Function monad or any other. Regardless, this is a great digression from the topic of this thread, which was covering nested pipes and the effect on the |
This comment has been minimized.
This comment has been minimized.
This is what we are afraid of... Never. |
No, the F# pipe operator transplanted into JS is not associative, because rearranging parentheses changes meaning. (x |> f) |> g /* desugars to */ g(f(x))
x |> (f |> g) /* desugars to */ (g(f))(x) |
I really can't help but think maybe there's another kind of pattern library that's waiting to be born from this kind of thing. I've felt that way every time I've seen RxJS code or tried to use it myself, and especially feel that way using this example. It intuitively feels to me like Hack Pipes will make such a new library a lot more realistic than ever before. I don't have any specific examples right now, it's just, on the tip of my mind so to speak. |
I'm going to go ahead and close this issue; it's gotten way off track. As far as I can tell, the original issue raised by @benlesh has been addressed as well: do-exprs, when they mature, will allow easy renaming of an outer topic so it doesn't clash with nested pipelines. While it's slightly less convenient syntax-wise, an IIFE also does the job, at the cost of preventing async/yield/etc in the inner pipe. Finally, there is discussion focused on the possibility of topic-renaming going on in #203. |
RxJS plans to move toward whatever pipeline operator lands. It's very frequent in RxJS that we have child observables that have been set up with pipes of their own. The pattern below is very common:
I'm not sure how to cleanly handle this with Hack pipeline:
It seems like the presence of a
|>
in a child function would give new meaning to the^
on its RHS until the end of the function's context?I've seen in other examples that
^
can be used within closures. Which is why I have this question.The text was updated successfully, but these errors were encountered: