Yomguithereal / baobab

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

Question on Baobab's immutability #429

Open dumconstantin opened 8 years ago

dumconstantin commented 8 years ago

I really must be missing something, maybe someone can clue me in.

Here is what I mean:

var a = new Baobab({ b: 1 })
var c = a.set('b', 2)
a.get('b') // I expected 1 but got 2!
c // I expected a reference to the new tree but was surprised to see 2

Am I wrong to think that Baobab should work like ImmutableJS in terms of preserving the tree on mutations on just returning an updated version?

ImmutableJS doesn't have by default the excellent cursors and event system on data change like Baobab does and that's why I turned to using Baobab - but was a bit surprised that data mutated on every update.

Yomguithereal commented 8 years ago

Hello @dumconstantin. Baobab is meant to be used as store for an application state. It therefore works a bit differently than ImmutableJS. That is to say, the data stored by the tree is immutable (try setting an object and mutate this object and you'll see that's not possible). However, the tree has a internal reference to the data it is storing for you, and each time you write something to the tree, this internal reference is swapped to be replaced by the new version of the data.

Yomguithereal commented 8 years ago

You can think of Baobab as working like a Clojure atom in fact.

I was thinking lately of creating a spin off or else of the lib that would work as a plain data structure you could use like ImmutableJS but I need some time to reflect on it.

dumconstantin commented 8 years ago

Hey @Yomguithereal, thank you for the answer! I think I got the gist now - initial data is deeply cloned and the immutability does not transpire across the API

The ImmutableJS approach is a bit easier to reason about when you want to push state around (like with React components). So the spin off might be well received.

So as a twist (I'm using RiotJS with Redux and trying to simulate Om Next/Relay ideas on listening to data change instead of passing stores around), would something like the following work?

I'm just a bit worried that the Baobab instance might get somehow compromised if I don't isolate it well enough...

Yomguithereal commented 8 years ago

I think I got the gist now - initial data is deeply cloned and the immutability does not transpire across the API

Initial data is not deeply cloned. It is deeply frozen.

You can think of the tree as a Redux store in fact.

keeping the Baobab instance isolated

Yes you should

only dispatched actions can mutate the Baobab instance

Yes, only actions should be allowed to mutate the tree.

components use fake cursors (which trigger internally the cursors on the Baobab instance) to subscribe to the Baobab's updates

You should check baobab-react

components use fake cursors to get a raw objects from Baobab

What do you mean by fake cursors?

I'm just a bit worried that the Baobab instance might get somehow compromised if I don't isolate it well enough...

How could it get compromised exactly?

dumconstantin commented 8 years ago

Thanks for the correction just saw the deepFreeze helper.

Nice, thanks confirming some ideas for the baobab-react link, yup that's kind what I had in mind with cursors.

For the "fake cursor" label, actually mean is "proxy cursors" so that you use without having the store instance

Kinda like:

import cursor from './store.js'
var name = cursor(['user', 'name'])
name.on('update', () => console.log(name.get())

For compromising, I can't ever export the Baobab instance and make sure when passing the instance to the reducers they don't store it locally or export a direct reference to it. It also mean that the main reducer and the cursor proxy code needs to stay in the same file as the Baobab instance.

couchand commented 8 years ago

Drive by comment here. My understanding is that it is inaccurate to describe a Baobab tree as immutable, even if you've frozen the underlying data. @dumconstantin's original example exactly illustrates an expected property of immutable data structures: "If an object is known to be immutable, it can be copied simply by making a copy of a reference to it instead of copying the entire object." from Wikipedia (article linked from the README). The end of that section gives the proper name for the pattern used by Baobab: Observer.

Yomguithereal commented 8 years ago

"If an object is known to be immutable, it can be copied simply by making a copy of a reference to it instead of copying the entire object."

Then immutability is impossible in JavaScript and even the immutable-js and mori libraries cannot achieve this goal.

@couchand, I agree that you can argue that the Baobab tree itself is not immutable. But the underlying data is immutable (or at least "not mutable" if you find the term erroneous) and persistent.

The end of that section gives the proper name for the pattern used by Baobab: Observer.

Yes, Baobab implements the Observer pattern but not only because you can very well compare two different versions of the underlying data by reference only.

couchand commented 8 years ago

Immutability is all in the eye of the beholder. The underlying data is stored on very mutable RAM, so we know that fundamentally everything is mutable, it's about the API that's exposed to the user.

Consider this snippet, the first from the ImmutableJS README:

var Immutable = require('immutable');
var map1 = Immutable.Map({a:1, b:2, c:3});
var map2 = map1.set('b', 50);
map1.get('b'); // 2
map2.get('b'); // 50

Or this one, the second example from the mori README:

var v1 = mori.vector(1,2,3);
var v2 = mori.conj(v1, 4);
v1.toString(); // => '[1 2 3]'
v2.toString(); // => '[1 2 3 4]'

The fact that these examples are so primary to the explanation of these two libraries should be a sign that they're following the expected pattern for immutable data structures. That Baobab would be described as immutable and persistent, despite that fact that the API plainly isn't, seems like a case of buzzword-compliance.

Let's not water down the meaning of the terms.

Yomguithereal commented 8 years ago

I see your point. You are right.

So would a slight rewrite of the docs & the description of the library explaining this point more clearly by correctly explaining that the underlying data is immutable but not the actual object and API (which is more like an atom or Observer) be a satisfactory solution?

couchand commented 8 years ago

That sounds right, I think the only thing that need change is the docs. Thanks!

dumconstantin commented 8 years ago

:+1: +1