-
Notifications
You must be signed in to change notification settings - Fork 5
Other useful common functions #17
Comments
+1 for |
uncurryThis is https://npmjs.com/call-bind, and it’s solved by https://github.com/tc39/proposal-bind-operator and https://github.com/js-choi/proposal-bind-this |
There's a lot of prior art for |
@ljharb do you have a problem with this entire proposal? |
There are like a half dozen bind proposals, they keep getting binned. |
Of course i don’t. I think this is too large a list to be reasonable to add, and i think each addition will need its own motivation, and i don’t think there’s much value in an explicit noop when Function.prototype and ()=>{} exist. |
Like I said in the OP, most of those are niche and I wouldn't expect them to be accepted (at least, not on I mentioned function * tap(iterable, cb) {
if (typeof cb !== "function") throw new TypeError("Function expected: cb");
if (cb === Function.noop) {
yield* iterable;
return;
}
for (const x of iterable) {
cb(x);
yield x;
}
}
function * map(iterable, cb) {
if (typeof cb !== "function") throw new TypeError("Function expected: cb");
if (cb === Function.identity) {
yield* iterable;
return;
}
let i = 0;
for (const x of iterable) {
yield cb(x, i++);
}
}
iterable
|> tap(^, debugMode ? value => console.log(value) : Function.noop)
|> map(^, addIndex ? (value, index) => ({ ...value, index }) : Function.identity) The one I have used most frequently, though, is const getResult = Function.lazy(complexComputation);
getResult(); // performs computation, returns result
getResult(); // returns result |
Also, |
fwiw i do like the idea of an API solution for it, even if PFA or the bind operator advances, but i wouldn't call it "uncurry" since "currying" is a confusing concept. |
I'd argue currying is not confusing, merely unfamiliar. I've not worked with anyone who was unable to adapt to it even if, owing to current mainstream idioms, it's not what they were used to. |
I'm quite familiar and I still find the concept confusing. Either way, "unfamiliar" is pretty much the same as "confusing" when it comes to language design. |
"Confusing" implies it's inherently hard to reason about whereas something merely unfamiliar may become comfortable and easy given a little time. It's an important distinction given users will be to varying extents unfamiliar with any new language construct. |
I’ve used (To be honest, I’ve never used a specific noop function before in all my FP life. At most I might have done So I can see the appeal of many of these (particularly the complement and memoization functions). But @ljharb is right: the Committee will demand specific use cases and evidence of high frequency for each of these functions. So I think a prerequisite for each of these functions (at least if we want it to have any chance in the Committee) would be to show that it’s been implemented in userland as one or more very popular libraries, along with specific use cases. I also want to avoid this initial proposal becoming overly big at once. If the Committee shows that one of the proposal’s functions risks killing the entire proposal, then I will drop it. That goes for
A memoization function might be complicated enough to warrant its own proposal. I am uncertain whether including a call- |
@js-choi Regarding your footnote, I think it would be on the contrary regarding affecting the chances. From my observation, for a syntax level change (+ competing proposals), you're looking at a good couple years to Stage 3. If 1: Agree about "curry" being confusing, so +1 for a better name. It's an annoying trend right now, functional programming terms are getting force-fed to the JS community. |
|
@ljharb: I’m hemming and hawing about // From three@0.133.1/test/benchmark/benchmark.js
SuiteUI.prototype.run = function() {
this.runButton.click = _.noop;
this.runButton.innerText = "Running..."
this.suite.run({
async: true
});
}
// From corejs-typeahead@1.3.1/test/bloodhound/transport_spec.js
for (var i = 0; i < 5; i++) {
this.transport.get('/test' + i, $.noop);
}
expect(ajaxRequests.length).toBe(2);
// From corejs-typeahead@1.3.1/src/bloodhound/lru_cache.js
// “if max size is less than 0, provide a noop cache”
if (this.maxSize <= 0) {
this.set = this.get = $.noop;
}
// From corejs-typeahead@1.3.1/src/bloodhound/bloodhound.js
sync = sync || _.noop;
async = async || _.noop;
sync(this.remote ? local.slice() : local);
// From verdaccio@5.1.6/packages/middleware/src/middleware.ts
errorReportingMiddleware(req, res, _.noop);
// From <https://github.com/odoo/odoo/tree/15.0/addons/bus/static/src/js/services/bus_service.js>
Promise.resolve(this._audio.play()).catch(_.noop);
// From <https://github.com/ClickHouse/ClickHouse/blob/v21.10.2.15-stable/website/js/docsearch.js>
if (this.$hint.length === 0) {
this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop;
} It goes on and on. Many thousands of developers seem to download and use
It is true that the bind-this operator proposal is looking at several years before it might get standardized and implemented. However, I am going to be presenting both at the plenary, and I really do not want the representatives there to get confused between them. The operator is not really syntactic sugar for the call-bind function, either: it’s sugar for Having said that, the fact that @ljharb (one such Committee representative) is also supportive of a call-bind function, in addition to the bind-this operator, does make me hopeful that the Committee might be amenable to both. I’m just not sure about how it will affect other Committee representatives. If I do add a call-bind–like function to the proposal, I probably wouldn’t do it until this proposal successfully reaches Stage 1. |
I am also in favor of
I don't think this is very compelling on its own. "People do thing a lot" does not mean thing needs to be blessed by the language - people do a lot of things. Also, often people (quite reasonably) feel that if a language or library provides a function it's better to use that rather than rolling their own version, even if it would be trivial to do so, so the fact that people are using a |
@bakkot: It is true that frequent usage of Thing is not a reason per se to include it in the language proper. Having said that, it is evidence that it may be useful. The basis of this proposal is that:
Yeah, it’s true that there’s an effect of convenience—whether they would have bothered defining their own function if there wasn’t a library that provided it. But at the same time, plenty of people would also argue that it is better to avoid dependencies on external libraries. It goes both ways. (And plenty of people are importing lodash.noop by itself, rather than the entirety of lodash.) Anyways, people indeed do a lot of things, but if a lot of people do a thing, then that’s evidence that there’s a cowpath that might deserve to be paved. We should take that as what it is—moderate but not super-strong evidence to standardize—and consider it case by case. As for a |
My point is that I think what's happening here is that someone says "I need a noop function", and then because they are already using lodash and it provides one they use that, whereas if they were not already using lodash they'd've cheerfully written their own. I agree that many people like to avoid dependencies, but I don't see what bearing that has on this point.
... Are they? I see 125 dependents of |
IMO a better metric than the number of dependents is weekly downloads. The vast majority of js code isn't open source. 400k/week is a lot.
Most developers don't have any tools that automagically install lodash modular packages. Which means enough human beings painstakingly typed
I have many times uncheerfully written my own as |
@mmkal 400k/week is quite a small number considering that lodash itself is at ~40M, and other lodash utilities like lodash.throttle have ~3.7 million downloads a week. Additionally, note that download counts include when something is a transitive dependency - so all it would take is one package author (whose package has 400k downloads a week) to type that and publish, for 400k downloads to appear. |
@bakkot: Yeah, I think this is totally plausible. “If it’s not available, I’ll define my own or embed
@bakkot, @ljharb: I think these are good points, although my own point with giving download statistics is a general “this is still used a lot, even if it’s not the highest compared to something like For what it’s worth, I consider the inclusion of |
I'm against adding |
I think it's even weaker than that. Like I said, many people will reach for the version of a thing provided in the language or in a library they're already using for that reason alone, rather than because it makes the code clearer in their particular case. (Witness people reaching for
Yeah, agreed. To be clear, I'm not strongly opposed to |
@bakkot: Yes, it indeed is true that the inclusion of a helper function in a general library like Lodash (or jQuery for that matter, since jQuery also has I think that there’s a reasonable argument to be made that But the fact that people who use Its popularity is only a reason to give it some consideration; further bikeshedding in Stage 2 will have to be on its own merits. We probably all agree about that. 🙂 |
Me personally, have never needed a Then you might have some memo function using |
It's like having an |
@Salmatron This is going a little off course but being able to provide an identity element, such as Back to |
@samhh |
Promise.reject().catch() // => Promise<rejected>, Uncaught (in promise) undefined
Promise.reject().catch(() => {}) // => Promise<fulfilled> |
Good catch, but that seems like a bug in the HTML spec (and node) to me (the language spec doesn't dictate anything about what logging those rejection notifications produces). |
It's not a bug in the HTML spec, it's how ES promises works. The first rejected, the second fulfilled. |
Fair enough. |
I too have never used noop in any of my code. But, regardless of that,
I understand this slippery-slope argument, but I don’t think it’s very compelling in this case. No library exports Anyways, I personally don’t feel too strongly about |
@js-choi Nicely done! One thing: Is the move to I rather liked keeping all these in
|
@Salmatron: Well, only one of the old helper functions (aside) has been moved to Function.prototype. Most of the old functions (flow, pipe, constant, identity) remain static methods on Function. For the prototype-based methods, I was following the precedent set by But it’s currently uncertain whether we would even include many of them in this proposal—or, indeed, whether this proposal will reach Stage 1 when I present it to the Committee plenary for Stage 1 tomorrow or on Thursday. If the proposal successfully reaches Stage 1, we can bikeshed the home of some of these functions more. |
I like the new suggestions. 👍 |
I presented this proposal to the Committee at the plenary today. The Committee rejected Stage 1 primarily based on the fact that it’s too broad and should be split up into multiple proposals. I think this is reasonable, and I will do so before presenting again.
I will comment here again when I am able to create new proposal repositories. I plan to create repositories for |
I know it wasn't mentioned in the spec, but was there any side-talk on |
@js-cho Good luck and thank you for all the great work you're doing to improve the DX for us devs! 💝 |
There was some interest in a LTR |
I’ve split out separate proposals for Function.pipe and flow and unThis. I will archive this proposal sometime after the October plenary notes are published. |
In https://github.com/esfx/esfx/blob/master/packages/fn/src/common.ts I've gathered a bunch of useful one-off functions for FP-style development. If we are considering adding
.constant
and.identity
, perhaps some of those make sense as well. There are at least two that might be worth adding to the standard library:noop()
— Always returnsundefined
.Function.prototype
, though it might be useful to have a separate copy.lazy(factory, ...args)
— Creates a function with the following properties:factory
with the supplied arguments and returns the resultThere's a bunch of other ones in there, but most are very niche:
incrementer
—(start = 0, step = 1) => () => start += step
decrementer
—(start = 0, step = 1) => () => start -= step
property
—key => object => object[key]
propertyWriter
—key => (object, value) => object[key] = value
invoker
—(key, ...args) => (object, ...rest) => object[key](...args, ...rest)
caller
—(...args) => (func, ...rest) => func(...args, ...rest)
allocater
—(...args) => (ctor, ...rest) => new ctor(...args, ...rest)
factory
—(ctor, ...args) => (...rest) => new ctor(...args, ...rest)
uncurryThis
—Function.prototype.bind.bind(Function.prototype.call)
complement
—f => (...args) => !f(...args)
both
—(a, b) => (...args) => a(...args) && b(...args)
either
—(a, b) => (...args) => a(...args) || b(...args)
fallback
—(a, b) => (...args) => a(...args) ?? b(...args)
The text was updated successfully, but these errors were encountered: