This question already has answers here:
Function composition with multiple arguments using reduceRight() - JavaScript
(3 answers)
Closed 2 years ago.
I'm trying a very simple program to understand composing functions in JavaScript.
// This compose comes from web
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
const array = 'array'
const actionCreator = (type, payload) => {
return {
type,
payload
}
};
const log = object => {
console.log(object)
};
// This works
log(actionCreator(array, [1,2,3,4]));
// This does not
compose(log, actionCreator)(array, [1,2,3,4]);
For some reasons, calling log(actionCreator) works, and it logs{type: 'array', payload: [1,2,3,4]}. But calling compose keeps logging{type: 'array', payload: undefined}. And it's not just an array. Regardless of what the second argument after array is, the payload is always undefined, whether it's a string, a number...But somehow it stills knows type, only payload is undefined So is there something I did wrong. It's so simple, it should not have these kinds of bugs. Thanks for reading.
You can adjust your compose() function:
const compose = (...fns) => (...args) =>
fns.reduceRight((v, f, i) => (i !== fns.length - 1) ? f(v) : f(...args), null);
That will use the list of provided arguments for the first function, and then behave as .reduceRight normally behaves for the rest, using the return value of the previous iteration as the input value for the next.
The .reduceRight() is started with null as the input because the first iteration won't use that.
edit — here's a simpler version:
const compose = (...fns) => (...args) => fns.reduceRight((v, f) => f(v), fns.pop()(...args));
That one uses the last function applied to the arguments as the initial value for the .reduceRight().
It´s clear that your compose function expects "x" but you are passing "(array, [1,2,3,4])" so in fact "x" becomes the first param which is "array", but the second param is undefined cause you are not passing it to the initial value of the accumulator
Related
The Problem:
I'm learning functional programming
Just kidding, but also...
I have a helper function that composes a function with itself over and over again until some condition is met. Like f(f(f(f(f(f(f(x))))))) or compose(f,f,f,f,f,f,f)(x), except it keeps going unless told to stop.
The way I've implemented it, it doesn't really feel like composition (and perhaps that's the wrong word to use here regardless)
This is my current solution:
const selfComposeWhile = curry(
(pred, fn, init) => {
let prevVal = null;
let nextVal = init;
while(prevVal == null || pred(prevVal, nextVal)){
prevVal = nextVal;
nextVal = fn(nextVal);
}
return nextVal;
}
);
and here it is in use:
const incOrDec = ifElse(gt(30), inc, dec);
console.log(
selfComposeWhile(lt, incOrDec, 0)
); // -> 29
I don't want to use recursion as JavaScript doesn't have proper tail recursion and the namesake of this site (Stack Overflow) is a real concern for how I use this.
There's nothing wrong with it as is, but I've been trying to learn functional programming techniques by applying them to a dummy problem and this is one of the few places my code stands out as decidedly imperative.
I also have
useWith(selfComposeWhile, [pipe(nthArg(1), always)]);
That takes a predicate that is only concerned with the nextVal, which seems like the more general case of this.
The Question:
Can anybody think of a more functional (sans recursion) way to write selfComposeWhile and its cousin?
R.unfold does mostly what you, it accepts a seed value (init), and transforms it, and on each iteration it returns the current value, and the new seed value. On each iteration you need to decide to continue or stop using a predicate.
The main difference between your function, and R.unfold is that the last one produces an array, and this is easily solvable with R.last:
const { curry, pipe, unfold, last } = R
const selfComposeWhile = curry(
(pred, fn, init) => pipe(
unfold(n => pred(n, fn(n)) && [n, fn(n)]),
last
)(init)
)
const { ifElse, gt, inc, dec, lt } = R
const incOrDec = ifElse(gt(30), inc, dec)
console.log(selfComposeWhile(lt, incOrDec, 0)) // -> 29
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous"></script>
The solution I've settled on for now.
I've taken selfComposeWhile and named it unfoldUntil. I'm not sure that's the best name as it doesn't return a list. It is basically R.until where the predicate can access the previous and the next value.
To bring them a bit more in alignment, I've changed my while behavior into until behavior (R.complement the predicate).
unfoldUntil
If it were typed:
unfoldUntil: <T>(
pred: (p:T, n:T) => boolean,
fn: (a:T) => T,
init: T
) => T
Implemented
const unfoldUntil = curry(
(pred, fn, init) => pipe(
unfold(n =>
isNil(n) ?
false :
call((next = fn(n)) =>
(pred(n, next) ?
[next, null] :
[next, next])
)
),
last
)(init)
);
Notes: This will never pass null/undefined into the transformation function (fn). You can use a transformation that returns null as a stopping condition and be returned the previous value. Otherwise, you'll be returned the first value of next that causes the predicate to return true.
Can somebody explain to me how Array.reduce can put functions as arguments in function composition like this:
const composeB = (f, g) => x => f(g(x))
const add = a => b => a + b
const add5 = add(5)
const double = a => a * 2
const add5ThenDouble = [double, add5].reduce(composeB)
console.log(add5ThenDouble(6)); // 22
So, according to my knowledge (which is not enough) of reduce function is that Array.reduce iterate through an array like this - it takes each of array values and puts them through callback function with another argument (lets call it accumulator). The next array value will undergo the same callback function, but with (eventually) changed accumulator value.
What confuses me in code example above is:
1) Array is list of functions [double, add5].
2) In first iteration, composeB will receive arguments: f=accumulator (empty value), g=double(function). ComposeB should return emptyvalue(double(6)) (or maybe not??)
I know that I am missing something, but can someone explain me what?
The documentation for reduce says that the first argument is
A function to execute on each element in the array (except for the first, if no initialValue is supplied).
So in this case, you have not supplied an initialValue and so compose is only called once (with arguments double and add5).
var inc = (x) => ++x, // increment +1
x2 = (x) => x*2, // multiply by 2
sq = (x) => x*x; // square
var compose = (f,g) => (_) => g(f(_));
var f1 = [ sq, inc, inc ].reduce(compose, (_) => _);
f1(10); // 102
var f2 = [ inc, sq, inc ].reduce(compose, (_) => _);
f2(10); // 122
See the code above, notice:
identity function (_) => _ as default value (second argument) for reduce
compose will NOT return a number, it will return a FUNCTION that is a composition of all the functions that were passed in before... So - only when you CALL the (say) f1, only then the functions will be executed.
we are putting all the functions from list [a,b,c,d,e] into a chain of e,d,c,b,a (reverse order!) and then execute as e(d(c(b(a(10))))) getting the order we actually wanted.
(f,g) => (_) => g(f(_)) <-- function arguments are actually reversed when calling them. Longer version: function compose (f, g) { return function (z) { return g(f(z)); }; }
p.s.: i use var because i can ;)
I'm trying to map through collection of objects inside an object and access the color item, but i get an error Unexpected token, expected ",". This is how i'm trying to map through. Is this the right way to map objects to retrieve value from colors.
{Object.keys(this.state.lists).map((item, i) =>
(this.state.lists[item].colors).map(item, i) =>
<li key={i}>{this.state.lists[item].colors[item]} </li>
)}
this.state.lists looks like this:
{{id: 1, colors:["red", "blue"]}, {id: 2, colors:["green", "yellow"]}}
You are not passing a callback function to your second map call, .map(item, i). Hence the syntax error. It should instead be something like .map((item, i) => ...).
Here's some cleaned up code that might make sense of this, though I haven't tested if it works with React:
const colors = Object.keys(this.state.lists).map(itemKey => {
return <li key={itemKey}>{this.state.lists[itemKey].colors[0]}</li>
})
And when you render,
<ul>{colors}</ul>
When using ES6 functions, you can omit the () of the parameters, only if you use 1 parameter. What you've done is actually closed your map before you even got to the fat arrow (=>). Your error is saying it doesn't understand the , in map(item, i), since map doesn't accept a second parameter. Here's a bit of a break-down, followed by some optimized code for your problem.
A basic ES6 function is () => {}, where the parameters go between the () braces, and the code goes between the {}.
Here's a basic sum function: (a, b) => { return a+b }. Since this only has one line, and it's the return value, you can omit the {} brackets. i.e., (a, b) => a+b
Here's a hello function: (name) => { return 'hello ' + name }. Since it only has 1 parameter, you can use name => { return 'hello ' + name }. Or even using the above rule: name => 'hello ' + name.
These shortcuts can make code easier to write, but perhaps more difficult to understand. If in doubt, just always keep the () braces to avoid confusion.
const obj = {
1: {id: 1, colors:["red", "blue"]},
2: {id: 2, colors:["green", "yellow"]}
}
for (key in obj) {
const item = obj[key];
item.colors.map((color, i) => {
console.log( `<li key=${item.id}-${i}>${color}</li>`)
// Below lines are commented out because StackOverflow
// does not process JSX tags. Just uncomment and remove
// the console.log above
// return (
// <li key={item.id}-${i}>{color}</li>
// )
});
}
NOTES: Instead of using Object.keys to get an array of keys, I just use a for...in loop to accomplish the same thing.
Documentation
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in
In Professor Frisby Introduces Composable Functional JavaScript the identity functor was introduced:
const Box = x =>
({
map: f => Box(f(x)),
fold: f => f(x) // for testing
})
I spent the better part of the day understanding functors and why the above JavaScript code is actually the identity functor. So I thought I would alter it to get a "real" functor that is not the identity functor. I came up with this:
const Endo = x =>
({
map: f => Endo(f(x).split('')),
fold: f => f(x).split('') // for testing
})
My reasoning is that with Box, Id_Box: Box -> Box and Id_Box f = f. Endo would also map to itself but Endo(f): Endo(x) -> Endo(y) (if f: x -> y).
Am I on the right track?
EDIT:
Replaced string with the more generic x as it was in the original examples.
As pointed out in this answer, for our purposes as programmers we can treat all functors as endofunctors so don't get too caught up on the differences.
As for what a functor is, in brief it is
a data structure (Box in your example)
that can support a mapping operation (think Array.prototype.map)
and that mapping operation respects identity: xs === xs.map(x => x)
...and composition: xs.map(f).map(g) === xs.map(f . g) where . is function composition.
That's it. No more, no less. Looking at your Box, it's a data structure that has a map function (check 1 & 2) and that map function looks like it should respect identity and composition (check 3 & 4). So it's a functor. But it doesn't do anything, which is why it's the identity functor. The fold function isn't strictly necessary, it just provides a way to 'unwrap' the box.
For a useful functor, let's look at JavaScript arrays. Arrays actually do something: namely they contain multiple values rather than just a single one. If an array could only have one element, it'd be your Box. For our purposes we'll pretend that they can only hold values of the same type to simply things. So an array is a data structure, that has a map function, that respects identity and composition.
let plus = x => y => x + y;
let mult = x => y => x * y;
let plus2 = plus(2);
let times3 = mult(3);
let id = x => x;
let compose = (...fs) => arg => fs.reverse().reduce((x, f) => { return f(x) }, arg);
// Here we need to stringify the arrays as JS will compare on
// ref rather than value. I'm omitting it after the first for
// brevity, but know that it's necessary.
[1,2,3].map(plus2).toString() === [3,4,5].toString(); // true
[1,2,3].map(id) === [1,2,3]; // true
[1,2,3].map(plus2).map(times3) === [1,2,3].map(compose(times3, plus2)); // true
So when we map a function over a functor (array) we get back another instance of the same functor (a new Array) with the function applied to whatever the functor (array) was holding.
So now lets look at another ubiquitous JavaScript data structure, the object. There's no built in map function for objects. Can we make them a functor? Assume again that the object is homogenous (only has keys to one type of value, in this example Number):
let mapOverObj = obj => f => {
return Object.entries(obj).reduce((newObj, [key, value]) => {
newObj[key] = f(value);
return newObj;
}, {});
};
let foo = { 'bar': 2 };
let fooPrime = mapOverObj(foo)(plus2); // { 'bar': 4 }
And you can continue on to test that the function accurately (as far as is possible in JavaScript) supports identity and composition to satisfy the functor laws.
Is there a known reason why passing in null as a parameter in ES6 does not use the default parameter when one is provided?
function sayHello(name = "World") {
console.log("Hello, " + name + "!");
}
sayHello("Jim"); // Hello, Jim!
sayHello(undefined); // Hello, World!
sayHello(null); // Hello, null!
This is not that obvious
I've read some comments of why undefined is completely different than null and that's why it explains the current behavior of default parameters.
One could argue that explicitly passing undefined should not trigger the default value substitution because when I have a function:
const f = (x = 'default') => console.log(x);
I would like it to print "default" when I run it as:
f();
but I would like it to print "undefined" when I explicitly run it as:
f(undefined);
because otherwise why would I use f(undefined) in the first place? Clearly my intention here is to provide some argument instead of leaving it out.
Examples to the contrary
Now, consider this function:
const g = (...a) => console.log(JSON.stringify(a));
When I use it as:
g();
I get: []
But when I use it as:
g(undefined);
I get: [null]
which clearly demonstrates that:
passing undefined is not the same as not passing an argument at all
sometimes null can be a default value instead of undefined
TC39 decision
Some background on the current behavior of the default parameters can be seen in the July 24 2012 Meeting Notes by TC39:
https://github.com/rwaldron/tc39-notes/blob/master/meetings/2012-07/july-24.md
Incidentally, it shows that explicitly passing undefined originally did not trigger the default value in the first draft and there was a discussion about whether or not it should do that. So as you can see the current behavior was not so obvious to the TC39 members as it now seems to be to people who comment here.
Other languages
That having been said, the decision of what should and what should not trigger the default value substitution is completely arbitrary at the end of the day. Even having a separate undefined and null can be though of as quite strange if you think about it. Some language have only undefined (like undef in Perl), some have only null (like Java), some languages use equivalents of false or an empty list or array for that (like Scheme where you can have an empty list or #f (false) but there is no equivalent of null that would be distinct from both an empty list and a false value) and some languages don't even have equivalents of null, false or undefined (like C which uses integers instead of true and false and a NULL pointer which is actually a normal pointer pointing to address 0 - making that address inaccessible even when mapped by any code that tests for null pointers).
What you can do
Now, I can understand your need to substitute default values for null. Unfortunately this is not a default behavior but you can make a simple function to help you:
const N = f => (...a) => f(...a.map(v => (v === null ? undefined : v)));
Now every time you want defaults substituted for null values you can use it like this. E.g. if you have this function from one of the examples above:
const f = (x = 'default') => console.log(x);
it will print "default" for f() and f(undefined) but not for f(null). But when you use the N function defined above to define the f function like this:
const f = N((x = 'default') => console.log(x));
now f() and f(undefined) but also f(null) prints "default".
If you want somewhat different behavior, e.g. substituting default values for empty strings - useful for environment variables that can sometimes be set to empty strings instead of not existing, you can use:
const N = f => (...a) => f(...a.map(v => (v === '' ? undefined : v)));
If you want all falsy values to be substituted you can use it like this:
const N = f => (...a) => f(...a.map(v => (v || undefined)));
If you wanted empty objects to be substituted you could use:
const N = f => (...a) => f(...a.map(v => (Object.keys(v).length ? v : undefined)));
and so on...
Conclusion
The point is that it's your code and you know what should be the API of your functions and how the default values should work. Fortunately JavaScript is powerful enough to let you easily achieve what you need (even if that is not the default behavior of default values, so to speak) with some higher order function magic.
That's just how default parameters are defined in the spec. See MDN: (emphasis mine)
Default function parameters allow formal parameters to be initialized with default values if no value or undefined is passed.
null is neither no value, nor undefined, so it doesn't trigger the default initialization.
null is a value that won't trigger the default value to be used, the default values will be used when the argument is undefined.
ramdajs version code
// import ramda
// infra code
const isEmptyOrNil = (value) => isEmpty(value) || isNil(value)
const nullChange = (defaultValue) => (value) => isEmptyOrNil(value)? defaultValue: value
// single null
const _somef = (value) => value
const somefWithDefault = (defaultValue) => pipe(nullChange(defaultValue), _somef)
const somef = somefWithDefault('myValue')
somef(null)
// many args
const _somef2 = (value, value2, value3) => value + value2 + value3
const nullChangeMyValue = nullChange('myValue')
const somef2 = useWith(_somef2, [nullChangeMyValue, nullChangeMyValue, nullChangeMyValue])
somef2(null, undefined, 1234)
// many args2 version2
const nullChangeWith = (defaultValue) => nullChange(defaultValue)
const arrayNullChangeWith = (defaultValue) => (array) => array.map(nullChangeWith(defaultValue))
const arg2Array = (...args) => args
const funcApplyDefaultValue = (defaultValue) => (func) => pipe(
arg2Array, arrayNullChangeWith(defaultValue), apply(func)
)
const v2somef2 = funcApplyDefaultValue(8)(_somef2)
v2somef2(1,2,null)
gits: https://gist.github.com/otwm/3a6358e53ca794cc2e57ade4af91d3bb