Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

be able to create protected or immutable observables #9

Closed
mweststrate opened this issue Jul 29, 2015 · 7 comments
Closed

be able to create protected or immutable observables #9

mweststrate opened this issue Jul 29, 2015 · 7 comments

Comments

@mweststrate
Copy link
Member

Based on an idea by @mattmccray: https://gist.github.com/mattmccray/c5b6c69c1653b941ccd7

There are two main directions in which this can be implemented

  1. Protected: so that you cannot accidentally change it by doing an assignment
  2. Full immutable: be never able to change so that mobservable plays nicely with frameworks that expect immutables

When 'mutating' immutable items, observers should still be notified, otherwise data would not be observable anymore.

Protected could be combined with the existing .batch (which should be renamed to .transaction in that case?

@mweststrate
Copy link
Member Author

Added an experimental implementation, see the immutables branch.

Simple values
For immutable values you can do

var foo = mobservable("foo");
foo.observe((newValue) => console.log(newValue), true);
// prints 'foo'

var bar = foo.mutate('bar');
// bar shares observers with foo, so
// prints 'bar'
// but, still foo() === 'foo'
// and bar() === 'bar'

Calling the setter of either foo or bar throws an error. After the mutation, foo basically becomes history and cannot be mutated anymore, but bar can.

Objects
With objects stuff becomes a bit more hairy, you can now do the following

var coffee = mobservable.immutableProps({ coffee : 'please' });
mobservable.sideEffect(() => console.log(coffee.coffee));
// prints 'please'

var noCoffee = coffee.mutate({ coffee : 'no thanks' });
// prints 'please' (XXX!)

With the last mutation, the side effect was triggered, because the coffee property was changed, but prints still 'please', because the coffee object was accessed through the closure which has (correctly) the old value, while 'no thanks' only exists in noCoffee

Even doing coffee = coffee.mutate({ coffee: 'no thanks' }) doesn't fix that, because the sideEffect callback is executed before the coffee assignment (which is usually a great property of mobservable, but not here ;)).

A way to work around that might be to introduce a ES 7 based .observe method, as is already done for array. In that case the triggering object would be passed to the listener which would hold the correct value (although that wouldn't fix the side effect example directly, because you still have to detach the function from the closure variable, for example by putting the root in a plain mutable observable, but I guess that same problem exists with immutables and cursors right?)

So these are some first thoughts. Not sure whether this is a desirable direction, because it might be confusing for users. But maybe that isn't an issue at all. The protected options is feasible in any case, as that doesn't bring a second paradigm into the library.

@wmertens
Copy link

This would be really useful for undo/redo stacks etc!

with passing the triggering object, do you mean:

var coffee = mobservable.immutableProps({ coffee : 'please' });
coffee.sideEffect((coffee) => console.log(coffee.coffee));

@mweststrate
Copy link
Member Author

Yup, but the arguments cannot be actually passed into the sideEffect callback, since there could be potentially many objects that cause the function to be recomputed. But I think the following would be possible:

var coffee = mobservable.immutableProps({ coffee : 'please' });
var coffeeHolder = mobservable(coffee);
coffee.sideEffect(() => console.log(coffeeHolder.coffee.coffee));

// update the reference of the latest state by mutating the current state.
// Cofee remains immutable, while the sideEffect will still yield the correct result
coffeeHolder.set(coffeeHolder.get().mutate({ coffee: 'no thanks'}))

@mattmccray
Copy link

Interesting. On the protected side, would it allow me to do something along these lines?

import {protectedFromJson, transaction} from 'mobservable'
import API from 'some-fictional-module'

export let state = protectedFromJson({
  authenticated: false,
  authenticatedAt: null,
  currentUser: null,
  currentUserName() {
    return this.authenticated ? `${this.currentUser.firstName} ${this.currentUser.lastName}` : ''
  }
})

export function login( username, password ) {
  return API.doLogin( username, password )
    .then( user => {
      transaction(() => {
        state.authenticated = true
        state.authenticatedAt = new Date()
        state.currentUser = protectedFromJson( user )
      }) 
      return state.currentUser
      // currentUser couldn't be modified w/o a 'transaction' block
    })
}

export function logout() {
  transaction(() => {
    state.authenticated = false
    state.authenticatedAt = null
    state.currentUser = null
  })
}

// Example:
state.authenticated = true // Here, is a NOOP

@mweststrate
Copy link
Member Author

@mattmccray Yup exactly that.

I'm not sure whether the illegal assignment should fail silently or throw. ES5 properties fail silently if not writable, so probably that should be correct behavior, combined with some logging.

@mweststrate
Copy link
Member Author

Fun experiment as it is, I don't how immutable observables could lead to something viable with a comprehensible mental model, so i'll close it for now.

@mattmccray: Feel free to file transition protected observables as separate issue, those are very doable.

@mattmccray
Copy link

Fair enough.

Honestly, I'm not sure I'm really sold on true 'immutability' in JS anyway -- It's trying to force the language to do something it's not designed for. The only compelling argument I've seen has to do with object reference comparison, which is really more of a side effect of immutability than goal (abstractly speaking).

I do think protected observables could be useful -- But with proper developer discipline probably not needed. I may create a new issue to discuss.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants