-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Recursive values? #259
Comments
From what I can tell, it seems that other languages solve this with lazy values (see: http://stackoverflow.com/questions/8374010/scala-circular-references-in-immutable-data-types ). immutable could do something like this: function Lazy(aFunction)
{
this.get = memoize(aFunction);
} Then, in Map/List/etc's .get(): get = function(key)
{
value = /*current*/get(x);
if (value.constructor === Lazy)
return value.get();
return value;
} Now, we can do the following: var recursive = I.Map().set("x", Lazy(() => recursive));
console.log(recursive === recursive.get("x")) // prints true
//We can also use it to make doubly linked lists:
var node0 = I.Map({ prev: null, next: Lazy(() => node1) })
var node1 = I.Map({ prev: Lazy(() => node0), next: Lazy(() => node2) })
var node2 = I.Map({ prev: Lazy(() => node1), next: Lazy(() => node3) })
var node3 = I.Map({ prev: Lazy(() => node2), next: null }) |
There is no way (intentionally) for immutable persistent data structures to contain circular references. Immutable structures can contain circular references, but in order to be persistent and take advantage of structural sharing, they need to be acyclic. The stack-overflow you link to illustrates this well - showing how a double-linked list needs to make a complete copy in order to perform an append operation. There are some other problems that emerge from circular references in values, such as deep equality checking and hash-value computation. Languages based on immutable structures handle this in different ways: Scala has Clojure has For equality and hashing, Atoms are treated as Objects not as Values. So So you might treat this like: var node0 = I.Map({ prev: null, next: {} })
var node1 = I.Map({ prev: {value: node0}, next: {} })
node0.get('next').value = node1;
var node2 = I.Map({ prev: {value: node1}, next: {} })
node1.get('next').value = node2;
var node3 = I.Map({ prev: {value: node2}, next: null })
node2.get('next').value = node3; However, both approaches: lazy function or atom object are totally fine! This data structure library has no opinion on either, you just need to be aware of their use so you can treat them appropriately: // Lazy function
node3.get('prev').get();
// Atom object
node3.get('prev').value; |
For those looking for an implementation of Lazy evaluated variables, try this: function lazy(fn) {
var didRun;
var result;
return { get: () => didRun ? result : (didRun = true, result = fn()) };
} Usage example var lazyFoo = lazy(() => "foo");
console.log(lazyFoo.get()); |
I'm currently not sure baking this concept into the library, and changing the At this point I think it's reasonable to assume if you're using lazy values to determine for yourself when the lazy function should be evaluated. |
Yeah I suppose the hope is to have some sort of built in support for On Monday, December 22, 2014, Lee Byron [email protected] wrote:
|
I'll definitely considering adding true support for lazy values in the future, but I would prefer to see such a thing used outside the library first to better understand the implications before baking it in. If you end up patching Immutable to add this functionality, I would be really interested in hearing about the unexpected issues and opportunities you run into. |
So, interestingly enough, I was able to accomplish this using withMutations: https://gist.github.com/tolmasky/8a91af5ac1e0a949b423 Now the only issue is that toString borks and toJS bork (these are much easier to fix). Would you be opposed to having an implementation of toString and toJS that account for circular references (just like log you'd see something like Map { a: [Circular] }). I'm happy to just have them in my code of course, but given that this is "possible", I figured you might want it in mainline as well. |
I've filed this related issue which deals with the same reference in objects (not necessarily circular): #305 |
Circular references break a lot of promises that immutable data provides, so I don't think I plan on accounting for them. You can read a more lengthy explanation of the issues with circular references via manual mutation written up in the "transients" section of the clojure documentation. |
Closing this for now - I think this is going to be really hard to do without language features. |
I've been going through and trying to replace my mutable structures with immutable ones, the thing I've run into though is recursive structures. As a simple example:
How would I do something similar with I.Map? x.set("a", x) doesn't work because it stores the old one. (I'm converting a JS value, and as such fromJS doesn't work, but I can handle the conversion myself as long as I can figure out some way of getting a self reference to begin with)
The text was updated successfully, but these errors were encountered: