Function composition of pluck - javascript

I'm learning functional programming in JS and I'm trying to write my own pluck.
const curry = (f, arr = []) => (...args) =>
(a => (a.length === f.length ? f(...a) : curry(f, a)))([
...arr,
...args,
]);
const map = curry((fn, arr) => arr.map(fn));
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);
const prop = curry((key, obj) => obj[key]);
const pluck = pipe(prop, map);
But for some reason, pluck doesn't work. As far as I thought, this pluck would:
Call prop with the key I invoke pluck with.
So, prop with a curried key gets put as the function into map, which is returned from pipe.
Then if I pass it an array, it should map over the array, applying prop with the key.
But,
pluck('foo')([{ foo: 'bar'}]);
[ƒ]
What am I doing wrong?

Because the built-in .map() function passes 3 arguments to the callback, your code is getting confused. It's easy to fix:
const map = curry((fn, arr) => arr.map(v => fn(v)));

Related

JS: Don't add value to map on condition

I have an object that I need to convert into an array. This is the object:
const dogCounts: {
maltese: 4,
poodle: 2,
labrador: 10,
corso: 0
}
And I send it to a component thru props and I have a useMemo hook to convert it into a structure like this: [ [maltese, 4], [poodle, 2], ... ]
const formatDogCounts = useMemo(() => {
const z = Object.keys(dogCounts || {})?.map(i => {
if (dogCounts[i] === 0) return; // Don't add it to map and skip it
return [i, dogCounts[i]]
})
}, [dogCounts])
When the number is zero I don't want to add it to the formatDogCounts variable. What I put above doesn't fit to eslints rules. Arrow function expected no return value.eslintconsistent-return.
Also I put that {} in the object.keys for the case when the counts haven't loaded yet is there a cleaner way to null check that?
map doesn't filter out values; doing a simple return; in map makes the corresponding entry in the result array undefined. If you want to do that, you'll need to filter first, or build the array another way (such as a simple loop).
Here's the filter approach:
const formatDogCounts = useMemo(() => {
const z = Object.keys(dogCounts || {})?
.filter(name => dogCounts[name] !== 0)
.map(name => [name, dogCounts[i]]);
}, [dogCounts]);
Note that Object.entries provides the very [name, value] pairs you want, so you could avoid map, and there's no reason for the conditional chaining operator as neither Object.keys nor Object.entries ever returns undefined or null:
const formatDogCounts = useMemo(() => {
const z = Object.entries(dogCounts || {})
.filter(([, value]) => value !== 0);
}, [dogCounts]);
Note the , prior to value in the [, value] destructuring pattern so we're grabbing the second array entry (the value), not the first (the name).
We can also avoid the calls to Object.entries and filter entirely when there is no dogCounts:
const formatDogCounts = useMemo(() => {
const z = dogCounts
? Object.entries(dogCounts).filter(([, value]) => value !== 0)
: [];
}, [dogCounts]);
In a comment you've said:
The entries solution worked really well for me! Is there a way now to return an object instead of an array with total dog counts and then an array of items? Ex: formatDogCounts: { totalDogs: 30, items: [...] }
Sure. If there will only ever be a reasonable number of dogs (fewer than hundreds of thousands), I'd just do it as a separate operation at the end:
const formatDogCounts = useMemo(() => {
const items = dogCounts
? Object.entries(dogCounts).filter(([, value]) => value !== 0)
: [];
return {
totalDogs: items.reduce((sum, [, value]) => sum + value, 0),
items,
};
}, [dogCounts]);
(A straight sum is the only ad hoc use of reduce I'll do, and even then I don't much care for it.)
Or you could make your filter callback slightly impure and count them as you go:
const formatDogCounts = useMemo(() => {
let totalDogs = 0;
const items = dogCounts
? Object.entries(dogCounts).filter(([, value]) => {
totalDogs += value;
return value !== 0;
})
: [];
return {
totalDogs,
items,
};
}, [dogCounts]);
If you want to perform a map and filter operation together, you can use flatMap, returning an empty array to skip an element.
const formatDogCounts = useMemo(() => {
const z = Object.keys(dogCounts || {})?.flatMap(i => {
if(dogCounts[i] === 0) return []; // Dont add it to map and skip it
return [[i, dogCounts[i]]];
})
}, [dogCounts])
You are trying to filter using a map which isn't possible. Map will return the same amount of values as you put in. You can do a foreach or you can do a combination of map and filter to get the expected results.
Foreach
const z = []
Object.keys(dogCounts).forEach((key) => {
if(dogCounts[key]) {
// if the value is truthy push the structure to the array.
z.push([key, dogCounts[key]]);
}
}
Map/Filter
const z = Object.keys(dogCount)
.map((key) => [key, dogCount[key]) // map to restructure object.keys
.filter(([key, value]) => value); // filter to remove falsey values (0, null, undefined)

Transform from string array to hashmap in Lodash

What is the most precise way to transform from this
["access","edit","delete"]
to this
{access:true, edit:true, update:true}
Currently i loop to assign each value in object but i wonder if lodash already provide function for this
Use reduce(). This can all be done with a simple one-liner, that doesn't require any libraries:
const input = ["access","edit","delete"];
console.log(
input.reduce((obj, key) => { obj[key] = true; return obj; }, {})
);
With the new es6 spread syntax, you can even make this easier:
const input = ["access","edit","delete"];
console.log(
input.reduce((obj, key) => ({...obj, [key]: true}), {})
);
LODASH
You can map it to a array of entries and then simply use fromPairs of lodash
_.fromPairs(input.map(k=>[k, true]))
var input = ["access","edit","delete"];
var res = _.fromPairs(input.map(k=>[k,true]));
console.log(res);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
ES6
You can map your input to a key (your each input) value (true) pair of objects and assign them.
Object.assign( ...input.map(k=>({[k]: true})))
var input = ["access","edit","delete"]
var res = Object.assign( ...input.map(k=>({[k]: true})));
console.log(res);
In case you want a Map object you can map your input to entries (as used in lodash example) and simply construct a new Map like
new Map(input.map(k=>[k, true]))
No need to import a library for something so simple, just reduce the array of keys into an object indexed by those keys:
const input = ["access","edit","delete"];
const output = input.reduce((a, key) => Object.assign(a, {[key]: true}), {});
console.log(output);
Or, assigning to the property of the accumulator rather than using Object.assign:
const input = ["access","edit","delete"];
const output = input.reduce((a, key) => {
a[key] = true;
return a;
}, {});
console.log(output);
If you absolutely want to use lodash (As opposed to the above vanilla javascript reduce() answers), you can use _.mapValues() to accomplish this:
const input = ["access","edit","delete"];
const output = _.mapValues(_.keyBy(input), () => true)
console.log(output);
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.11/lodash.min.js" integrity="sha256-7/yoZS3548fXSRXqc/xYzjsmuW3sFKzuvOCHd06Pmps=" crossorigin="anonymous"></script>

map non-object values in nested arrays in javascript

I'm trying to map an array that has n-dimension sub-arrays. I've looked at deep-map but I'm not using any objects. An example of what I'm trying to do is:
deepMap([4,5,[3,4,[2]]], (x) => x+5)
returns [9,10,[8,9,[7]]]
The function after the array in deepMap can be any function
const deepMap=(arr, fn) =>{
const stop = arr.length===0;
const tail = arr.slice(1);
const head = arr[0];
const front = head instanceof Array ? [head[0]]: [] ;
const next = fn(front, head);
return stop ? [] : front.concat(deepMap(tail,fn));
}
How do you apply a function to values in nested arrays while keeping the whole array a nested array?
any help is greatly appreciated!
You may do as follows in a Haskellesque fashion;
function deepMap([x,...xs],f){
return x ? [Array.isArray(x) ? deepMap(x,f) : f(x), ...deepMap(xs,f)]
: [];
}
var arr = [4,5,[3,4,[2]]],
res = deepMap(arr, x => x+5);
console.log(res);
Use simple recursion. For a nested array, map over the array calling deepMap recursively. When you reach a leaf, call the function.
function deepMap(arr, fn) {
return arr instanceof Array ? arr.map(el => deepMap(el, fn)) : fn(arr);
}
console.log(deepMap([4, 5, [3, 4, [2]]], (x) => x + 5));

Nested for-loop in idiomatic JavaScript

I'm translating some code from Python to JavaScript and I need to rewrite the following nested for-loop list comprehension (where solution is a dictionary of string keys and list values).
events = [e for key in solution.keys() for c in solution[key] for e in c.events]
Without giving it much thought, I'd translate it to something like this unwieldy, ugly nested for-loop.
const events = []
for (const key of solution.keys()) {
for (const c of solution[key]) {
for (const e of c.events) {
events.push(e)
}
}
}
But maybe there's a nicer way. How can I rewrite the above nested for-loop in short, idiomatic, modern (ES2015+) JavaScript?
There’s not a much nicer way. The original Python should be using values:
events = [e for foo in solution.values() for c in foo for e in c.events]
which you can reflect in JavaScript:
const events = [];
for (const foo of solution.values()) {
for (const c of foo) {
for (const e of c.events) {
events.push(e);
}
}
}
This is pretty short and easy to read (or it will be when foo is replaced with an appropriate name). You can use concat if you like creating lots of intermediate lists:
const events = [];
for (const foo of solution.values()) {
for (const c of foo) {
events = events.concat(c.events);
}
}
and reduce if you like function calls that don’t really save space or readability, assuming the values of solution are arrays:
const events = [];
for (const foo of solution.values()) {
events = foo.reduce(
(m, n) => m.concat(n.events),
events
);
}
and Array.from and reduce if you really like intermediate lists and don’t really like readability:
const events =
Array.from(solution.values()).reduce(
(events, foo) => events.concat(
foo.reduce(
(m, n) => m.concat(n.events),
events
)
),
[]
);
Defining more functions dulls the pain but doesn’t change the fact that ES6 is not so great
const concat = arrays =>
arrays.reduce((m, n) => m.concat(n), []);
const concatMap = (fn, arrays) =>
concat(arrays.map(fn));
const events = concatMap(
foo => concatMap(foo, c => c.events),
Array.from(solution.values())
);
Maybe the standard library is missing some iterator functions
function* map(fn, iterable) {
for (const x of iterable) {
yield fn(x);
}
}
function* concat(iterables) {
for (const iterable of iterables) {
yield* iterable;
}
}
const concatMap = (fn, iterables) =>
concat(map(fn, iterables));
const events = Array.from(
concatMap(
([key, foo]) => concatMap(c => c.events, foo),
solution
)
);
Stick with the for loops, honestly.

Is this a valid monad transformer in Javascript?

In order to better understand monad transformers I implemented one. Since Javascript is dynamically typed I don't mimic type or data constructors but declare only plain old Javascript objects, which hold the corresponding static functions to form a specific monad / transformer. The underlying idea is to apply these methods to a value/values in a container type. Types and containers are separated so to speak.
Arrays can contain any number of elements. It is trivial to extend Arrays so that they implement the monad interface. Arrays can also represent the two variants of the maybe type. An empty Array corresponds to nothing. An Array with a single element corresponds to just(a). Consequently I will use Arrays as my container type. Please note that this is an quick and dirty implementation just for learning:
const array = {
of: x => Array.of(x),
map: f => ftor => ftor.map(f),
ap: ftor => gtor => array.flatten(array.map(f => array.map(f) (gtor)) (ftor)),
flatten: ftor => ftor.reduce((xs, y) => xs.concat(y), []),
chain: mf => ftor => array.flatten(array.map(mf) (ftor))
}
const maybe = {
of: array.of,
empty: () => [],
throw: ftor => { if (ftor.length > 1) throw Error("indeterministic value"); return ftor },
map: f => ftor => maybe.throw(ftor).map(f),
ap: ftor => gtor => maybe.flatten(maybe.map(f => maybe.map(f) (gtor)) (ftor)),
flatten: array.flatten,
chain: mf => ftor => maybe.flatten(maybe.map(mf) (ftor)),
T: M => {
return {
of: x => M.of(maybe.of(x)),
empty: () => M.of(maybe.empty()),
map: f => ftor => M.map(gtor => maybe.map(f) (gtor)) (ftor),
ap: ftor => gtor => M.flatten(M.map(htor => M.map(itor => maybe.ap(htor) (itor)) (gtor)) (ftor)),
flatten: maybe.flatten,
chain: mf => ftor => M.chain(gtor => maybe.chain(mf) (gtor)) (ftor)
};
}
};
Now I combine a maybe transformer with the monadic array to get a monad that can handle arrays of maybes.
const arraym = maybe.T(array);
const add = x => y => x + y;
const addm = x => y => [x + y];
const arrayOfMaybes = [[1],[],[3]]
When I treat arraym as an applicative functor, everything works as expected:
// yields: [[11],[],[13]] as expected
arraym.ap(arraym.map(add) (arrayOfMaybes)) (arraym.of(10));
However, when I apply chain something goes wrong:
// yields: [11,13] but [[11],[13]] expected
arraym.chain(x => arraym.chain(y => addm(x) (y)) (arrayOfMaybes)) ([[10]])
Is the cause of this problem
that this isn't a valid monad transformer?
that the way I apply chain is wrong?
that my expectation regarding the result is wrong?
Is the cause of this problem that the way I apply chain is wrong?
Yes. You need to pass an mf that returns an arraym, not an array like addm does. You could use
const addmm = x => y => array.map(maybe.of)(addm(x)(y))
arraym.chain(x => arraym.chain( addmm(x) )(arrayOfMaybes))([[10]])
To help with this, you also might consider implementing lift for every monad transformer.

Categories