Use JS Proxy to get nested form element - javascript

I want to make accessing my form elements easier in JS. Forms have the property 'elements' that gives you access to all the DOM inputs. This allows to get elements either by their position or their DOM name.
This becomes cumbersome however when using arrays in the input name:
<input name='shipping[address][city]' />
const shippingCity = form.elements['shipping[address][city]'];
It would be much nicer to access it with dot notation:
const inputs = new Proxy(form.elements, {
get(target, handler) {
...
}
}
const shippingCity = inputs.shipping.address.city;
I'm guessing theres a way to do this with JS proxies. I've found examples on how to access nested data in an object but this needs to build the string from the properties instead, then return the DOM at the top level proxy. I'm guessing this would take 2 seperate proxy handlers.
TLDR: Turn X -> Y
inputs.shipping.address.city -> form.elements['shipping[address][city]']

I can offer you a solution which works like this, but I warn you it's overwhelmingly hacky. It has to wrap every object property with a proxy, and I wouldn't trust it not to create property name collisions which would be horrible to debug.
const subAccessor = (obj, base, path) => new Proxy(obj, {
get: (target, prop) => {
const newPath = `${path}[${prop}]`;
const obj = base[newPath];
if (target[prop]) return target[prop];
return subAccessor(obj || {}, base, newPath);
}
});
const inputs = new Proxy(form.elements, {
get: (target, prop) => {
return subAccessor(target[prop] || {}, target, prop);
}
});
That's code is a truly awful solution, though, so I strongly recommend that you instead use a simple helper function such as
// usage: `getInput('shipping.address.city')`
const getInput = path => form.elements[
path.split('.')
.map((part, i) => i > 0 ? `[${part}]` : part)
.join('')
];

Related

How to create shorthand for anyElement.querySelector method

One can create shorthand for document.querySelector with
const $ = document.querySelector.bind(document);
const $$ = document.querySelectorAll.bind(document);
so now let a = $('a') and let a = document.querySelector('a') are equivalent.
Is there a way to create shorthand for the querySelector method itself?
I.e. to make let a = element.shortHand(args) and let a = element.querySelector(args) to be equivalent for any (unknown in advance) element.
Edit: Since people are telling that doing the above is a bad idea, there is another question: How to make$ $$ selectors like the one in the Chrome DevTools, which accept the root element as second parameter?
I.e. to make let a = $('a',element) and let a = element.querySelector('a') to be equivalent.
Here are some options:
Add Method to Element.prototype
Element.prototype.shortHand = Element.prototype.querySelector
This "monkey-patches" the Element class in the DOM itself and adds this function on all elements in DOM, which is just a copy of the querySelector function.
This is very discouraged. It's bad for performance and it is bad in case browsers decide to add more functions in the future that conflicts with your function. But if you're just playing around and not shipping this code it should be fine.
Mini jQuery
If you're looking to create your own mini jQuery, you can also do something like this:
class MiniJQuery {
constructor(el) {
this.el = el;
}
shortHand(...query) {
return this.el.querySelector(...query);
}
// ... put any other functions you want to use
}
const $ = (queryOrElement) => {
if (typeof queryOrElement === 'string') {
return document.querySelector(queryOrElement);
}
return new MiniJQuery(queryOrElement);
}
// Now you can:
const a = $(element).shortHand(args);
// which is equivalent to
const a = element.querySelector(args);
This is a much safer approach and not problematic. I don't think this adds much value as you can just type the slightly longer method name, but you could add more interesting methods on your class to make it worthwhile.
Proxy
Very similar to the approach above, but you can use a Proxy instead of the MinijQuery class to "forward" unknown methods to the element itself. This means that $(element) will have all the methods that element itself has.
Example:
const handler = {
get: function (target, prop, receiver) {
if (prop === "shortHand") {
return target.querySelector.bind(target);
}
const retVal = Reflect.get(...arguments);
// Bind methods to the element.
return typeof retVal === 'function'
? retVal.bind(target)
: retVal;
},
};
const $ = (queryOrElement) => {
if (typeof queryOrElement === 'string') {
return document.querySelector(queryOrElement);
}
// You can add all sorts of custom function handlers here.
return new Proxy(queryOrElement, handler);
}
$('div') // gets divs
$(element).shortHand(...)
// works the same as element.querySelector
// But the HTMLElement methods still work too:
$(element).querySelector
$(element).querySelectorAll
$(element).className
// ...
Read More Here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy

How do I run a function with arguments from an object?

I have a lot of functions like the following:
var runme = function(liked, disliked){
console.log(`i like ${liked} but I dislike ${disliked}`)
}
I have an object with I would like to use to fill in the arguments of the function
var obj = {liked: 'apple', disliked: 'pear'}
How can I run the function using the object to specify the arguments?
I tried using spread syntax:
runme(...obj)
But this produces:
TypeError: Found non-callable ##iterator
How can I run a function with parameters from an object?
I can't change the functions, as I am creating a wrapper that needs to be able to handle arbitrary functions.
Edit: I've edited the post to use 'liked' and 'disliked' instead of 'one' and 'two' as this better shows that ordering matters.
I can use any version of JavaScript, up to and including ES9.
There is no general-purpose way to accomplish this.
If you can guarantee the provenance of the source then you could use an AST operation to build your wrappers during a transpilation or load phase.
If you cannot, then you're particularly out of luck, because the parameter names may be mangled, making an object-key-to-parameter-name transformation impossible.
I can't change the functions, as I am creating a wrapper that needs to be able to handle arbitrary functions.
That's unfortunate, since making them accept a destructured parameter would be exactly what you need.
Unfortunately, the names of parameters are not available unless you parse the result of calling toString on the function, which is...fraught with peril. You basically need a full JavaScript parser (because parameter lists are complex these days, including possibly containing entire function definitions for default values) and minifiers and such may rename parameters (changing liked to _0, for instance).
You also can't count on the order of the properties in an object. (They do have an order, but not one that helps here...or almost anywhere else.)
You've said you need to handle functions whose parameters you don't know in advance, so my various ideas around wrapping functions with utilities that require passing in the names of the parameters won't work. (If anyone's curious, look at the revision list to see those.)
You can do this from toString if we can make several assumptions:
The function parameter lists are simple. They don't include destructuring or default values.
Comments are not used within the parameter lists.
Your minifier does not rename function parameters.
The functions are all traditional functions, methods, or arrow functions that do have () around the parameter list (so for instance, (x) => x * 2, not just x => x * 2).
You don't mind that it'll be fairly inefficient (parsing each time).
That's a lot of assumptions and I don't recommend it. But if you can rely on them:
// LOTS of assumptions here!
function run(f, obj) {
let params = /\(([\w\s,]*)\)/.exec(String(f));
if (!params) {
throw new Error("Couldn't parse function");
}
params = params[1].split(/\s*,\s*/).map(n => n.trim());
return f.apply(this, params.map(param => obj[param]));
}
run(runme, obj);
Live Example:
// Traditional function
const runme = function(liked, disliked){
console.log(`i like ${liked} but I hate ${disliked}`)
}
// Traditional function with newlines
const runme2 = function(
liked,
disliked
){
console.log(`i like ${liked} but I hate ${disliked}`)
}
// Arrow function
const runme3 = (liked, disliked) => {
console.log(`i like ${liked} but I hate ${disliked}`)
}
// Method
const {runme4} = {
runme4(liked, disliked) {
console.log(`i like ${liked} but I hate ${disliked}`)
}
};
const obj = {liked: 'apple', disliked: 'pear'}
function run(f, obj) {
let params = /\(([\w\s,]*)\)/.exec(String(f));
if (!params) {
throw new Error("Couldn't parse function");
}
params = params[1].split(/\s*,\s*/).map(n => n.trim());
return f.apply(this, params.map(param => obj[param]));
}
run(runme, obj);
run(runme2, obj);
run(runme3, obj);
run(runme4, obj);
That works because Function.prototype.toString is standardized now, and even in resource-constrained environments it's required to include the parameter list (but may well not include the rest of the function implementation).
Answer Re-written to factor in correct ordering.
So long as your object keys are named to match the paramaters, you can parse the functions like the following: You can see that no matter which order they are passed in, the output is correct;
var obj = {liked: 'apple', disliked: 'pear'}
var runme = function (liked, disliked) {
console.log(`i like ${liked} but I dislike ${disliked}`)
}
var translateFunc = function (func, args) {
let funcAsString = func.toString();
let argNames = funcAsString.slice(funcAsString.indexOf('(') + 1, funcAsString.indexOf(')')).match(/([^\s,]+)/g);
let parsedArgs = [];
for (let a of argNames) {
for (let k of Object.keys(args)) {
if (k == a) {
parsedArgs.push(args[a]);
}
}
}
eval(func(...parsedArgs));
}
translateFunc(runme, obj);
obj = {disliked: 'pear', liked: 'apple'}
translateFunc(runme, obj);
A bit late, but here's another try. I think it works, but minification of the code will change your function arguments but not the object properties, so you'll need to work around that.
var runme = function(liked, disliked){
console.log(`i like ${liked} but I dislike ${disliked}`)
}
var obj = {liked: 'apple', disliked: 'pear'}
const wrap = runme => {
const regex = new RegExp(/\(([^\)]*)\)/)
const args = regex.exec(runme.toString())[1].split(',').map(s => s.trim())
return obj => {
const result = args.map(a => obj[a])
runme(...result)
}
}
const wrappedRunme = wrap(runme);
console.log(wrappedRunme(obj))
I got the same error and just in case this was the issue, here's what resolved it for me...
Instead of:
runme(...obj)
I needed to spread the object into a new object:
runme({ ...obj })
You can do that in following steps:
First convert the function to a string
Use RegExp to get the arguments of the function.
Use split() to convert the argument string to array of arguments.
Then use reduce() on that array and create a new ordered array having values of given object.
var runme = function(liked, disliked){
console.log(`i like ${liked} but I dislike ${disliked}`)
}
function wrapper(obj){
let args = runme.toString().match(/\(.+\)/)[0]
args = args.slice(1,-1);
args = args.split(',').map(x => x.trim());
let ordered = args.reduce((ac,ar) => [...ac,obj[ar]],[]);
runme(...ordered);
}
wrapper({liked:"liked", disliked:"disliked"})
wrapper({ disliked:"disliked",liked:"liked"})

Is it possible to use Proxy with native browser objects (HTMLElement, Canvas2DRenderingContext ,...)?

What I was trying to accomplish. I wanted to share a single canvas (because what I'm doing is very heavy) and so I thought I'd make a limited resource manager. You'd ask it for the resource via promise, in this case a Canvas2DRenderingContext. It would wrap the context in a revokable proxy. When you're finished you are required to call release which both returns the canvas to the limited resource manager so it can give it to someone else AND it revokes the proxy so the user can't accidentally use the resource again.
Except when I make a proxy of a Canvas2DRenderingContext it fails.
const ctx = document.createElement('canvas').getContext('2d');
const proxy = new Proxy(ctx, {});
// try to change the width of the canvas via the proxy
test(() => { proxy.canvas.width = 100; }); // ERROR
// try to translate the origin of via the proxy
test(() => { proxy.translate(1, 2); }); // ERROR
function test(fn) {
try {
fn();
} catch (e) {
console.log("FAILED:", e, fn);
}
}
The code above generates Uncaught TypeError: Illegal invocation in Chrome and TypeError: 'get canvas' called on an object that does not implement interface CanvasRenderingContext2D. in Firefox
Is that an expected limitation of Proxy or is it a bug?
note: of course there are other solutions. I can remove the proxy and just not worry about it. I can also wrap the canvas in some JavaScript object that just exposes the functions I need and proxy that. I'm just more curious if this is supposed to work or not. This Mozilla blog post kind of indirectly suggests it's supposed to be possbile since it actually mentions using a proxy with an HTMLElement if only to point out it would certainly fail if you called someElement.appendChild(proxiedElement) but given the simple code above I'd expect it's actually not possible to meanfully wrap any DOM elements or other native objects.
Below is proof that Proxies work with plain JS objects. They work with class based (as in the functions are on the prototype chain). And they don't work with native objects.
const img = document.createElement('img')
const proxy = new Proxy(img, {});
console.log(proxy.src);
Also fails with the same error. where as they don't with JavaScript objects
function testNoOpProxy(obj, msg) {
log(msg, '------');
const proxy = new Proxy(obj, {});
check("get property:", () => proxy.width);
check("set property:", () => proxy.width = 456);
check("get property:", () => proxy.width);
check("call fn on object:", () => proxy.getContext('2d'));
}
function check(msg, fn) {
let success = true;
let r;
try {
r = fn();
} catch (e) {
success = false;
}
log(' ', success ? "pass" : "FAIL", msg, r, fn);
}
const test = {
width: 123,
getContext: function() {
return "test";
},
};
class Test {
constructor() {
this.width = 123;
}
getContext() {
return `Test width = ${this.width}`;
}
}
const testInst = new Test();
const canvas = document.createElement('canvas');
testNoOpProxy(test, 'plain object');
testNoOpProxy(testInst, 'class object');
testNoOpProxy(canvas, 'native object');
function log(...args) {
const elem = document.createElement('pre');
elem.textContent = [...args].join(' ');
document.body.appendChild(elem);
}
pre { margin: 0; }
Well FWIW the solution I choose was to wrap the canvas in a small class that does the thing I was using it for. Advantage is it's easier to test (since I can pass in a mock) and I can proxy that object no problem. Still, I'd like to know
Why doesn't Proxy work for native object?
Do any of the reasons Proxy doesn't work with native objects apply to situations with JavaScript objects?
Is it possible to get Proxy to work with native objects.
const handlers = {
get: (target, key) => key in target ? target[key] : undefined,
set: (target, key, value) => {
if (key in target) {
target[key] = value;
}
return value;
}
};
const { revoke, proxy } = Proxy.revocable(ctx, handlers);
// elsewhere
try {
proxy.canvas.width = 500;
} catch (e) { console.log("Access has been revoked", e); }
Something like that should do what you're expecting.
A revocable proxy, with handlers for get and set traps, for the context.
Just keep in mind that when an instance of Proxy.revocable() is revoked, any subsequent access of that proxy will throw, and thus everything now needs to use try/catch, in the case that it has, indeed, been revoked.
Just for fun, here's how you can do the exact same thing without fear of throwing (in terms of simply using the accessor; no guarantee for doing something wrong while you still have access):
const RevocableAccess = (item, revoked = false) => ({
access: f => revoked ? undefined : f(item),
revoke: () => { revoked = true; }
});
const { revoke, access: useContext } = RevocableAccess(ctx);
useContext(ctx => ctx.canvas.width = 500);
revoke();
useContext(ctx => ctx.canvas.width = 200); // never fires
Edit
As pointed out in the comments below, I completely neglected to test for the method calls on the host object, which, it turns out, are all protected. This comes down to weirdness in the host objects, which get to play by their own rules.
With a proxy as above, proxy.drawImage.apply(ctx, args) would work just fine.
This, however, is counter-intuitive.
Cases that I'm assuming fail here, are Canvas, Image, Audio, Video, Promise (for instance based methods) and the like. I haven't conferred with the spec on this part of Proxies, and whether this is a property-descriptor thing, or a host-bindings thing, but I'm going to assume that it's the latter, if not both.
That said, you should be able to override it with the following change:
const { proxy, revoke } = Proxy.revocable(ctx, {
get(object, key) {
if (!(key in object)) {
return undefined;
}
const value = object[key];
return typeof value === "function"
? (...args) => value.apply(object, args)
: value;
}
});
Here, I am still "getting" the method off of the original object, to call it.
It just so happens that in the case of the value being a function, I call bind to return a function that maintains the this relationship to the original context. Proxies usually handle this common JS issue.
...this causes its own security concern; someone could cache the value out, now, and have permanent access to, say, drawImage, by saying
const draw = proxy.drawImage;...
Then again, they already had the ability to save the real render context, just by saying
const ctx = proxy.canvas.getContext("2d");
...so I'm assuming some level of good-faith, here.
For a more secure solution, there are other fixes, though with canvas, unless it's in-memory only, the context is ultimately going to be available to anyone who can read the DOM.

JS-Interpreter - changing “this” context

JS-Interpreter is a somewhat well-known JavaScript Interpreter. It has security advantages in that it can completely isolate your code from document and allows you to detect attacks such as infinite loops and memory bombs. This allows you to run externally defined code safely.
I have an object, say o like this:
let o = {
hidden: null,
regex: null,
process: [
"this.hidden = !this.visible;",
"this.regex = new RegExp(this.validate, 'i');"
],
visible: true,
validate: "^[a-z]+$"
};
I'd like to be able to run the code in process through JS-Interpreter:
for (let i = 0; i < o.process.length; i++)
interpretWithinContext(o, o.process[i]);
Where interpretWithinContext will create an interpreter using the first argument as the context, i.e. o becomes this, and the second argument is the line of code to run. After running the above code, I would expect o to be:
{
hidden: false,
regex: /^[a-z]+$/i,
process: [
"this.hidden = !this.visible;",
"this.regex = new RegExp(this.validate, 'i');"
],
visible: true,
validate: '^[a-z]+$'
}
That is, hidden and regex are now set.
Does anyone know if this is possible in JS-Interpreter?
I’ve spent a while messing around with the JS-Interpreter now, trying to figure out from the source how to place an object into the interpreter’s scope that can be both read and modified.
Unfortunately, the way this library is built, all the useful internal things are minified so we cannot really utilize the internal things and just put an object inside. Attempts to add a proxy object also failed failed since the object just wasn’t used in a “normal” way.
So my original approach to this was to just fall back to providing simple utility functions to access the outside object. This is fully supported by the library and probably the safest way of interacting with it. It does require you to change the process code though, in order to use those functions. But as a benefit, it does provide a very clean interface to communicate with “the outside world”. You can find the solution for this in the following hidden snippet:
function createInterpreter (dataObj) {
function initialize (intp, scope) {
intp.setProperty(scope, 'get', intp.createNativeFunction(function (prop) {
return intp.nativeToPseudo(dataObj[prop]);
}), intp.READONLY_DESCRIPTOR);
intp.setProperty(scope, 'set', intp.createNativeFunction(function (prop, value) {
dataObj[prop] = intp.pseudoToNative(value);
}), intp.READONLY_DESCRIPTOR);
}
return function (code) {
const interpreter = new Interpreter(code, initialize);
interpreter.run();
return interpreter.value;
};
}
let o = {
hidden: null,
regex: null,
process: [
"set('hidden', !get('visible'));",
"set('regex', new RegExp(get('validate'), 'i'));"
],
visible: true,
validate: "^[a-z]+$"
};
const interprete = createInterpreter(o);
for (const process of o.process) {
interprete(process);
}
console.log(o.hidden); // false
console.log(o.regex); // /^[a-z]+$/i
<script src="https://neil.fraser.name/software/JS-Interpreter/acorn_interpreter.js"></script>
However, after posting above solution, I just couldn’t stop thinking about this, so I dug deeper. As I learned, the methods getProperty and setProperty are not just used to set up the initial sandbox scope, but also as the code is being interpreted. So we can use this to create a proxy-like behavior for our object.
My solution here is based on code I found in an issue comment about doing this by modifying the Interpreter type. Unfortunately, the code is written in CoffeeScript and also based on some older versions, so we cannot use it exactly as it is. There’s also still the problem of the internals being minified, which we’ll get to in a moment.
The overall idea is to introduce a “connected object” into the scope which we will handle as a special case inside the getProperty and setProperty to map to our actual object.
But for that, we need to overwrite those two methods which is a problem because they are minified and received different internal names. Fortunately, the end of the source contains the following:
// Preserve top-level API functions from being pruned/renamed by JS compilers.
// …
Interpreter.prototype['getProperty'] = Interpreter.prototype.getProperty;
Interpreter.prototype['setProperty'] = Interpreter.prototype.setProperty;
So even if a minifier mangles the names on the right, it won’t touch the ones on the left. So that’s how the author made particular functions available for public use. But we want to overwrite them, so we cannot just overwrite the friendly names, we also need to replace the minified copies! But since we have a way to access the functions, we can also search for any other copy of them with a mangled name.
So that’s what I’m doing in my solution at the beginning in patchInterpreter: Define the new methods we’ll overwrite the existing ones with. Then, look for all the names (mangled or not) that refer to those functions, and replace them all with the new definition.
In the end, after patching the Interpreter, we just need to add a connected object into the scope. We cannot use the name this since that’s already used, but we can just choose something else, for example o:
function patchInterpreter (Interpreter) {
const originalGetProperty = Interpreter.prototype.getProperty;
const originalSetProperty = Interpreter.prototype.setProperty;
function newGetProperty(obj, name) {
if (obj == null || !obj._connected) {
return originalGetProperty.call(this, obj, name);
}
const value = obj._connected[name];
if (typeof value === 'object') {
// if the value is an object itself, create another connected object
return this.createConnectedObject(value);
}
return value;
}
function newSetProperty(obj, name, value, opt_descriptor) {
if (obj == null || !obj._connected) {
return originalSetProperty.call(this, obj, name, value, opt_descriptor);
}
obj._connected[name] = this.pseudoToNative(value);
}
let getKeys = [];
let setKeys = [];
for (const key of Object.keys(Interpreter.prototype)) {
if (Interpreter.prototype[key] === originalGetProperty) {
getKeys.push(key);
}
if (Interpreter.prototype[key] === originalSetProperty) {
setKeys.push(key);
}
}
for (const key of getKeys) {
Interpreter.prototype[key] = newGetProperty;
}
for (const key of setKeys) {
Interpreter.prototype[key] = newSetProperty;
}
Interpreter.prototype.createConnectedObject = function (obj) {
const connectedObject = this.createObject(this.OBJECT);
connectedObject._connected = obj;
return connectedObject;
};
}
patchInterpreter(Interpreter);
// actual application code
function createInterpreter (dataObj) {
function initialize (intp, scope) {
// add a connected object for `dataObj`
intp.setProperty(scope, 'o', intp.createConnectedObject(dataObj), intp.READONLY_DESCRIPTOR);
}
return function (code) {
const interpreter = new Interpreter(code, initialize);
interpreter.run();
return interpreter.value;
};
}
let o = {
hidden: null,
regex: null,
process: [
"o.hidden = !o.visible;",
"o.regex = new RegExp(o.validate, 'i');"
],
visible: true,
validate: "^[a-z]+$"
};
const interprete = createInterpreter(o);
for (const process of o.process) {
interprete(process);
}
console.log(o.hidden); // false
console.log(o.regex); // /^[a-z]+$/i
<script src="https://neil.fraser.name/software/JS-Interpreter/acorn_interpreter.js"></script>
And that’s it! Note that while that new implementation does already work with nested objects, it may not work with every type. So you should probably be careful what kind of objects you pass into the sandbox. It’s probably a good idea to create separate and explicitly safe objects with only basic or primitive types.
Have not tried JS-Interpreter. You can use new Function() and Function.prototype.call() to achieve requirement
let o = {
hidden: null,
regex: null,
process: [
"this.hidden = !this.visible;",
"this.regex = new RegExp(this.validate, 'i');"
],
visible: true,
validate: "^[a-z]+$"
};
for (let i = 0; i < o.process.length; i++)
console.log(new Function(`return ${o.process[i]}`).call(o));
Hi may be interpretWithinContext look like something like that ?
let interpretWithinContext = (function(o, p){
//in dunno for what you use p because all is on object o
o.hidden = (o.hidden === null) ? false : o.hidden;
o.regex = (o.regex === null) ? '/^[a-z]+$/i' : o.regex;
console.log(o);
return o;
});
https://codepen.io/anon/pen/oGwyra?editors=1111

How to curry a function that takes an options object as argument rather than several distinct arguments?

I have been looking into partial application and currying over the last few days.
I'm wondering how could I use these concepts with a function that only takes one options object as argument.
const myFunc = options => {
const options.option1 = options.option1 || 'default value';
const options.option2 = options.option2 || 'another default value';
// ... etc, it takes about 5 more options, all of which have a
// default fall-back value if not supplied
return doSometing(options);
}
In that case, I don't feel good changing the myFunc signature and pass every option as a separate argument because it's a pain to remember the order in which the options must be supplied.
I'd like to stick with a functional style and avoid instantiating objects with new ... just to keep state; I have a hunch this can be achieved with partial application. It keeps things simpler when it's time for testing, or to instantiate.
But then, I don't know how to do partial application properly without separate arguments.
How would you handle this refactor?
I would suggest that the equivalent of currying a function taking an option object would be the same as how to handle defaults. Consider this as a partial applier:
myFuncWithMyOptions(myFunc, myOptions) {
return function(options) {
return myFunc(Object.assign({}, myOptions, options));
}
}
If you want the options in myOptions not be be overridden by those in options simply swap the two arguments to Object.assign
Following on Dan D's answer and the comments, this technique would let you partially apply such a function repeatedly until all the required fields are supplied:
const vals = (obj) => Object.keys(obj).map(key => obj[key]);
const addDefaults = (() => {
const req = Symbol();
const addDefaults = (defaults, fn) => (options) => {
const params = Object.assign({}, defaults, options);
return (vals(params).includes(req))
? addDefaults(params, fn)
: fn(params);
};
addDefaults.REQUIRED = req;
return addDefaults;
})();
const order = addDefaults({
color: addDefaults.REQUIRED,
size: addDefaults.REQUIRED,
giftWrap: false,
priority: false
}, (opts) =>
`${opts.size}, ${opts.color}, ${opts.giftWrap ? '' : 'no'} wrap, priority: ${opts.priority}`
);
order({color: 'Red', size: 'Medium'}); // "Medium, Red, no wrap, priority: false"
const orderLarge = order({size: 'Large'}); // Options -> String
orderLarge({color: 'Blue'}); // "Large, Blue, no wrap, priority: false"
I don't think your problem is connected with partial application. What exactly does myFunc do actually?
it sets a couple of optional default values
it invokes another function
This is not much. And yet two problems arise:
the function composition is hard coded and hidden in the body of myFunc
it doesn't get apparent from the function signature which default values are overwritten
Simply put, a "proper" function reveals its functionality by its signature. So let's get rid of the myFunc wrapper:
const options = {
foo: 1,
bar: true,
bat: "",
baz: []
};
// function composition
const comp = (...fs) => x => fs.reduceRight((acc, f) => f(acc), x);
// applicator for partial application with right section
const _$ = (f, y) => x => f(x) (y); // right section
// `Object` assignment
const assign = o => p => Object.assign({}, o, p);
// specific function of your domain
const doSomething = o => (console.log(o), o);
// and run (from right-to-left)
comp(doSomething, _$(assign, {baz: [1, 2, 3], bat: "abc"})) (options);
Now you can exactly see what is going on without having to look into the function bodies. The property order of the options Object doesn't matter either.
A remark on _$. It has this odd name because I prefer a visual name over a textual one in this particular case. Given the function sub = x => y => x - y, _$(sub, 2) simply means x => x - 2. This is called the right section of the partially applied sub function, because the "left" argument is still missing.

Categories

Resources