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

README: Method Chaining probably shouldn't be mentioned? #238

Open
benlesh opened this issue Sep 30, 2021 · 22 comments
Open

README: Method Chaining probably shouldn't be mentioned? #238

benlesh opened this issue Sep 30, 2021 · 22 comments
Labels
documentation Improvements or additions to documentation question Further information is requested

Comments

@benlesh
Copy link

benlesh commented Sep 30, 2021

Given that the hack pipeline doesn't really adequately meet the goals for folks trying to mimic method chains (aka "fluent" interfaces), it seems like the section on method chaining might be a little off? Or worse, might take away from the bind operator proposal? Case in point: RxJS's entire use case for functional pipelining was built around a need for tree-shakable way to chain would-be/former methods as functions, but RxJS can't/won't likely leverage the hack pipeline, as it's not really adequate for our needs.

One of the main advantages of a method over a bare function that takes the type it acts on, is in how it implicitly passes a reference to what it's acting on. If anything, perhaps the readme should state something about how mimicking method chains is not a goal of this proposal?

@js-choi js-choi added documentation Improvements or additions to documentation question Further information is requested labels Sep 30, 2021
@tabatkins
Copy link
Collaborator

but RxJS can't/won't likely leverage the hack pipeline, as it's not really adequate for our needs.

Can you elaborate on this?

I was under the impression that RxJS is currently not planning to do anything to make themselves more compatible with Hack pipelines solely because it would be ecosystem churn with two versions of your functions. In previous discussion, you've said that if pipelines had existed at the time RxJS was being written, you'd have simply written the library to use them, passing the observable as the first argument of each function, and that this would be a useful pattern anyway as it would allow using the Observable-modifying functions directly, without a pipeline when they're not needed.

Have you come to a new realization that something would be problematic for libraries like yours, beyond that of historical inertia?

@benlesh
Copy link
Author

benlesh commented Oct 2, 2021

Well, whenever we made the decision to go with .pipe() over prototype patching and method chaining, the tradeoff that needed to be considered was one of terseness versus utility. .pipe() was decidedly less terse than method chaining, but we gained a lot of utility (tree-shaking, composition, etc). Readability wise, it added 7 characters to any given chain, an required an annoying — but understandable — , between each step.

Hack pipelines are interesting because they do mean that our "operators" (formerly methods) can be used in standalone ways, but in practice, more options for how to use any given thing would likely just make RxJS more confusing, not less. (And RxJS is confusing enough!!).

On top of which, now each step in the chain has the added mental overhead of an additonal ^ character that needs to be properly placed, plus an additional 3 to 7 characters per step in the chain (depending on the function's impl). It seems like an additional step away in readability from observable.map(fn).filter(fn), observable::map(fn)::filter(fn), observable |> map(fn) |> filter(fn), or observable.pipe(map(fn), filter(fn)) to observable |> map(^, fn) |> filter(^, fn)

IMO, it's just not as good of a fit to replace method chaining. But I'm sure other people will have other opinions. All of our hopes were originally on the :: bind operator, but that seemed to fall through, and I'm not sure it has a good chance now (I don't have much luck with features I desire passing the committee, hahaha)

@tabatkins
Copy link
Collaborator

plus an additional 3 to 7 characters per step in the chain (depending on the function's impl)

The 3, yeah (^, ), but what's the 7?

@ghost
Copy link

ghost commented Dec 7, 2021

In my view, the main advantage of method chaining over bare function calls is the ability to unnest expressions, which any variant of the pipe operator achieves, with the added benefit of composability and extensibility.

@jasonatfocus
Copy link

If you have your methods all as arrows on a property getter--the conversation changes from replacing chaining... to replacing chaining/currying/dynamically defining as new atomic methods classes, efficiently sharing safer, more predefined code between classes, gaining the ability to use context specific computed properties as your rest parameters... and there is more that I know the arrow getter methods can do that I I just am not so sure a pipe can do. The sharable get arrow methods also can shrink your class code base by at 50%. I am not at all against the pipe proposal but I would HATE to see the enormous advantages of chaining/composing/currying/deleting and extending these types lost just for a pipe... It's not a good trade at all. It's Babe Ruth from the Red Sox to the Yankees. I have examples of this approach if you need them. Some are for math, some are for canvas graphics, others are for custom elements. I love the approach. I am only saying anything at all in an effort to preserve the approach... Pipes cannot replace it.

@lozandier
Copy link

lozandier commented Jun 14, 2022

@tabatkins from what I understand @benlesh is referring to .pipe() which is 7 characters. Function.pipe proposed by @js-choi makes the matter worse by adding 8 additional characters needed for a total of 15 characters to alternately communicate method chaining (first-class functional composition) in a tree-shakable-friendly way without objects. This is before the additional 3 characters per function at minimum hack-style requires!

This puts a lot of tax attempting to alternately communicate chaining in a readable matter by arguably fundamentally being directly opposed to that with the additional non-white characters required that adds up in a large code base.

With this in mind, Function.pipe isn't really ideal towards that resolving such concerns (still useful to exist supporting function composition ffrom a long overdue static method perhaps IMO) vs a pipeline operator that treats chaining as a first-class utility of its usage towards using the same amount of characters as tree-shakable function chaining (functional composition) that can be useful alternatively represented in a pipeline matter others strongly think shouldn't require additional characters that you seem directly opposed to.

The following code uses 7 non-white-space characters

f(g(x)) 

The following pipeline behavior would require the same amount of non-white-space charactes

x |> g |> f

Hack-style currently requires 14 non-white-space chracters (double the amount of characters!)

x |> g(^) |> f(^)

This is a serious concern for those who use assistive technologies to input JS that primarily solves problems using JS that involves a lot of data pipelining like myself and others that solves data science problems at enterprise scale in addition to functional programmers + those that want to chain in JS easier (terser) or clearer without objects.

Accordingly it is in my strong opinion for hack-style be tweaked to enable developers to do away with additional characters such as (^) needed by default to further minimize the cognitive noise communicating chaining using hack-style's current semantics. This suggestion revision of hack-style aligns with the core purpose of most operators to more clearly communicate a computation with minimal or equal amount of characters than imperatively writing the computation. (or the alternate way the computation is written without the operator).

At minimum I believe this should be the case for variables assigned to a function being assigned to the right-hand side of hack-style's pipelining semantics involving |>.

This revision would encourage communicating chaining without an object like Function.pipe, <some Lib Token like _ Or Object>.chain vs. communicating more clearly and in a more tree-shake friendly way what is really going on to begin with that doesn't need an object to begin with: Function composition (a basic/elementary mathematical concept).

That is particularly why I think many are at odds with hack-style not supporting first-class functional composition currently with its usage semantics. I'm extremely confused what's stopping the style from supporting this. Method chaining is a universal appeal of the operator, and method chaining is composition.

A red herring to such discussions has been auto currying which isn't really the focus of such complaints whatsoever.

//cc @mAAdhaTTah @rbuckton @littledan

@ljharb
Copy link
Member

ljharb commented Jun 14, 2022

I'm confused why we're counting characters; code should be optimized for reading, not for writing.

@lozandier
Copy link

lozandier commented Jun 14, 2022

@ljharb The contentiousness is that amount of characters needed to parse computations impacts readability.

I would also not minimize the amount of characters needed to communicate a computation that definitely impacts those who require assistive technologies to write their code; an alternate being prevalent that requires more much more characters at attempts to be sold as being more readable becomes something contentious

@ljharb
Copy link
Member

ljharb commented Jun 14, 2022

I'm pretty sure human brains don't generally parse things by moving character by character; they're not machines.

@lozandier
Copy link

lozandier commented Jun 14, 2022

@ljharb I didn't suggest nor argue humans do; I think you're misconstruing what I said. It's a loaded assumption and contentious to assume what is suggested negatively impacts readability or is suggesting code be more optimized for writing than reading.

Verbose things or things deemed more verbose than alternatives of communicating the same idea ultimately creates negative cognitive noise (thus less readable) that I think an operator should keep in mind with its purpose or validity.

Of course, only so much of an operator utility of making certain things easier to do/parse is associated with the amount of characters it requires. A balance must be made if an operator is used to make easier to do or easier to read certain behavior, what behaviors it prioritizes over others towards being more readable–especially if it requires more non-space characters to do than the typical way it hopes to replace as a "simpler" and "more readable" way.

Hack-style certainly to me doesn't make chaining (composition) easier nor more readable, hence why this issue probably exists.

Note I was primarily answering an unresolved question @tabatkins had (what makes it possible for 7 characters per chaining step) rather than making a primary focus of counting characters. I do think hack-style makes method chaining unnecessary cumbersome– if not undesirable–to be done with it than code left as is. I suggested a fix that I think suffices: Variable identifiers that are associated with a function (an object with an internal [[call]] method and etc) be passed ("piped") the evaluated value on the left hand side. Without such a change, I concur with @benlesh that method chaining probably shouldn't be mentioned. I'm of the strong opinion hack-style is too unorthodox to the task with its current semantics.

This aligns with how I think a meaningful amount of people mental model pipelining–at least the data science, machine-learning, and software engineering contemporaries + up-and-coming engineers I know/mentor/each that care enough about JavaScript enough follow such a spec I consider an intermediate feature or exposed to it casually on what's to come in the language.

I think rather than we engage in further armchair linguistics, objective quantitive data is pursued if we want to engage in arguments about readability without being naive to not account for the pragmatics and bias involved with our personal viewpoints on what's more readable.

Kind of like this response to prove a point (argubably tl;dr;), I think one can't completely dismiss or deemphasize the mention of arguably unnecessary token/characters impacting an operator's overall usefulness, readability, and ability to be written. I believe hack-style could do a better balance of such three things among other things.

@tabatkins
Copy link
Collaborator

@lozandier Your argument, both in general and in most specifics, has been argued extensively in the past; the results of that can be found in the README and some of the pinned issue threads. I won't rehash this argument in general; it's not respectful of the time of the champions or the other followers of this repository. Please review the previous discussions; if you believe there is new information to be presented, or that we've missed an important detail of the existing arguments, we welcome further discussion. Outside of that, tho, general discussion of tacit vs topic-style syntax and the effects on code length and/or readability has been settled.

A specific argument you've presented that I believe is novel is:

This is a serious concern for those who use assistive technologies to input JS that primarily solves problems using JS that involves a lot of data pipelining like myself and others that solves data science problems at enterprise scale in addition to functional programmers + those that want to chain in JS easier (terser) or clearer without objects.

Here, you appear to be arguing that function-calling syntax is an accessibility concern; users of assistive typing technologies have issues with the current syntax (the parens at the end, presumably) and would be well-served by a function-calling syntax that reduces the use of non-alpha characters. Is this a correct restatement of your argument?

Assuming it is, then I don't believe it ends up strong enough to matter here, for several reasons. First and most importantly, I haven't heard of this assistive-tech problem before, or it being addressed in any mainstream language. Do you have any pointers to evidence of this being an issue, and hopefully syntax suggestions to help address it?

Secondly, if this is an important issue to address, it seems like it's something that applies across the language - such assistive-tech users would be equally hampered by function-calling syntax everywhere else in JS, which is pervasive. I don't see why solving it here in pipeline would meaningfully affect such users' experience; the majority of their JS code will still involve calling functions with the existing (problematic) syntax. Can you elaborate on why you think fixing the issue just here in pipeline will be a sufficient solution for these users? What are such users currently doing to deal with function calling?

Thirdly, if non-alpha characters are the problem, I think the pipeline operator itself would be equally problematic, if not moreso, to type (it's certainly a rarer combination of characters), and you need exactly one of those per expression as well. Do you use such tech, or are you familiar with such tech, such that you could elaborate on how the pipeline operator isn't an issue for these users, but an extra set of parens is?


Finally, I'll note that this entire discussion is off-topic for the issue at hand, which was about the suitability of mentioning "faux method chaining" as a possible use of pipelines. We've had significant issues in the past with threads being continually derailed by The Big Argument, and I'd like to keep that from happening again. Feel free to answer the questions I've posed above, since I'm contributing to the off-topicness, but beyond that and for future topics, please either raise them in an appropriate existing issue or open a new one.

@lozandier
Copy link

lozandier commented Jul 6, 2022

@tabatkins Here's my attempt to summarize the a11y-related/power user issues without tokenizing, providing a solution, and my attempt to show why it relates to this issue overall. Note I use chaining and composition interchangeably primarily because chaining is composition.

Despite the conveniences of expressing problems via composition from a mental/concept standpoint, compositions depicted as the following have diminishing returns in readability in code.

const output = function2(function1(input))

It can get harder to communicate the computational work when you begin chaining many functions (especially with long names).

Here's the following rewritten with a style of pipelining ideally expected by the standard; note it's the same amount of effort to type as before but easier to maintain and reason about with a variety of a11y and power-user tools:

const output = input |> function1 |> function2 

Here's a more real-world example among other great ones:
RxJS and Reactive Programming - Animations and visual lessons

There have been many attempts in userland to resolve this through all sorts of non-standard abstractions–for example, lodash and ramda 's pipe/chain abstractions; RxJS's .pipe() method)–to communicate pipelining (composition) in a more understandable and maintainable way. Chaining itself is a way devs attempt to compose easier in JS, but as @benlesh pointed out, terser ways to communicate composition/chaining are desired by developers nonetheless. .pipe() has a diminishing return in tacitness that something even more tacit than pipe() is highly desired (Function.pipe proposed by @js-choi doesn't quite fulfill this) in codebases of intermediate and length/complexity that usually maximizes the value of operators.

Hack-style not accommodating chaining being written more tacitly is a problem that developers were hoping a standardized pipeline operator in JS would better solve. I firmly believe a pipeline operator should accommodate chaining with the same or less effort to type/read/understand as before (I typically expect an operator to simplify expressing the code it is an alternative for).

As far as I know, functions don't use parenthesis unless invoked anywhere else in JS. Hack-style breaks this, and I don't see the benefit. The style requires (^) tokens or parentheses on the simplest composition expressions–breaks tried and true search/replace conventions and adds cognitive noise that doesn't occur with other syntax involving functions in the language.

For those who require assistive technologies, power users and so on, expressing pipelines using hack-style semantics requires a lot more extra token navigation to work with, read, and maintain them compared to the alternatives. As the style behaves today, it wastes the time of such users– especially those using braille, speech readers, and power users who use things like Vim. The (^) tokens for bare compositions is unneeded; it doesn't communicate anything that |> variableNameCorrespondingToAFunction already doesn't, being a function composition operator.

The pipeline operator spec provides an opportunity for a standardized function composition operator to exist that allows tacitness userland efforts cannot achieve parity with. The hack-style of expressing pipelines being this poorly accommodating of chaining functions tacitly is accordingly disappointing/concerning to many people.

Tacitness is an important principle of the functional paradigm regardless of how functional a language is; it is one of the fundamental ways the functional paradigm distinguishes itself from procedural programming and class-oriented programming. A functional operator should accordingly lean on being tacit with its use of functions.

I think it'd be great if the champions of the current spec understood such concerns with perspectives more similar to this than what seems to be the case today.

My Solution

If a variable name associated with a Function (Object with [[call]] is on the right side of an expression using the |> operator, the previous value should be passed in to the function associated with that variable name to then have its value returned to be invoked by the following function (or as the final result of an expression).

If the previous value is a tuple, the values are automatically passed in as individual parameters of the function associated with the variable name. For cases where you want multiple values of a tuple to be passed to a function as parameters in a particular order, tokens associated with partial application behavior should have semantics to allow the Web creative to do that (i.e., @2).

Regardless, it's ubiquitous that a pipeline is an input (which can be expressed as a function that returns an initial result) that passes the result to all remaining functions/processes to produce a particular outcome.

Pipelining is also fundamentally unary by nature; even inlining the result from earlier in a pipelining chain to a later function that accepts multiple parameters that you pre-fill is implicit currying (with or without partial-application-related tokens/abstractions). JavaScript functions make it easier than most languages to not make this confusing for novices because functions in JavaScript always return a value (and one value at that). Each function in a pipeline accordingly can be kinda thought as (x: any) -> any before being passed to the next function in the chain.

With that in mind, JavaScript makes it easier than most languages to support such composition expressions with its operators than what you're probably giving it credit for. ^_^

Conclusion

Because of all this, I firmly believe the pipeline operator should be a more tacit function composition operator to accommodate better all programmers who prefer or find themselves composing intermediate/advanced functional expressions in JS moving forward. The hack-style default handling of functions I do not think optimizes for functional composition or chaining makes it very problematic to a meaningful amount of JS developers who compose/chain things often today.

Considering the most popular FE frameworks and libraries in the world are or are increasingly functional, it'll generally be a good idea for champions not to downplay tacitness with the standardization of new functional operators moving forward.

Especially when it comes to chaining/composition, it'd be great if more work is done to enable the hack-style of expressing pipelines compose/chain functions more straightforward in the best interests of devs who often compose things.

The pipeline operator enabling unprecedented tacitness expressing chaining than what's possible in userland outside of preprocessor tools like Babel is something I hope the standard gets back to.

@lozandier

This comment was marked as off-topic.

@ljharb

This comment was marked as off-topic.

@lozandier

This comment was marked as off-topic.

@tabatkins
Copy link
Collaborator

As I've said before, we are not relitigating the core question of "what style of pipeline should this operator use?". The argument has been had and decided, and the question has been attacked from every angle I could imagine, including every one brought up here. Please stop attempting to relitigate it; as we've had to do before, I will be closing threads and/or marking comments as off-topic that are doing so.

@lozandier
Copy link

lozandier commented Jul 7, 2022

@tabatkins What about what I raised that you requested? I think that was what you specifically asked me to elaborate about.

@tabatkins
Copy link
Collaborator

As far as I can tell, you didn't answer any of my questions. I'll quote them here:

Here, you appear to be arguing that function-calling syntax is an accessibility concern; users of assistive typing technologies have issues with the current syntax (the parens at the end, presumably) and would be well-served by a function-calling syntax that reduces the use of non-alpha characters. Is this a correct restatement of your argument?

Assuming it is, then I don't believe it ends up strong enough to matter here, for several reasons. First and most importantly, I haven't heard of this assistive-tech problem before, or it being addressed in any mainstream language. Do you have any pointers to evidence of this being an issue, and hopefully syntax suggestions to help address it?

Secondly, if this is an important issue to address, it seems like it's something that applies across the language - such assistive-tech users would be equally hampered by function-calling syntax everywhere else in JS, which is pervasive. I don't see why solving it here in pipeline would meaningfully affect such users' experience; the majority of their JS code will still involve calling functions with the existing (problematic) syntax. Can you elaborate on why you think fixing the issue just here in pipeline will be a sufficient solution for these users? What are such users currently doing to deal with function calling?

Thirdly, if non-alpha characters are the problem, I think the pipeline operator itself would be equally problematic, if not moreso, to type (it's certainly a rarer combination of characters), and you need exactly one of those per expression as well. Do you use such tech, or are you familiar with such tech, such that you could elaborate on how the pipeline operator isn't an issue for these users, but an extra set of parens is?

@lozandier
Copy link

I felt I did address all those things. I can be more explicit to say I don't use screen reading and braille tech but know developers that do; in addition to them, I also account for testimonials of others (such as this) openly expressing the problem more verbose than terser alternatives have on their ability to code that I see lacking in these discussions thus far by you and other champions.

I do use ergonomic keyboards (i.e. Kinesis Advantage 2, ZSA Moonlander) and Vim out of necessity for my hands that I won't elaborate further about here.

@tabatkins
Copy link
Collaborator

Your statement did not address my questions; it stated more generic points about terseness, which is not what I was asking about.

You had specifically made the argument:

This is a serious concern for those who use assistive technologies to input JS that primarily solves problems using JS that involves a lot of data pipelining like myself and others that solves data science problems at enterprise scale in addition to functional programmers + those that want to chain in JS easier (terser) or clearer without objects.

And my questions were about why pipeline being terser in one sense was an adequate solution to this problem, which appears to otherwise apply to the entire JS language.

@lozandier
Copy link

lozandier commented Jul 7, 2022

@tabatkins I felt I did, but I can paraphrase how I did so now that we've perception-checked how helpful my previous responses are about that:

The dev audiences I previously mentioned highly anticipated the pipeline operator alleviating the verbosity and library dependence of tacitly communicating functional composition–particularly chaining. Chaining is the most common form of functional composition in JS today. Hack-style requires (^) in a manner that seems arbitrary, unprecedented for a binary operator in the language for its simplest case, and overall very concerning for such audiences.

Existing binary operators are fundamentally and deliberately tacit for their most basic use cases, but hack-style deviates from this. The most basic binary operation involving functions is a functional composition with unary functions. The pipeline operator with hack-style semantics differs from existing binary operators with the requirement of (^) tokens for even the most basic functional composition expression.

JS's existing binary operators are less verbose alternatives to the expressions they abbreviate. That convention is broken with hack-style's current semantics to express functional composition with the simplest functions you can compose with that are ubiquitous with the conceptual and mathematical concept of pipelining regardless of discipline: unary functions. As I've demonstrated many times, explicitly functionally composed expressions and class mixins expressions are substantially more verbose when you substitute them with hack-style's representation of pipelines. This is unprecedented.

Audiences most negatively impacted by this are people who require assistive technology to read/understand code + power users such as those who use Vim and/or frequently chain. Chaining is very common and hack-style is more verbose than existing alternatives in the language and what framework/lib authors like @benlesh (who created this issue) have manifested with overwhelmingly positive reception.

Hack-style with its (^) token requirements for any functional composition expression makes such expressions longer to write, longer to announce, and arguably longer to reason about once we account for closed or inlined functions.

I am aware and in full agreement semantics involving additional tokens is needed to abbreviate the use of functions with multiple parameters; I am unconvinced the pros outweigh the cons that need to be done by default for all functions passed.

It makes no sense to me that any developer–regardless of physical and cognitive ability–has to do input |> ((x) => 2)(^) that hack-style seems to require; it should be sufficient that a function to the right of the operator tokens will have x inlined (as a parameter) as you expect with other binary operators in the language.

I firmly believe additional tokens like (^) and partial application tokens should be used only when necessary. This viewpoint aligns with the behavior of existing operands and syntactical constructs in the language. Hack-style frustratingly deviates from those things.

Because of these problems with hack-stye, if the committee/champions insist on not accounting for the most elementary representation of functional composition being simplified in what's required to read and write it, I'm inclined to say hack-style is more harmful to the language than a pipeline operator not existing at all.

@tabatkins
Copy link
Collaborator

Right, so this is all still a more generic terseness argument; nothing about this explains why invoking functions in the standard way (by putting an arglist after their name) is an accessibility concern, as you asserted in the original comment, or why, if this is an accessibility concern, it's only a problem in need of solving right here, in pipeline syntax, rather than across the rest of the language as well.

You made a very specific a11y-based argument (I quoted it in #238 (comment)), and if you'd like to continue arguing that specific point, feel free to (in a new thread), but if you instead want to argue on terseness directly, that's already addressed in the proposal README (see https://github.com/tc39/proposal-pipeline-operator/#alternative-proposal-f-pipes for the argument - terseness cuts both ways). If you feel you have new information about the general terseness argument, again feel free to argue that in a new issue.

Either way, I think we can close this off-topic branch of this particular issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation question Further information is requested
Projects
None yet
Development

No branches or pull requests

6 participants