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

Immutable all the way #3

Open
Gozala opened this issue Jan 31, 2015 · 5 comments
Open

Immutable all the way #3

Gozala opened this issue Jan 31, 2015 · 5 comments

Comments

@Gozala
Copy link

Gozala commented Jan 31, 2015

I don't see any note in the document whether those immutable data structures can contain mutable items in them or not. I would personally advocate against allowing that since:

  1. That would make a lot of otherwise possible optimization impossible.
  2. If it's immutable all the way there is a possibility of cheap transfer of them over workers (or why not even sharing).
@andersekdahl
Copy link

I think this is very important for this proposal because - like you said - making it immutable all the way enables transfer/sharing between threads, and that's a huge win. But also because allowing mutable data inside immutable data seems like a pretty big foot gun.

With immutable all the way, you can't put functions inside immutable structures (since functions are objects that are mutable). But it would be really cool if there were immutable functions as well, which when executed returns a promise for when it was really executed. That way, you could share it with another thread, and when the other thread calls it, the real function is added to the event loop of the original thread. Something like:

// thread A
let z = 1;

const map = #{
  x: 1, 
  y: 2,
  doStuff: ImmutableFunction(() => {
    z++;
    return z;
  });
};

shareWithThreadB(map);

// thread B

self.onShare = function (map) {
  console.log(map.get('x'));
  // Calling map.doStuff() adds to the event loop of thread A to actually invoke
  // the real function
  map.doStuff().then(z => console.log(z));
}

It would be necessary to make sure that arguments passed to an immutable function could only be immutable data, and that they could only return immutable data. Or that any mutable data passed/returned would be automatically copied to an immutable object.

If it's not immutable all the way, it raises some questions about value equality. If two immutable maps contain an object each, would comparing the immutable maps compare the objects inside them by reference equality or value equality? If by value equality, how would that work?

With immutable data all the way, you can be sure that value equality can be somewhat cheap since you can compare a hash of the data (the hash still has to be computed, of course), but if there's mutable data inside it you have to make a deep equality check.

@AsaAyers
Copy link

AsaAyers commented May 7, 2015

Associating mutable data with points in an immutable structure can be done with Symbols and WeakMaps. (I think Symbols are already immutable)

let asa = { name: "Asa", favoriteColor: "blue" }

const weak = new WeakMap();
const users = #{
  asa: Symbol('asa')
}
weak.set(users.asa, asa)

It might be slightly awkward that you have to pass the two around together

let asa = weak.get(users.asa)
asa.favoriteColor = "green"

weak.get(users.asa).favoriteColor // 'green'

@AsaAyers
Copy link

AsaAyers commented May 7, 2015

If immutable structures can hold mutable data then it seems like they would require deep comparison to really know if they are equal or not.

function memoize(fn) {
    const memo = new WeakMap()
    return function(object) {
        if (memo.has(object)) {
            // nothing has changed
            return memo.get(object)
        }
        const ret = fn(object);
        memo.set(object, ret);
        return ret;
    }
}

const doubleFooValue = memoize(function(object) {
    return object.foo.value * 2;
});

const immutable = #{ foo: { value: 2} }
doubleFooValue(immutable) // 4
immutable.foo.value = 11
doubleFooValue(immutable) // wrong result: 4

edit: if you remove the # when defining immutable that code runs as-is in Chrome and gives the exact same results.

@andersekdahl
Copy link

@AsaAyers Symbols themselves are immutable in that you cannot assign new properties on them like:

var mySymbol = Symbol('test');
mySymbol.x = 'z';
console.log(mySymbol.x); // logs 'undefined'

But all symbols share the same prototype, and that's not an immutable object. So you can still do:

Symbol.prototype.data = {x: 'z'};
var mySymbol = Symbol('test');
console.log(mySymbol.data.x); // logs 'z'
mySymbol.data.x = 'y';
console.log(Symbol.prototype.data.x); // logs 'y'

Not sure if that's an issue for transferability/shareability though, since the global prototypes on different threads would be different in any case, and sending a Symbol over threads would hopefully give that Symbol that threads global Symbol prototype. But I can imagine that this has some issues.

@AsaAyers
Copy link

AsaAyers commented May 8, 2015

2ality has some great info on Symbols and it mentions passing Symbols across realms (I think realms === threads for this purpose).

I also decided to try some experiments in the console with that page open. The things I found unexpected were that while Symbol.prototype exists, the instances don't have a prototype. They also all inherit from the local Symbol.prototype even if it originated in another realm.

local = Symbol('hello')
// Symbol(hello)
remote = frames[0].Symbol('hello')
// Symbol(hello)
Object.getPrototypeOf(local)
// VM1181:2 Uncaught TypeError: Object.getPrototypeOf called on non-object
//     at Function.getPrototypeOf (native)
//     at <anonymous>:2:8
//     at Object.InjectedScript._evaluateOn (<anonymous>:895:140)
//     at Object.InjectedScript._evaluateAndWrap (<anonymous>:828:34)
//     at Object.InjectedScript.evaluate (<anonymous>:694:21)(anonymous function) @ VM1181:2InjectedScript._evaluateOn @ VM149:895InjectedScript._evaluateAndWrap @ VM149:828InjectedScript.evaluate @ VM149:694
Object.getPrototypeOf(remote)
// VM1182:2 Uncaught TypeError: Object.getPrototypeOf called on non-object
//     at Function.getPrototypeOf (native)
//     at <anonymous>:2:8
//     at Object.InjectedScript._evaluateOn (<anonymous>:895:140)
//     at Object.InjectedScript._evaluateAndWrap (<anonymous>:828:34)
//     at Object.InjectedScript.evaluate (<anonymous>:694:21)(anonymous function) @ VM1182:2InjectedScript._evaluateOn @ VM149:895InjectedScript._evaluateAndWrap @ VM149:828InjectedScript.evaluate @ VM149:694
Symbol.prototype.hello = "Hello World"
// "Hello World"
local.hello
// "Hello World"
remote.hello
// "Hello World"
local.prototype
// undefined
remote.prototype
// undefined

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

No branches or pull requests

3 participants