Reputation: 16066
Here's an interesting challenge. I was reading through this old issue in TypeScript github to support Extension Methods with a usage similar to C#. There are two main approaches proposed, one is to add syntactic sugar to extend object prototype that is often frowned upon, and the other one is to rewrite calls sites (see cons and pros here).
I was thinking maybe we can do it another way, to mimic extension methods that also works in JavaScript.
Let's say we have the following function:
function includesAnyOf<T>(array: T[], ...searchElements: T[]) {
return searchElements.some(_ => array.includes(_));
}
// example:
const a = [1, 2, 3];
const b = 3;
includesAnyOf(a, b); // true
// as extension method it would look like:
// a.includesAnyOf(b);
Now I want to implement function ext
so that I can do:
ext(a).includesAnyOf(b)
ext(a)(_ => _.includesAnyOf(b));
preserving all the typings of the parameters. I wonder if that is possible at all, but I started with this and don't know how to finish it!
function ext(obj: any) {
return {
includesAnyOf: (...args: any[]) => includesAnyOf(...???)
};
}
I think it's an interesting challenge, how do you think this can be implemented, and how to generalize it, or can you think of a better way?
Upvotes: 0
Views: 611
Reputation: 16066
The easiest way I found to do this is to use lodash.partial to do the currying.
function includesAnyOf<T>(array: T[], ...searchElements: T[]) {
return searchElements.some(_ => array.includes(_));
}
import { partial } from 'lodash';
const a = [1, 2, 3];
const b = 3;
const ext = (o) => {
return {
includesAnyOf: partial(includesAnyOf, o)
};
};
console.log(
ext(a).includesAnyOf(b) // true
);
Upvotes: 0
Reputation: 138267
function ext<T, E>(obj: T, extension: E) {
return (receiver: (extended: T & E) => any) => {
receiver(new Proxy(obj, {
get(target, prop, receiver) {
if(prop in extension)
return extension[prop];
return Reflect.get(...arguments);
},
// TODO has, set, etc. according to needs
} as T & E));
};
}
ext(a, { includesAnyOf })(a => {
a.includesAnyOf("stuff");
});
Mission accomplished.
function ext(obj, extension) {
return (receiver) => {
receiver(new Proxy(obj, {
get(target, prop, receiver) {
if(prop in extension)
return extension[prop];
return Reflect.get(...arguments);
},
// TODO has, set, etc. according to needs
}));
};
}
function includesAnyOf(...searchElements) {
return searchElements.some(_ => this.includes(_));
}
const a = [1, 2, 3];
ext(a, { includesAnyOf })(a => {
console.log(a.includesAnyOf("stuff"));
});
can you think of a better way?
I honestly don't see any benefit over a regular function. In C# & others it is useful as you can easily find these extensions using autocomplete. No matter how you do that in TS, it won't serve that purpose.
Upvotes: 2