Open peey opened 3 years ago
Lodash's _.partial works pretty much like that (without some of the more advanced features you envision in the last example).
const f = (a, b, c) => { console.log(a, b, c) }
const incomplete = _.partial(f, 1, _, 3)
incomplete(2)
// logs 1, 2, 3
there are also similar functions in other popular utility libraries.
Indeed some of the syntax clunkiness in a userland library would go away
IMO this is a big benefit of the operator.
Ease of use is a "break it or make it" quality of a utility feature like this.
I practically never use the partial
helper functions because at the end of the day, writing
import {_, partial} from 'a-partial-application-library'
const plus2 = partial(plus, _, 2)
is more cumbersome and arguably even less readable than your everyday
const plus2 = _ => plus(_, 2)
Also their static typing with TS/Flow often leaves much to be desired.
Here is my proposal polyfill. It only supports partial application from left to right, just like in a functional programming language like Haskell or Elm. One could also write a flip
function if there is a need to do partial application backward.
/**
* @description Partial polyfill
* @author Amin NAIRI <https://github.com/aminnairi/>
* @version 1.0.0
* @example https://replit.com/@amin_nairi/LividEffectiveConversions#index.js
*/
const partial = (callback, ...initialArguments) => {
return (...additionalArguments) => {
const allArguments = [...initialArguments, ...additionalArguments];
if (allArguments.length >= callback.length) {
return callback(...allArguments);
}
return partial(callback, ...allArguments);
};
};
const add = partial((first, second) => {
return first + second;
});
const range = partial((from, to) => {
return [...Array(to - from + 1)].map((_, index) => {
return index + from;
});
});
const fromFiveTo = range(5);
const increment = add(1);
const decrement = add(-1);
console.log(fromFiveTo(10)); // [5, 6, 7, 8, 9, 10]
console.log(range(1, 5)); // [1, 2, 3, 4, 5]
console.log(add(2, 3)); // 5
console.log(increment(4)); // 5
console.log(decrement(0)); // -1
Not sure if this is really necessary to do partial application backward for an argument a
in a function f(a, b)
like f(?, b)
. I find it really odd and never had any problem doing so. Haskell does a great job defining function argument order so that it is rarely needed to use flip
on a function. The data comes last and the settings come first.
import {storageGet} from "./library/api/storage-get.mjs";
import {storageSet} from "./library/api/storage-set.mjs";
storageSet(localStorage, "search", "");
storageGet(localStorage, "search").whenOk(searchValue => console.log(searchValue));
storageGet(localStorage, "items").whenOk(storageSet(localStorage, "savedItems"));
I'm by no means an expert on this, but I assume another huge benefit of this being part of the language is performance. Engines would be able to optimize partial application and/or pipes as if they were regular function calls, and not create the mess of intermediate functions.
True, Rambda has a placeholder for doing that with R.__
but of course, this adds some runtime overhead (just like the solution I proposed above) and thus slows down the interpretation of the script.
Though this won't add too much overhead for most of the Web applications (to not say practically invisible for a non-power user) out there that do not have performance in mind, this can be critical for other range of domains like Gaming or Geolocation and such.
In the end, achieving this in a userland library should be viable if there is no need for performance and we can achieve a pretty slick API with partial application and function composition.
import {compose} from "partials/compose.mjs";
import {map, mapWithIndex} from "partials/map.mjs";
import {reduce} from "partials/map.mjs";
import {isModulo} from "partials/utils.mjs";
import {or} from "partials/logical.mjs";
const isValidFrenchCompanyIdentifier = compose([
map(compose([Number, or(0)])),
mapWithIndex(doubleIfOddIndex),
map(sumDigits),
reduce(sum),
isModulo(10)
]);
isValidFrenchCompanyIdentifier("732829320"); // true
isValidFrenchCompanyIdentifier("732829321"); // false
@lazarljubenovic yes indeed!
Terser could also know how to inline bound functions, since it can't know for sure that .bind() wasn't modified in the function prototype.
When combining with pipelining, I think the performance cost of ? would disappear for most pipelining cases. As well as end the debate over on that proposal regarding first-argument vs curried.
Terser could also know how to inline bound functions, since it can't know for sure that .bind() wasn't modified in the function prototype.
I don't know of how much of a niche this is, but the current proposal indeed would allow some light version of static metaprogramming without the need of a tool such as babel. Although one could argue that this is already achieved by using arrow functions to reorder/fix parameters (minus this
).
If we treat the pipelines proposal as a separate proposal and disregard the pipelines-related examples, I'm wondering if this proposal can entirely be achieved through libraries (and if not, then what what are the key issues that prevent it)
e.g. it should be possible to write a library that allows you to write
it may even be possible to support https://github.com/tc39/proposal-partial-application/issues/5
and to support https://github.com/tc39/proposal-partial-application/issues/7
Through these examples, I'm not saying that this proposal is not needed. Indeed some of the syntax clunkiness in a userland library would go away if this were a language feature. I'm just hoping that this leads to a discussion of he core language limitations that prevent this proposal from happening as a userland library, and to discuss if this proposal addresses those limitations well.
A discussion of how this can (even partially) be achieved in a userland library may also support development of this feature in transpilers (babel, typescript) and allow it to reach users faster.