-
Notifications
You must be signed in to change notification settings - Fork 110
To what extent is functional-style not already addressed in userland? #164
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
Comments
There's already several discussions about adding a pipe function instead of an operator in the issues. #154 (closed), #131 and #146 I guess what you are proposing is a bit different because those issues are about adding a new function to the spec while you are suggesting that it's left for users to implement (i.e. keeping the status quo). Still, there's some general discussion about function vs operator in there that applies here too. |
I'd suggest there's a subjective readability benefit to the operator that you don't get. A long function call is subjectively more onerous to read because pipe(line operator) encourages longer chains of function calls, and on those longer chains, the explicitness of the operator itself next to each line/call makes it easier to track, that saying so at the top with a long line of commas. My understanding also is that engines would be able to optimize away some cases of inline arrow functions, eliminating the overhead of the function call and perhaps even the creation of the function object itself. This wouldn't be feasible in a userland Lastly, an operator would be much easier to manage for static type systems. I don't know how much influence that has on JS's language design, but I don't know if these are functional-specific (I'd argue the third is, at least), so may not be exactly what you're looking for, but those are some advantages the operator has over the |
(Note before I start responding: my point in starting this thread is to make sure that my arguments against the functional-style pipeline aren't missing anything major. I intend to argue for topic-style pipeline.)
Oh, not necessarily. Whether this is actually done in userland, or JS defines a built-in for it, doesn't matter much to me.
Thank you for the links! They were very helpful; my exact sync pipe() was already talked about, and a cleverer async pipe was also given; neat.
Yup, def a valid point, but a more minor one. The major benefit of pipeline is unfolding nested function stacks into linear, reading-order-is-execution-order code. We already have syntax for some instances of this, in the form of method chaining. I support making it easier to do this more widely (method chaining is popular for a reason), but if we can do it without minting new syntax, all the better. That is still a valid point, tho, so thanks.
Right, a JS built-in that still looks like a function could get around that, so not a concern for my purposes. (I'm somewhat dubious about how much could really be optimized away, but JS engines have done plenty of miraculous things, so who knows.)
Yeah, writing the type of an n-ary compose is... tricky. But it looks like (from reading the other threads linked) that Flow, at least, provides such a type directly precisely to allow things like this to be typed more easily, and it looks like people have figured out how to get TS to deduce the type. But also, a built-in function for it could be given proper typing directly, so this isn't a concern for my purposes. |
This is effectively the argument for F#: instead of creating a topic token and its associated complexities, lean on the existing syntax of the arrow function. I don't personally think that's a strength of the Smart Pipeline.
I honestly have no idea how that would work. If it's a variable or a method (e.g.
...maybe it could be, I am not an engine dev, what do I know? I will also add that the Babel plugin itself already optimizes away a number of arrow function cases. My assumption is the much-more-clever engine developers would be able to expand on that.
The types exist, yes, but I've experienced enough headaches with typing them that I don't really use them. Maybe someone can chime in if they've seen this improve (I know generic functions in pipelines were painful). |
(I'm not wanting to debate merits of the proposals in general in this topic, but I will note that functional-style still has to invent special-case syntax for some of its cases, notably
Oh, engines do this all the time actually, tracking whether you've overridden a built-in or not, and using a more optimized, less pessimistic implementation if it's untouched. |
Ok, I'll hold off on that debate until later then. Well then, "what do I know" indeed. That said, that wouldn't apply to a "userland" |
Right. Also, I put in a clarifying paragraph in the OP (marked explicitly as an edit to avoid making later comments seem weird) clarifying that I was using words badly, and I really meant "pipeline as an ordinary, possibly built-in, function". Thanks for the push to clarify it. ^_^ |
The partial application proposal promises to use a shallow stack, so function divide(numerator, denominator) {
if (denominator === 0) throw new Error('Division by zero');
return numerator / denominator;
}
return 0 |> divide(5, ?) Would give you a nice, short stack trace like
I didn't see mention of this in the Smart proposal, but I'd expect the same result there. AFAIK the spec doesn't give any tools to observe stack from userland, but I'd imagine the implementers would want to keep the current behavior of Function.pipe(
0,
_ => divide(5, _)
)
Uncaught Error: Division by zero
at divide
at <anonymous>
at Function.pipe
// Little better with .bind but not ideal for UX/readability
Function.pipe(
0,
divide.bind(null, 5)
)
Uncaught Error: Division by zero
at divide
at Function.pipe Performance-wise, I have no idea how well the engine would still be able to treat |
Yes, |
@tabatkins for you, perhaps, but that means you're not in the target audience for this proposal. Infix math operators are not needed, semantically, but they make math-heavy code more approachable. The same goes for the pipeline. Point-free composition is a major pattern in FP, being able to tell it apart from other function calls helps make the code more clear, visually. In a typical app written by an FP mind, function composition is more prevalent than math. FWIW, I wouldn't be in the target audience either (I (try to) think functionally, but serialize my thoughts to plain JS), but I hang around people who would. |
Given that I asked this question in the context of putting together slides for a presentation on the pipeline operator, I think I'm 100% the target audience for this proposal. ^_^ I just wanted to make sure I wasn't missing anything obvious in the functionality of the functional-style syntax specifically that would make it more competitive amongst the possible syntaxes. |
@tabatkins FP in JavaScript eschews statement composition for function composition. It's all function calls. Without the pipeline, you end up in Lisp land... Oatmeal with fingernails clippings. I didn't mean that you're not welcome to discuss this, and I'm glad you're setting your sight on this, but the fact that you see the visual aspect of syntax as a minor point is significant. The people behind the proposal do see syntax as a major aspect, because the subset of JS they use doesn't have any otherwise. Edit: "not any" is an exageration, but there's, comparatively, a dearth of syntax when programming functionally in JS. |
@tabatkins is it true you're one of the "committee" members?
So for all the conversations we've had on this proposal via this repo and elsewhere, for the babel implementation, and for the proposal to get past stage-1 and the work on stage-2, you got from that that this can all be done in userland, with the implication there's no need for a Not singling you out, but in general we could have a
I think the operator allows functions to be composed together in a much more readable way that looks like a fluent style query syntax. We could achieve something similar by writing . Just take the |
There was a ton of demand for I mentioned elsewhere that classes were good at modeling artificial problems. The corporations is the quitesential human contraption, and OOP was unsurprisingly a good match when the time came for automating them. Corporations were full of programs that had been written for people not only to read, but also to interpret. These workflows that had often been designed by people without systems qualification, and, given is therefore logical that these processes relied on abstractions that are easy on the brain, even if conceptually sub-optimal. Taxonomy is easy on the brain, and corporations are almost universally built as hierarchies. Per Conway's law, it isn't surprising that classes and inheritance ended up being a great tool in the industrty. Also a good cultural fit. This created a large cohort of smart people who think in OO and who applied their skills to other domains. ... and those folks wanted to use their skills in the browser, and some of them were shaming JS for not having classes. So having classes in JS was important from a mostly sociological standpoint. However, now that we have these folk's attention, I'm not sure that dedicating that much energy to classes is a good use of the TC's time. JS is about to lose the exclusivity in its niche (IE11 will soon be out, and WASM-derived solutions will thus become mainstream). You want to skate where the ball is going to be, not where it was five years ago. I don't know if FP as it is done today is where the future lies, but it is a step in the right direction. It is currently a fertile breeding ground of ideas (reactive streams, lenses, patch-oriented state management AFAIK all originate from there). Encouraging FP in JS today is a good bet for the language's future. |
I'm gonna stop responding here, because I don't know how many times I can repeat that I want a pipeline operator, and was just making sure there was nothing I was missing functionality-wise in the functional-style syntax. |
Sorry @tabatkins from the title and the first post by you, it seemed you were making the case against it. After looking at all your comments and turning my head sideways, I can see what you're trying to do (but it wasn't obvious to start). Also, you say you're going to argue for topic-style pipeline? Why that style? Why not F#? And why not just argue for both styles to be implemented (and have the "committee" pick which one they prefer)? |
@aadamsx Tab is on the committee, and having more votes who count behind any proposal is a plus at this point. The winds seems to be turning towards "topic"-based pipes, which I'm afraid will kill partial application (too close in syntax and purpose to have both). P-app is useful in its own right in non-pipeline scenarios, but a little worse in pipelines than topic/smart pipes, even when combined with F# style. So while "topic/smart" is IMO [ missing the forest for the tree / aiming for a local maximum / too far on the Playmobil side of the Lego-Playmobil scale ], having pipelines land at all is still a major plus. |
I know this thread is closed, but while working on a project this evening, and doing a somewhat-annoying composition without either |
Uh oh!
There was an error while loading. Please reload this page.
I've been thinking on this a bit again, and as far as I can tell, most of the functional-style's functionality can be done purely in userland, with similar syntax weight.
(Edited to add: bad wording here; by "userland" I really just mean "as an ordinary function". Even if it could be done by authors, there's still some optimization-related benefits to be had if it was provided as a built-in, not to mention the ecosystem benefits of having it in a standardized, always-supported form.)
That is, to unfold:
The functional style would be written as:
But this could be done in userland already as:
This is nearly identical in syntax weight, and carries the same readability benefits afaict.
The only additional functionality unlocked by the functional-style operator seem to be:
Piping to an object's method, a la
"foo" |> bar.baz
. Note that the userland impl can handle this just fine when the object is just a namespace, likepipe("foo", console.log)
orpipe(5, CSS.px)
, or a module record; it's only actual object methods that userland needs to wrap in a function (pipe("foo", x=>bar.baz(x))
).Awaiting a value in the pipeline. The operator has a special form that lets it do this; userland is more complicated:
(Note that the user has to await the result. No need to worry about asyncness within the pipe, tho; they can freely mix sync and async there.)
Is there anything else that the functional-style brings to favor it over a userland impl?
The text was updated successfully, but these errors were encountered: