Yomguithereal / baobab

JavaScript & TypeScript persistent and optionally immutable data tree with cursors.
MIT License
3.15k stars 115 forks source link

Is "apply" working as intended when operating on lists/arrays? #475

Open Nimelrian opened 7 years ago

Nimelrian commented 7 years ago

Hi,

I'm confused by the result of calling apply on a cursor pointing at a list. Looking at the documentation and the function's name, to me it would seem that apply works as follows:

  1. If the cursor value is an object, pass that object to the apply-callback and replace the cursor's value with the result.
  2. If the cursor value is a list / an array, iterate over the list, supplying each element to the callback. Collect the callback results in a new list and replace the original list with the new one (similar to Array.prototype.map).

This, however, does not seem to be the case, as visible in this short example:

const Baobab = require('baobab');

const tree = new Baobab({
  list: [0, 1, 2, 3, 4],
  obj: {
    firstName: 'John',
    lastName: 'Doe',
  }
}, {asynchronous: false});

const haveAnIdentityCrisis = person => Object.assign({}, person, {
  lastName: 'Buck'
});

console.log(tree.get('obj')); // { firstName: 'John', lastName: 'Doe' }
tree.apply('obj', haveAnIdentityCrisis);
console.log(tree.get('obj')); // { firstName: 'John', lastName: 'Buck' }

console.log(tree.get('list')); // [ 0, 1, 2, 3, 4 ]
tree.apply('list', val => val + 1);
console.log(tree.get('list')); // 0,1,2,3,41

Apparently it makes no distinction between the cursor value being an object or an array. Is this intended? In this case it would make sense to clarify this in the documentation, especially since it uses a variable called "newList" in the example:

var inc = function(nb) {
  return nb + 1;
};

// Applying the function
var newList = cursor.apply(inc);
Yomguithereal commented 7 years ago

Hello @Nimelrian. The documentation is clearly misleading because the describe operation should probably return NaN indeed. The original intention was for apply to receive the value at the selected cursor to act upon, not to differentiate whether the value was an array or a scalar. But I admit the idea is interesting. However, I am not sure what this would mean if, say, the value at the cursor is a plain object.

Nimelrian commented 7 years ago

If the value at the cursor would be a plain object, the behaviour would stay the same as it is right now. It passes the current object to the callback and replaces the cursor's value with the result. Basically Array.prototype.map as if it were an array with a single object.

In code:

function apply (cursor, callback) {
  const currentValue = cursor.get();
  cursor.set(isArray(currentValue)
    ? currentValue.map(callback)
    : callback(currentValue));
}