Related
I'm trying to improve my JavaScript skills. I'm learning composability and functional patterns and I'm totally lost.
I have two functions: one mapping an array and the other called from within the previous function to generate the markup.
const names = ['peter', 'paul', 'patrice']
const namesMarkup = name => {
return `<p>${name}</p>`
}
const showNames = listOfNames => {
return listOfNames.map(el => {
return namesMarkup(el)
})
}
showNames(names)
I have been reading about HOF, which technically are functions that take a function as an argument and/or return a function.
How could I compose these functions to have a HOF?
I went through the basic examples like
const square = num => num * num
const plus10 = (num, callback) => {
return callback(num) + 10
}
console.log(addTwo(7, square))
but I cannot make my mind around the previous example and working with lists.
I will appreciate help since the more I research the more confused I get.
Your mistake is to assume an array for showNames. Never do this. Always implement the simplest version of a function. In FP array is a computational effect. Don't implement such an effectful function as default:
const nameMarkup = name => {
return `<p>${name}</p>`;
}
const nameMarkup2 = name => {
return `<p>${name.toUpperCase()}!</p>`;
}
const showName = f => name => {
const r = f(name);
/* do something useful with r */
return r;
}
const names = ['peter', 'paul', 'patrice']
console.log(
showName(nameMarkup) ("peter"));
// lift the HOF if you want to process a non-deterministic number of names:
console.log(
names.map(showName(nameMarkup2)));
Now swapping the markup just means to pass another function argument. Your showName is more general, because a HOF lets you pass part of the functionality.
If we drop the array requirement, your showNames doesn't do anything useful anymore. It still illustrates the underlying idea, though.
I have a bunch of regular utility functions. I want to convert those functions to ones that accept arguments wrapped in functions (to introduce a side-effect when a value is used).
// Some utility function:
const pick = (takeLeft, left, right) =>
takeLeft ? left : right;
// Some wrapper with a side-effect
const Watched = x => () => {
console.log(`Value ${x} is used`);
return x;
};
// I want this to log:
// Value true is used
// Value L is used
const myPick = runWithWatchedValues(
pick,
Watched(true), Watched("L"), Watched("R")
);
I’m looking for help implementing runWithWatchedValues, or for somebody to explain me why it can’t be done.
Attempts
The problem with unwrapping the values before calling the inner function:
// An incorrect attempt: (also logs "R")
const runWithWatchableValues = (f, ...args) =>
f(...args.map(w => w()));
I tested if calling the function with apply and some special getters would work, but it turns out this does exactly the same 😅.
// Another incorrect attempt: (also logs "R")
const runWithWatchableValues = (f, ...watched) => {
const args = watched.reduce(
(acc, get, i) => Object.defineProperty(acc, i, { get }),
[]
);
return f.apply(null, args);
};
My current solution is to manually rewrite utility functions. I.e.:
const pick = (takeLeft, left, right) =>
takeLeft ? left : right;
const pickW = (takeLeft, left, right) =>
takeLeft() ? left() : right();
// Correctly logs true and L
pickW(Watched(true), Watched("L"), Watched("R"));
I’d rather not maintain my own library of utility functions when there are well documented and well maintained libraries like ramda or lodash…
The question(s)
I’m starting to feel like the thing I want just is just not something the language can do… But I hope I’m wrong!
Is it theoretically possible to write runWithWatchableValues and get the desired result?
If yes, how?
If no, is there another automated way (Babel/build steps?) you can think of to prevent having to manually rewrite pick to work with wrapped values?
“This is an x/y problem!”
It might be, but I wanted to keep it simple. Here’s what I’m really doing (using knockout.js):
const pick = (takeLeft, left, right) =>
takeLeft ? left : right;
const takeLeft = ko.observable(true);
const left = ko.observable("L");
const right = ko.observable("R");
// BROKEN: The easy, but wrong implementation:
const myPick = ko.pureComputed(
() => pick(takeLeft(), left(), right())
);
console.log(
"Naive approach:",
myPick(), // Right: "L"
myPick.getDependenciesCount() // Wrong: 3
);
// The dependency on `right` will mean that updating
// it will cause myPick to re-evaluate, even though we
// already know its return value won't change.
// FIXED: The manual fix:
const pickObs = (takeLeft, left, right) =>
takeLeft() ? left() : right();
const myCorrectPick = ko.pureComputed(
() => pickObs(takeLeft, left, right)
);
console.log(
"With manual rewrite:",
myCorrectPick(), // Right: "L"
myCorrectPick.getDependenciesCount() // Right: 2
);
// Changing `right` doesn't do anything. Only once `takeLeft`
// is set to `false`, a dependency on `right` will be created
// (and the dependency on `left` will be removed).
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
Is it theoretically possible to write runWithWatchableValues and get the desired result?
No. Arguments are not passed lazily in JavaScript. You would need to strictly evaluate the functions before passing the values to the wrapped function, and that's what you are trying to avoid.
Is there another automated way (Babel/build steps?) you can think of to prevent having to manually rewrite pick to work with wrapped values?
Sure, you can write your own compiler that does that (it seems relatively easy), but I doubt there is an existing babel plugin that does this. Lazy evaluation is not useful that often, most functions use all their arguments in any case.
You can handle a form of "lazy evaluation" in your pick function, if you can change how data is passed to that function:
function pick(takeLeft, left, right){
if(typeof takeLeft === "function"){
takeLeft = takeLeft();
}
let choice = takeLeft ? left : right;
if(typeof choice === "function"){
choice = choice();
}
return choice;
}
const p1 = pick(
() => true,
() => { console.log("Left is called!"); return "left"; },
() => { console.log("Right is called!"); return "right"; });
const p2 = pick(
false,
"left",
"right");
console.log(01, p2)
As you can see, the 2nd parameter isn't called if you tell it to get the left one, but you can still pass normal variables.
So, if you want something to be evaluated lazily, only if it's chosen, pass it as a callback instead of a normal value.
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"})
Say I have some code like this:
function a(...numbers) {
return numbers.map(n => b(n));
}
function b(n) {
return n+1;
}
I've been looking at ways I would test like code like this, specifically to test the functionality of a without actually calling b.
One option is to use dependency injection, and to pass function b as a parameter.
ie.
function a(...numbers, _b=b) {
return numbers.map(n => _b(n));
}
But of course, the rest operator won't allow me to tack an argument on the end.
And I don't want to put the function argument first - because then the developer is having to have to pass function b in every time, or whatever, or pass a null value or similar.
Is there a way you could achieve this functionality?
rest parameters can only work as the last argument accepted by a function, it captures all argumets that were not declared in the function parameter. You can actually let go of the rest parameter and pass in an array
function a(numbers, _b = b) {
return numbers.map(n => _b(n));
}
function b(n) {
return n+1;
}
console.log(a([1,2,3,4], f => f * 1));
Function.prototype.bind() (kinda) solves this!
//The convention we will use here is that developers shouldn't use the
// _underscore methods in production.
export const _a = function(_b, ...numbers) {
return numbers.map(n => _b(n));
};
export const b = function(n) {
return n+1;
}
export const a = _a.bind(null, b);
console.log(a(1,2,3)) //[2,3,4]
This also has the advantage of that you're hiding the injected function from the developer.
Now how would you test this?
You have to test the _underscore method, so something like:
import { _a } from "../functions";
describe("_a", () => {
it("_a(1,2,3) calls _b three times.", () => {
const mockFn = jest.fn();
const a = _a.bind(null, mockFn);
a(1, 2, 3);
expect(mockFn.mock.calls).toHaveLength(3);
})
});
If you're interested - I've started a Github repo with a more fleshed out example of this approach here.
If anyone has a tidier way of doing this - I'm all ears.
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.