mergeAllWith
. I'd think I have to use reduce
, but I can't see how that's possible.
{}
initial value to reduce haha
mergeAllWith
? I'm intrigued @m59peacemaker , would that work something like this?mergeAllWith(
sum,
[
{ a: 'test', value: 1 },
{ b: 'test', value: 2 },
{ c: 'test', value: 3 },
{ d: 'test', value: 4 },
]
);
//=> { a: 'test', b: 'test', c: 'test', d: 'test', value: 10 }
reduce(mergeWith(add))
for example
reduce(mergeWith(add), {})
var mergeAllWith = curry2((fn, array) => reduce(mergeWith(fn), {})(array)
fn
is something like mean
or median
?
reduce
then
reduce
won't cut it.
var x = [
{a: 1},
{a: 2, b: 1},
{a: 3, b: [1, 2]}
]
// => { a: [1, 2, 3], b: [1, [1, 2]] }
pipe(
map(map(of)),
reduce(mergeWith(concat), {})
)(x)
map(map(of))
FTW man I love fp
reduce(mergeWith((x, y) => [].concat(x, [y])), {})
mergeWith
is good :)
lodash.mergewith
require('the-package')
, but you can and probably will just require('the-package/one-function')
and it's like a quarter of a kb.
du -sh ./
map
> Also treats functions as functors and will compose them together.
map
for function is compose
map(append(2), of)(1) // => [1, 2]
lift(lift(fn))
?
liftN
, but I guess it's just lift
but with manual arity
specification
@Bravilogy Good question. Not sure. But you could at least define lift
lift
separately, if you want to reuse it. I think it reads fine inline, particularly if you are only doing it once.
const liftTwice = compose(lift, lift)
const liftedTwice = liftTwice(fn)
You could also map
and then run the lifted function in that inner context.
Lately I've found myself using R.sequence
instead of lift, particularly with streams.
Ok so I wanted to practice a bit with lift and here's a bit complex problem I came up with and its solution.
Scenario:
I'd like to fetch a user with an ID. Then I want to fetch all posts for that user and then I want to fetch all comments for each post. The end result should give me a nested and constructed object that looks something like this:
{
//...userInfo,
posts: [{
// ... an array of posts and for each post:
comments: [ /* an array of comments */ ]
}]
}
http://jsonplaceholder.typicode.com/users/${id}
http://jsonplaceholder.typicode.com/posts?userId=${userId}
http://jsonplaceholder.typicode.com/comments?postId=${postId}
const httpGet = url =>
new Task((reject, resolve) =>
fetch(url)
.then(res => res.json().then(resolve))
.catch(reject));
const getUser = id =>
httpGet(`http://jsonplaceholder.typicode.com/users/${id}`);
const getPosts = userId =>
httpGet(`http://jsonplaceholder.typicode.com/posts?userId=${userId}`);
const getComments = postId =>
httpGet(`http://jsonplaceholder.typicode.com/comments?postId=${postId}`);
const withComments = traverse(Task.of, converge(lift(merge), [
Task.of,
compose(
lift(compose(objOf('comments'), map(dissoc('postId')))),
getComments,
prop('id')
)
]));
const withPosts = useWith(merge, [
identity,
compose(objOf('posts'), map(dissoc('userId')))
]);
const getUserPosts = id =>
Task.of(withPosts)
.ap(getUser(id))
.ap(chain(withComments, getPosts(id)));
getUserPosts(1).fork(console.error, console.log);
propOr('', 'a')({
a: undefined
});
undefined
pathOr('', ['a'])({
a: undefined
});
''
or
for non-null. Not sure if undefined
would be classes as non-null
but they should be consistent
[ [foo: 123], [bar: 'abc'], [baz: true] ] // => { foo: 123, bar: 'abc', baz: true }
[ ['foo', 123], ['bar', 'abc'], ['baz', true] ] // => [ [ 'foo', 'bar', 'baz' ], [ 123, 'abc', true ] ]
transpose([ ['foo', 123], ['bar', 'abc'], ['baz', true] ] )
transpose([ ['foo', 123], ['bar', 'abc'], ['baz', true] ] )
[ [ 'foo', 'bar', 'baz' ], [ 123, 'abc', true ] ]
liftA2
?
unless
the best method for it?
I have data that is like this:
[
{key, value, collectionStrategy},
{key, value, collectionStrategy},
{key, value, collectionStrategy}
]
I need to merge all the objects into one, and the collectionStrategy
has been applied to the values. What the strategy represents is how to deal with key conflicts (OR LACK OF KEY CONFLICT!)
mergeWith
will only deal with conflicts, but I need to also do something where there aren't conflicts
collectionStrategy
has priority, two of the same keys might have different strategies, and one has to take precedence over the other
var data = [
{k: 'greetings', v: 'hi', s: 'alwaysArray'},
{k: 'number', v: 1, s: 'arrayWhenMore'},
{k: 'letters', v: ['a', 'b'], s: 'arrayWhenMore'},
{k: 'greetings', v: ['hey', 'sup'], s: 'alwaysArray'},
{k: 'greetings', v: 'yo', s: 'arrayWhenMore'},
{k: 'letters', v: 'c', s: 'arrayWhenMore'},
{k: 'foo', v: 'bar', s: 'alwaysArray'}
]
someFn(
[ {alwaysArray: identity}, {arrayWhenMore: (values, k) => values.length > 1 ? values : values[0]} ],
data
)
/* => {
greetings: [ 'hi', [ 'hey', 'sup' ], 'yo' ],
number: 1,
letters: [ [ 'a', 'b'], 'c' ],
foo: [ 'bar' ]
}*/
cond
or something in the fn
to check the s
keys and apply the correct operation
foo
would just be ”bar”
, but you want it wrapped in an array
compose(evolve({ /* ensure you ended up with the right type */ }), mergeWithKey(fn))
evolve({ foo: unless(isArrayLike, of) })
foo
could be any type?
someFn
seems to be a good, if not the perfect API
for example…knowing the tiny bit I know I would lean towards this strategy:
Before you get to this function, break up your input into actual types using something like daggy.taggedSum.
Then define concat
for the types (then you get .cata
to handle the various cases) and just use mergeWith(concat, input)
at this level
foo=a&foo=b&foo=c
foo[]=a&foo[]=b&foo[]=c
R.of
foo
is
foo=a
is a single value
foo[]=a
is always an array
You want to get to a point where you have a definition for every kind of data that you’ve parsed out.
And each one of those definitions has a single API for merging. Probably concat
(I think, still new to this).
So that it doesn’t matter if foo
is an array, a number, or a string. You call concat(x, y)
on it and it’ll do what you need.
[{key1: 'val1'},{key2: 'val2'},{key1:'oh, you again'}]
{key2: 'val2'}
or {key2: ['val2']}
{key2: ['val2']}
.length
?
{key2: 'val2'}
{key2: {isSingle: true, val: val2}}
reduce
function.
switch
or something.
{k, v, strategy}
const fn = id => getUser(id)
.chain(user => getPosts(user.id)
.chain(traverse(Task.of, post => getComments(post.id)
.map(comments => ({
...dissoc('userId', post),
comments: comments.map(dissoc('postId'))
}))
))
.map(posts => ({ ...user, posts })));
┌──────────────┐
│ QueryString │
└──────────────┘
┌─────────────────────────────────┐
│ │
│QueryStringPart = { String │
│ , Number │
│ , Array │
│ , Object │
│ , Bool │
│ } │
│ │
└─────────────────────────────────┘
┌─────────────────────────────────────────┐
│QueryStringPart.prototype.concat = b => {│
│ return this.cata({ │
│ String: <your merge strategy> │
│ Number: <your merge strategy> │
│ Array: <your merge strategy> │
│ ... │
│} │
│ │
└─────────────────────────────────────────┘
┌───────────────────────────────────────────────────┐
│ │
│ │
│reduce( │
│ (acc, o) => mergeWith(concat, acc, o), │
│ {}, │
│ queryStringParts │
│) │
│ │
│ │
│ │
└───────────────────────────────────────────────────┘
[]
or not
foo=a
and no more of them
foo=a&foo=b
definitely an array
foo[]=a
and no more of them - always an array
a
can actually be an array
foo[]=a&foo=b
foo[]=a&foo[1]=b
value[0]
it if length = 1
map
to get the value out
foo=a&foo=b
I think is the most common
parse('foo[]=a') // => { foo: 'a' }
parse('foo[]=a') // => { foo: ['a' ]}
map(map(
const fn = (id, data) => find(propEq('id', id), data);
const fnn = (id, data) => findIndex(propEq('id', id), data);
const findBoth = converge(Pair, [fn, fnn]);
const fn = useWith(find, [propEq('id'), identity]);
var data = [
{key: 'greetings', value: 'hi', meta: {array: true}},
{key: 'number', value: 1},
{key: 'letters', value: ['a', 'b']},
{key: 'greetings', value: ['hey', 'sup'], meta: {array: true}},
{key: 'greetings', value: 'yo'},
{key: 'letters', value: 'c'},
{key: 'foo', value: 'bar', meta: {array: true}},
{key: 'bar', value: [123]}
]
var result = {
greetings: [ 'hi', [ 'hey', 'sup' ], 'yo' ],
number: 1,
letters: [ [ 'a', 'b'], 'c' ],
foo: [ 'bar' ],
bar: [123]
}
var mergeAllWith = curry((fn, objects) => reduce(mergeWith(fn), {})(objects))
var wowCode = pipe(
groupBy(prop('key')),
map(pipe(
map(map(of)), // everything to arrays to prep for concat
mergeAllWith(concat), // all values in one array
({value, meta}) => {
if (value.length > 1 || contains({array: true}, meta || [])) { return value }
return value[0]
}
))
)
equals(wowCode(data), result) // => true at last, true at last, thank God, true at last!
map(pipe(
unnecessary? as in: doesn't map compose functions?
map(
map(map
?
map
map(...fns)
is compose
, not pipe
flip(map)
didn't fix it either, though
var compose = reduce(map, identity)
maybe?
No lists have been harmed in the application of this function.