-
Notifications
You must be signed in to change notification settings - Fork 108
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
Allowing 'instance methods' to be importable #101
Comments
@jakearchibald: I’ll let @mAAdhaTTah speak for Proposal 1: F-sharp Style Only. From the perspective of Proposal 4: Smart Pipelines, the code above would look like: import Whatever from "./Whatever.js";
import { rareAndComplexThing } from './whatever-extras.js';
new Whatever() |> do {
#.commonThing(123);
::rareAndComplexThing(321);
} The smart-pipelines explainer has a section addressing its relationship with the existing function-binding proposal
TL;DR: The smart-pipelines Core Proposal + Additional Feature PF subsumes the ECMAScript function binding proposal in the first use case (prefix new Whatever() |> do {
#.commonThing(123);
::rareAndComplexThing(321);
} Edit: There is a special type of secure/robust method pre-extraction that @ljharb desires to still be addressed: see #110 (comment) and #110 (comment). This would be orthogonal to the smart pipeline operator + feature PF; neither precludes the other. |
I can't really speak for the vital signs of the bind operator, but it has significant issues:
We still need a solution for method extraction, and it would be nice to have a better solution for extension methods, but I'm not sure that |
#101 (comment) looks like what |
With had some key differences... |
@rudiedirkx: The semantics of #101 (comment) and smart pipelines are very different than the semantics of
In contrast, only declarative lexical environments may establish a topic binding. This topic binding is immutable, and its origin and its type are always statically analyzable. It is, basically, a tacit |
TC39 really just needs to start owning the behavior of @jakearchibald to make the pipeline operator less of a badly fitting lego brick you'd probably need https://github.com/tc39/proposal-partial-application |
@spion That's true of the F# proposal; Smart pipelines does it's own type of partial application with its lexical topic. |
@mAAdhaTTah the smart pipeline proposal has terrible syntax costs. It introduces a whole new contextual lambda syntax just to remove rbuckton's partial application syntax is at least non-contextual, and while arbitrary expressions are great, I'd rather stick to arrow functions than pay the above costs. |
I think pipeline operator is very good, but it's not a good answer for extension methods or method extraction because the paradigms is very different (FP vs OO). OO guys expect the calling of extension methods should look very close to normal methods. Pipeline fails in four small but important aspects:
So the conclusion is very clear to me that we need both pipeline op and extension methods, and no one could replace the other. About method extraction, the main motivation and criterion should be ergonomics, or we should just stick on arrow function
I totally agree @spion and I want to point out the difficulty of |
In terms of explaining this stuff to developers: foo::bar(3); Call // F-sharp style without partial application
foo |> bar(3); Call // Hack style (similar to F-sharp with partial application)
foo |> bar(■, 3); Call I don't think However, I think it's more expected that an instance method would mutate an instance, whereas it's less expected that a non-instance method would. I acknowledge that we already have methods on |
@spion No argument from me (I'm backing F#), but you do end up tying the F# proposal to the partial application proposal in order for it to approach the level of power offered by the Smart proposal. @jakearchibald Explaining exactly what F#-style is doing like that sounds worse than explaining "you can import these extension methods and use them with the pipeline." Even better, using them with the pipeline operator looks like fluent APIs OO developers are already familiar with: [1, 2, 3]
|> filter(x => x > 1)
|> map(x => x * 2)
|> reduce((a, b) => a + b) You don't necessarily need to explain all the details under the hood off the bat, and basically any FP-style library (Ramda, lodash/fp) works this way already, so you can treat them like array extension methods already. Once this rolls out to the language proper, libraries with an OO approach can design their API around this feature, which gives OO developers an opportunity to tree-shake methods they don't use while still working with an API that should be fairly familiar to them. The F#-style proposal allows the details to be hidden from OO developers like this, allowing them to think of these imported methods as just methods. The Smart pipeline puts their implementation details front-and-center, cuz they have to know that the method they just imported isn't a method, its a function that takes two arguments, one of which is the instance itself. None of this is an argument against |
@spion @hax Also, IRT I don't think that resistance is a killer for the bind operator, nor do I think it means there will be no new |
Sure, but you need to know that stuff if you ever need to write your own, or debug an existing one.
Yeah, it's ok for methods that don't mutate the original, but take: arrayLike |> reverse(); It isn't clear to me that arrayLike::reverse(); If my understand is that |
Of course; I'm mostly focusing on the experience of developer consuming these APIs, not the ones writing them. That said, I don't think writing the arrow function as I mentioned above is complex, and debugging them is still straightforward, and in general, I think writing a library requires you to know more about the details than consuming said library. The F# proposal pushes the complexity to the library author rather than consumer, which I think is generally appropriate. It isn't clear to me that |
To clarify, I mean that it's more expected that Like I said earlier, there's |
I understood what you meant; I don't have that expectation. I think both of those cases are reliant on the APIs you're looking at. jQuery as the prime fluent API example has both;
|
How about introducing new usages that make it less confusing? The infix bind operator would make all
Also prefix bind fits like a glove there: I want to let I realise JS wants to hang around with the cool FP languages, but please, lets think a little bit more about what makes the most sense. |
Part of the problem currently is That said, pipeline is not intended solely for FP usage; my whole argument above is that it's great for OOP developers because you can design and implement fluent APIs without needing to ship everything on the prototype, allowing everyone to build smaller bundles, while also enabling FP-like usages as well (something the bind operator cannot do). TC39 is introducing new usages for |
Note that it's only a problem for event handlers if you use it inline; for robustness, you'd need to always use it at module level, and thus, store it in a variable. |
My comment may be off topic.
I don't think so.
I don't think Even that, I don't see much problems of confusion caused by
It's weird to me that why we never worry about the fp style features like pipelines would be too strange to many js programmers, but consider a feature related to Note, I love pipeline operators. (though some extension of smart pipelines proposal seems problematic, for example it's too easy to lose the context of what current |
I know you’re already aware of this @mAAdhaTTah, but I do want to note, for the benefit of other readers, that smart pipelines with Additional Feature PF also already addresses this use case. Using the example from tc39/proposal-partial-application#23: const arrayToStringArray = ?this.map(x => "" + x); …would instead be:
const arrayToStringArray = +> #.map(x => "" + x); Smart pipelines with Additional Feature PF would completely subsume @rbuckton’s partial-application proposal, as far as I can tell. But even with Additional Feature PF, subsume are orthogonal to both method binding and @ljharb’s use case for secure method pre-extraction (#110 (comment)). Both smart pipelines and method binding/pre-extraction can coexist, such that: const arrayToStringArray = ?this.map(x => "" + x);
inputs |> ?this.map(x => "" + x) |> console.log; …would instead be: const arrayToStringArray = +> #.map(x => "" + x);
inputs |> #.map(x => "" + x) |> console.log;
I’m interested in your feedback. I’ve done my best to make the lexical scoping of That’s not on topic for this issue, though. Please feel free to open another issue either here (specify Proposal 4) or in the smart-pipelines proposal. I’m busy still working on the spec right now, but I would definitely be listening. |
Closing this issue, as the proposal has advanced to stage 2 with Hack-style syntax. This solves @jakearchibald's original use-case; you just have to write ordinary functions, and then you get the readability benefits of method-chaining. |
I don't feel it solves my use-case, but I don't think this proposal is the one I'm after. |
I think the bind operator, or something like it, is more the solution you’re looking for. Watch the agenda for the next meeting. |
Well, it lets you import "methods" and call them on objects without resorting to function-call nesting, given you similar "linear code" benefits to method calls themselves while allowing for tree-shaking. In this respect, it solves the use-case as well as any pipe operator would. If the use of the |
Apologies if this has already been discussed to death.
I'm interested in solving this case:
…where
rareAndComplexThing
adds a lot of weight to theWhatever
class, and should ideally be an opt-in:I don't really like the above. Maybe it's because it isn't clear that
rareAndComplexThing
may change the state ofwhatever
.An alternative is what RxJS did:
Where
rareAndComplexThing.js
importsWhatever.js
and adds to its prototype. But this doesn't work well with tree-shaking. RxJS's pipable operators fix the issue for them, but it doesn't really work for the general case.I got excited about the pipeline operator here, but now I've written it down, I'm not so sure:
With the F-sharp style,
rareAndComplexThing
would have to be implemented like:That overhead isn't needed with the Hack style, but it feels really clunky. Again, it isn't clear that the state of
whatever
may be altered. I think what I really want is just:…where
rareAndComplexThing
is called likerareAndComplexThing.call(whatever, 321)
. Even if pipeline ships, I think I'd rather go with:Is the bind operator dead in favour of the pipeline operator? I like pipeline, but it doesn't feel like it solves this particular use-case. Is it intended to?
The text was updated successfully, but these errors were encountered: