RxJS: Asynchronously mutate tree - javascript

I have a sequence of objects that I need to asynchronously modify by adding a property to each object:
[{ id: 1 }, { id: 2 }] => [{ id: 1, foo: 'bar' }, { id: 2, foo: 'bar' }]
The synchronous equivalent of this would be:
var xs = [{ id: 1 }, { id: 2 }];
// Warning: mutation!
xs.forEach(function (x) {
x.foo = 'bar';
});
var newXs = xs;
However, in my case I need to append the foo property asynchronously. I would like the end value to be a sequence of objects with the foo property added.
I came up with the following code to solve this problem. In this example I'm just adding a property to each object with a value of bar.
var xs = Rx.Observable.fromArray([{ id: 1 }, { id: 2 }]);
var propertyValues = xs
// Warning: mutation!
.flatMap(function (x) {
return Rx.Observable.return('bar');
});
var newXs =
.zip(propertyValues, function (x, propertyValue) {
// Append the property here
x.foo = propertyValue;
return x;
})
.toArray();
newXs.subscribe(function (y) { console.log(y); });
Is this the best way to solve my problem, or does Rx provide a better means for asynchronously mutating objects in a sequence? I'm looking for a cleaner solution because I have a deep tree that I need to mutate, and this code quickly becomes unweidly:
var xs = Rx.Observable.fromArray([{ id: 1, blocks: [ {} ] }, { id: 2, blocks: [ {} ] } ]);
var propertyValues = xs
// Warning: mutation!
.flatMap(function (x) {
return Rx.Observable.fromArray(x.blocks)
.flatMap(function (block) {
var blockS = Rx.Observable.return(block);
var propertyValues = blockS.flatMap(function (block) {
return Rx.Observable.return('bar');
});
return blockS.zip(propertyValues, function (block, propertyValue) {
block.foo = propertyValue;
return block;
});
})
.toArray();
});
xs
.zip(propertyValues, function (x, propertyValue) {
// Rewrite the property here
x.blocks = propertyValue;
return x;
})
.toArray()
.subscribe(function (newXs) { console.log(newXs); });
Perhaps I shouldn't be performing this mutation in the first place?

Is there a reason you need to create two separate Observables: one for the list you're updating and one for the resulting value?
If you simply perform a .map() over your original list, you should be able to asynchronously update the list and subscribe to the result:
// This is the function that generates the new property value
function getBlocks(x) { ... }
const updatedList$ = Rx.Observable.fromArray(originalList)
// What we're essentially doing here is scheduling work
// to be completed for each item
.map(x => Object.assign({}, x, { blocks: getBlocks(x)}))
.toArray();
// Finally we can wait for our updatedList$ observable to emit
// the modified list
updatedList$.subscribe(list => console.log(list));
To abstract this functionality, I created a helper function that will explicitly schedule work to occur for each item using setTimeout:
function asyncMap(xs, fn) {
return Rx.Observable.fromArray(xs)
.flatMap(x => {
return new Rx.Observable.create(observer => {
setTimeout(() => {
observer.onNext(fn(x));
observer.completed();
}, 0);
});
})
.toArray();
}
You can use this function to schedule work to be completed for each item:
function updateItem(x) {
return Object.assign({}, x, { blocks: getBlocks(x) }
}
var updatedList$ = asyncMap(originalList, updateItem);
updateList$.subscribe(newList => console.log(newList));

Related

Different logic if both or only one key present in object?

I have this kind of schema:
const schema = {
actions: {
ident: {
action: (v) => v,
path: 'some.path.key',
},
mul: {
action: (v) => v * 2,
path: 'some.other.path.key',
},
},
};
And a helper function, that takes object with keys present in schema actions, e.g:
const obj = {
ident: 1,
mul: 2,
}
const res = helper(schema, obj);
/* res */
{
some: {
path: {
key: 1,
},
other: {
path: {
key: 4,
}
}
},
}
And construct a new object with a function applied to the value.
Sometimes i need a behavior when both keys present in source object, e.g:
const schema2 = {
actions: {
ident: {
action: (v) => v,
path: 'some.path.key',
},
mul: {
action: (v) => v * 2,
path: 'some.other.path.key',
},
'mul:ident': {
action: (v1, v2) => v1/v2,
path: 'key',
}
},
};
In case like this, i need the result object to be:
const obj = {
ident: 1,
mul: 2,
}
const res = helper(schema, obj);
/* res */
{
key: 2 // 2/1 == 2
}
How can i implement such conditional logic in a good way?
I'd traverse your actions backwards, then delete the keys from the input, and skip the action if the keys are missing:
const input = { ...obj };
const output = {};
// you might want to sort the keys in the desired order first (e.g. by the number of parameters)
for(const [key, { action, path }] of Object.entries(schema.actions).reverse()) {
const keys = key.split(".");
// If one ov the values is missing, another action already consumed it
if(keys.some(key => !(key in input))
continue;
// consume all keys into values
const values = keys.map(key => {
const value = input[key];
delete input[key];
});
// TODO: assign path to output correctly
output[path] = action(...values);
}

Javascript - Building conditions dynamically

I created a general function called unique() to remove duplicates from a specific array.
However I'm facing a problem: I want to build the conditions dynamically based on properties that I pass to the function.
Ex: Let's suppose that I want to pass 2 properties, so I want to check these 2 properties before "remove" that duplicated object.
Currently I'm using eval() to build this condition "&&", however according to my search it's really a bad practice.
So, my question is:
What's the proper way to do this kind of thing?
Below is my current code:
function unique(arr, ...props) {
const conditions = [];
for (let prop of props) {
conditions.push(`element['${prop}'] === elem['${prop}']`);
}
const condStr = conditions.join(' && ');
return arr.filter((element, index) => {
const idx = arr.findIndex((elem) => {
return eval(condStr);
});
return idx === index;
});
}
const arr1 = [{
id: 1,
name: 'Josh',
description: 'A description'
}, {
id: 2,
name: 'Hannah',
description: 'A description#2'
}, {
id: 1,
name: 'Josh',
description: 'A description#3'
}, {
id: 5,
name: 'Anyname',
description: 'A description#4'
}];
const uniqueValues = unique(arr1, 'id', 'name');
console.log('uniqueValues', uniqueValues);
This question is a bit subjective as far as implementation details, but the better way if you ask me is to pass in a callback function to hand over to filter.
In doing it this way, you can compose the function anyway you see fit. If you have a complex set of conditions you can use composition to build the conditions in the function before you pass it into your unique function https://hackernoon.com/javascript-functional-composition-for-every-day-use-22421ef65a10
A key to function composition is having functions that are composable. A composable function should have 1 input argument and 1 output value.
The hackernoon article is pretty good and goes much further in depth.
this will return a single function that applies all of your preconditions
function unique(arr, callback) {
return arr.filter(callback);
}
const compose = (...functions) => data =>
functions.reduceRight((value, func) => func(value), data)
unique(
[1, 3, 4, 5 ,7, 11, 19teen]
compose(
(someStateCondition) => { /** return true or false **/ },
(result) => { /** return result === someOtherStateCondition **/}
)
)
Use Array#every to compare all properties inline:
function unique(arr, ...props) {
return arr.filter((element, index) => {
const idx = arr.findIndex(
elem => props.every(prop => element[prop] === elem[prop]);
);
return idx === index;
});
}

ES6 pattern match in Switch

Consider the following code snippet:
if (msg.operation == 'create') {
model.blocks.push(msg.block)
drawBlock(msg.block);
} else if (msg.operation == 'select' && msg.properties.snap == 'arbitrary') {
doStuff(msg.properties.x, msg.properties.y);
} else if (msg.operation == 'unselect') {
doOtherStuff(msg.properties.geometry);
}
Is there a way to refactor this so I can pattern match on msg, akin to the following invalid code:
msg match {
case { operation: 'create', block: b } =>
model.blocks.push(b);
drawBlock(b);
case { operation: 'select', properties: { snap: 'arbitrary', x: sx, y: sy } } =>
doStuff(sx, sy);
case { operation: 'unselect', properties: { snap: 'specific' }, geometry: geom } =>
doOtherStuff(geom);
}
Alternatively, what would be the most idiomatic way of achieving this in ES6, without the ugly if-then-else chain?
Update. Granted that this is a simplistic example where a full-blown pattern matching is probably unneeded. But one can imagine a scenario of matching arbitrary hierarchical pieces of a long AST.
TL;DR; the power of destructuring, accompanied with an automatic check if it is possible to do it or not.
You could write a match function like this, which (when combined with arrow functions and object destructuring) is fairly similar to the syntax your example:
/**
* Called as:
* match(object,
* pattern1, callback1,
* pattern2, callback2,
* ...
* );
**/
function match(object, ...args) {
for(let i = 0; i + 1 < args.length; i += 2) {
const pattern = args[i];
const callback = args[i+1];
// this line only works when pattern and object both are JS objects
// you may want to replace it with a more comprehensive check for
// all types (objects, arrays, strings, null/undefined etc.)
const isEqual = Object.keys(pattern)
.every((key) => object[key] === pattern[key]);
if(isEqual)
return callback(object);
}
}
// -------- //
const msg = { operation: 'create', block: 17 };
match(msg,
{ operation: 'create' }, ({ block: b }) => {
console.log('create', b);
},
{ operation: 'select-block' }, ({ id: id }) => {
console.log('select-block', id);
},
{ operation: 'unselect-block' }, ({ id: id }) => {
console.log('unselect-block', id);
}
);
You can use a higher order function and destructuring assignment to get something remotely similar to pattern matching:
const _switch = f => x => f(x);
const operationSwitch = _switch(({operation, properties: {snap, x, y, geometry}}) => {
switch (operation) {
case "create": {
let x = true;
return operation;
}
case "select": {
let x = true;
if (snap === "arbitrary") {
return operation + " " + snap;
}
break;
}
case "unselect": {
let x = true;
return operation;
}
}
});
const msg = {operation: "select", properties: {snap: "arbitrary", x: 1, y: 2, geometry: "foo"}};
console.log(
operationSwitch(msg) // select arbitrary
);
By putting the switch statement in a function we transformed it to a lazy evaluated and reusable switch expression.
_switch comes from functional programming and is usually called apply or A. Please note that I wrapped each case into brackets, so that each code branch has its own scope along with its own optional let/const bindings.
If you want to pass _switch more than one argument, just use const _switchn = f => (...args) => f(args) and adapt the destructuring to [{operation, properties: {snap, x, y, geometry}}].
However, without pattern matching as part of the language you lose many of the nice features:
if you change the type of msg, there are no automatic checks and _switch may silently stop working
there are no automatic checks if you covering all cases
there are no checks on tag name typos
The decisive question is whether it is worth the effort to introduce a technique that is somehow alien to Javascript.
I think #gunn's answer is onto something good here, but the primary issue I have with his code is that it relies upon a side-effecting function in order to produce a result – his match function does not have a useful return value.
For the sake of keeping things pure, I will implement match in a way that returns a value. In addition, I will also force you to include an else branch, just the way the ternary operator (?:) does - matching without an else is reckless and should be avoided.
Caveat: this does not work for matching on nested data structures but support could be added
// match.js
// only export the match function
const matchKeys = x => y =>
Object.keys(x).every(k => x[k] === y[k])
const matchResult = x => ({
case: () => matchResult(x),
else: () => x
})
const match = x => ({
case: (pattern, f) =>
matchKeys (pattern) (x) ? matchResult(f(x)) : match(x),
else: f => f(x)
})
// demonstration
const myfunc = msg => match(msg)
.case({operation: 'create'}, ({block}) => ['create', block])
.case({operation: 'select-block'}, ({id}) => ['select-block', id])
.case({operation: 'unselect-block'}, ({id}) => ['unselect-block', id])
.else( (msg) => ['unmatched-operation', msg])
const messages = [
{operation: 'create', block: 1, id: 2},
{operation: 'select-block', block: 1, id: 2},
{operation: 'unselect-block', block: 1, id: 2},
{operation: 'other', block: 1, id: 2}
]
for (let m of messages)
// myfunc returns an actual value now
console.log(myfunc(m))
// [ 'create', 1 ]
// [ 'select-block', 2 ]
// [ 'unselect-block', 2 ]
// [ 'unmatched-operation', { operation: 'other', block: 1, id: 2 } ]
not quite pattern matching
Now actual pattern matching would allow us to destructure and match in the same expression – due to limitations of JavaScript, we have to match in one expression and destructure in another. Of course this only works on natives that can be destructured like {} and [] – if a custom data type was used, we'd have to dramatically rework this function and a lot of conveniences would be lost.
Sure, why not?
function match(object) {
this.case = (conditions, fn)=> {
const doesMatch = Object.keys(conditions)
.every(k=> conditions[k]==object[k])
if (doesMatch) fn(object)
return this
}
return this
}
// Example of use:
const msg = {operation: 'create', block: 5}
match(msg)
.case({ operation: 'create'}, ({block})=> console.log('create', block))
.case({ operation: 'select-block'}, ({id})=> console.log('select-block', id))
.case({ operation: 'unselect-block'}, ({id})=> console.log('unselect-block', id))
Given there's no easy way to properly do this until TC39 implemented switch pattern matching comes along, the best bet is libraries for now.
loadash
Go ol' loadash has a nice _.cond function:
var func = _.cond([
[_.matches({ 'a': 1 }), _.constant('matches A')],
[_.conforms({ 'b': _.isNumber }), _.constant('matches B')],
[_.stubTrue, _.constant('no match')]
]);
func({ 'a': 1, 'b': 2 });
// => 'matches A'
func({ 'a': 0, 'b': 1 });
// => 'matches B'
func({ 'a': '1', 'b': '2' });
// => 'no match'
patcom
One of the recommended libraries to look at, which has feature parity with the TC39 proposal for switch pattern matching, patcom, is quite small and nicely written - this is the main index.js:
import { oneOf } from './matchers/index.js'
export * from './matchers/index.js'
export * from './mappers.js'
export const match =
(value) =>
(...matchers) => {
const result = oneOf(...matchers)(value)
return result.value
}
Here's the simple example:
import {match, when, otherwise, defined} from 'patcom'
function greet(person) {
return match (person) (
when (
{ role: 'student' },
() => 'Hello fellow student.'
),
when (
{ role: 'teacher', surname: defined },
({ surname }) => `Good morning ${surname} sensei.`
),
otherwise (
() => 'STRANGER DANGER'
)
)
}
So for yours something like this should work:
match (msg) (
when ({ operation: 'create' }), ({ block: b }) => {
model.blocks.push(b);
drawBlock(b);
}),
when ({ operation: 'select', properties: { snap: 'arbitrary' } }), ({ properties: { x: sx, y: sy }}) =>
doStuff(sx, sy)
)
when ({ operation: 'unselect', properties: { snap: 'specific' } }, ({ geometry: geom }) =>
doOtherStuff(geom)
)
)
match-iz
For people wanting to implement the whole thing themselves there is a recommended small library match-iz that implements functional pattern matching in currently 194 lines.
Supercharged switch
I'm wondering if something like this 'supercharged switch' might get close to what your after:
const match = (msg) => {
const { operation, properties: { snap } } = msg;
switch (true) {
case operation === 'create':
model.blocks.push(b);
drawBlock(b);
break;
case operation === 'select' && snap === 'arbitrary':
const { properties: { x: sx, y: sy }} = msg;
doStuff(sx, sy);
break;
case operation === 'unselect' && snap === 'specific':
const { geometry: geom } = msg;
doOtherStuff(geom)
break;
}
}
Reducers
Also the whole concept of matching on strings within objects and then running a function based on that sounds a lot like Redux reducers.
From an earlier answer of mine about reducers:
const operationReducer = function(state, action) {
const { operation, ...rest } = action
switch (operation) {
case 'create':
const { block: b } = rest
return createFunc(state, b);
case 'select':
case 'unselect':
return snapReducer(state, rest);
default:
return state;
}
};
const snapReducer = function(state, action) {
const { properties: { snap } } = action
switch (snap) {
case 'arbitrary':
const { properties: { x: sx, y: sy } } = rest
return doStuff(state, sx, sy);
case 'specific':
const { geometry: geom } = rest
return doOtherStuff(state, geom);
default:
return state;
}
};

Using .map affecting the whole object (making it undefined) rather than updating property

When I do:
csvParse(txtString, {columns: true})
I get an array of objects, within this I want to update a certain property for each object.
But all that's happening is the object is becoming undefined.
This works exactly as I would expect it:
let x = [{
y: 123,
z: 'abc'
}, {
y: 456,
z: 'efg'
}, {
y: 789,
z: 'hij'
}];
console.log(x);
x.map(x => {
x.z.toUpperCase();
});
console.log(x); // no changes saved
x.map(x => {
x.z = x.z.toUpperCase();
});
console.log(x); //now z is uppercase
However when I do the same in my code:
resolve(
csvParse(txtString, {
columns: true
})
.map(x => {
x.categories = x.categories
.replace(regexes.doubleQuotesOrSquareBrackets, '')
.split(',');
})
);
Every element in the results array becomes undefined.
The way ES6 functions work, is that it returns computed value when there is a single statement, if there are multiple statements then you need to explicitly use return
In your case, your single statement is an assignment operation.
So it technically returns undefined
.map(x => {
x.categories = ...
});
is nothing but
.map(x => {
x.categories = ...
return undefined;
});
To fix it you can either do
.map(x => {
x.categories = ...;
return x.categories;
});
OR
.map(x => {
...;
return x;
});

How do I swap array elements in an immutable fashion within a Redux reducer?

The relevant Redux state consists of an array of objects representing layers.
Example:
let state = [
{ id: 1 }, { id: 2 }, { id: 3 }
]
I have a Redux action called moveLayerIndex:
actions.js
export const moveLayerIndex = (id, destinationIndex) => ({
type: MOVE_LAYER_INDEX,
id,
destinationIndex
})
I would like the reducer to handle the action by swapping the position of the elements in the array.
reducers/layers.js
const layers = (state=[], action) => {
switch(action.type) {
case 'MOVE_LAYER_INDEX':
/* What should I put here to make the below test pass */
default:
return state
}
}
The test verifies that a the Redux reducer swaps an array's elements in immutable fashion.
Deep-freeze is used to check the initial state is not mutated in any way.
How do I make this test pass?
test/reducers/index.js
import { expect } from 'chai'
import deepFreeze from'deep-freeze'
const id=1
const destinationIndex=1
it('move position of layer', () => {
const action = actions.moveLayerIndex(id, destinationIndex)
const initialState = [
{
id: 1
},
{
id: 2
},
{
id: 3
}
]
const expectedState = [
{
id: 2
},
{
id: 1
},
{
id: 3
}
]
deepFreeze(initialState)
expect(layers(initialState, action)).to.eql(expectedState)
})
One of the key ideas of immutable updates is that while you should never directly modify the original items, it's okay to make a copy and mutate the copy before returning it.
With that in mind, this function should do what you want:
function immutablySwapItems(items, firstIndex, secondIndex) {
// Constant reference - we can still modify the array itself
const results= items.slice();
const firstItem = items[firstIndex];
results[firstIndex] = items[secondIndex];
results[secondIndex] = firstItem;
return results;
}
I wrote a section for the Redux docs called Structuring Reducers - Immutable Update Patterns which gives examples of some related ways to update data.
You could use map function to make a swap:
function immutablySwapItems(items, firstIndex, secondIndex) {
return items.map(function(element, index) {
if (index === firstIndex) return items[secondIndex];
else if (index === secondIndex) return items[firstIndex];
else return element;
}
}
In ES2015 style:
const immutablySwapItems = (items, firstIndex, secondIndex) =>
items.map(
(element, index) =>
index === firstIndex
? items[secondIndex]
: index === secondIndex
? items[firstIndex]
: element
)
There is nothing wrong with the other two answers, but I think there is even a simpler way to do it with ES6.
const state = [{
id: 1
}, {
id: 2
}, {
id: 3
}];
const immutableSwap = (items, firstIndex, secondIndex) => {
const result = [...items];
[result[firstIndex], result[secondIndex]] = [result[secondIndex], result[firstIndex]];
return result;
}
const swapped = immutableSwap(state, 2, 0);
console.log("Swapped:", swapped);
console.log("Original:", state);

Categories