-
Notifications
You must be signed in to change notification settings - Fork 62
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
Behavior of Box(Box(x))
#238
Comments
It should be allowed to create a Assume that you can't create a
The former case becomes impossible because you can't put Boxes in Boxes anymore. So that leaves you with the more difficult option. But in that case what do you do when you receive Boxes:
This means that you'll have to wrap the So it is very important to keep |
I agree with @sjrd: when you box something, |
I know you already know this @nicolo-ribaudo, but for completeness. That is not true for all x, While allowing any value in a Box is more ergenomic for code creating boxes for generic values. Complexity has been shifted to consumers of boxes. Consumers of boxes can't rely on Boxes always containing objects. For example, always being able to take a value from a box and store it in a WeakSet. |
Yes, sometimes they do. And sometimes the additional guarantees at one point do not outweigh the cost of constraints at another point.
Do you have any realistic example of that scenario? WeakSets restrict their elements to being Objects for a very good, objective reason: they cannot provide their functionality for primitives because primitives do not have an identity. The constraint here is necessary to provide the essential functionality of WeakSet. The same cannot be said about |
The problem is that The question at the core is: what does a box represent? We'll all agree that the motivation for box is to allow mutable data inside immutable structures. So as with most problems in computer science, we solve this by introducing a level of indirection. The main point to realize is that a purely immutable structure is semantically different from an immutable structure with mutable objects contained within it. The former is identity-less and forgeable, it can be reconstructed by the program. The latter is unforgeable without access to the mutable objects, and thus combines the identity of the objects it contains. Back to boxes, on its own, a idea of a container that creates one level of indirection could be applied to any value, but when applied to the problem at hand, box has a specific purpose: a level of indirection to allow mutable objects with identity in immutable structure. It also conveys a meaning: if a record/tuple has a box, it carries identity. When a program handles a record or tuple, it may need to know what kind it is: forgeable or carrying identity. That program needs a way to discriminate between the 2. Asking a question "does this R/T directly contain a box" is a much simpler to reason about than "does this record/tuple recursively contains an object". As for specific cases where this matters:
What I'd like to see is an example of program which is made a lot more complex by not allowing boxing primitives. |
@mhofman I would like to give a counter argument. There are various functional languages that have a split between mutable / immutable data, and they handle mutable data with something similar to
All of these languages allow for nested references (i.e. Speaking more broadly, having a pointer which points to another pointer is a very common technique throughout C, C++, and Rust. There is nothing wrong with nested indirection, it is useful.
That sort of algorithm already needs to be recursive (because tuples / records can be infinitely nested), so I don't see how it is an extra burden to recurse into the
Membranes already need to special-case primitives (because they can't be stored in a I also think that the
I don't think that
All generic code will become more complex. All code which relies on a specific nesting depth becomes more complex. Even something as simple as I think that's far worse than making |
There is already an example here: #206 (comment) With implicit boxes (ideal): class SnapshottableStack {
constructor() {
this._stack = null;
}
push(x) {
this._stack = #[x, this._stack]; // x directly in the tuple
}
pop() {
if (this._stack === null)
throw new Error("empty stack");
const result = this._stack[0]; // directly get the user value from the tuple
this._stack = this._stack[1];
return result;
}
snapshot() {
return #{snapshot: this._stack};
}
restore(snapshot) {
this._stack = snapshot.snapshot;
}
} With explicit Boxes, but at least primitives allowed (not ideal, but generic code can actually stay generic, if not optimal from a performance point of view): class SnapshottableStack {
...
push(x) {
this._stack = #[Box(x), this._stack]; // Box in case it's an object
}
pop() {
if (this._stack === null)
throw new Error("empty stack");
const result = this._stack[0].unbox(); // unbox back for the user value in all cases
this._stack = this._stack[1];
return result;
}
...
} When not allowing primitives (it becomes being quite bad, and this code is wrong, see below): class SnapshottableStack {
...
push(x) {
// Box *only* if it is an object
const box = typeof x === "object" || typeof x === "function" ? Box(x) : x;
this._stack = #[Box(x), this._stack];
}
pop() {
if (this._stack === null)
throw new Error("empty stack");
const resultBox = this._stack[0];
this._stack = this._stack[1];
// unbox in case it is a Box, to get back the user value
const result = typeof resultBox === "box" ? resultBox.unbox() : resultBox;
return result;
}
...
} But then the user of the above cannot use Boxes in that data structures themselves. So if you want parity, you have to make it so complicated that, at the end of the day, you've got to use an Object inside your Box to keep being completely generic: class SnapshottableStack {
...
push(x) {
this._stack = #[Box({v: x}), this._stack]; // Box an Object with x inside
}
pop() {
if (this._stack === null)
throw new Error("empty stack");
const result = this._stack[0].unbox().v; // unbox and extract the user value
this._stack = this._stack[1];
return result;
}
...
} which means you are actually completely surrendering the benefits of the immutable tuples. And then, seriously, what's the point of the whole thing? |
@Pauan Those let x = ref 1;
let y = ref 1;
let eq = x == y; (* false *)
let () = x := 3; (* refs are mutable *) let x = Box(1);
let y = Box(1);
let eq = x == y; // true
// cannot change the content of a box They are more like plain JS objects: let x = { ref: 1 };
let y = { ref: 1 };
let eq = x == y; // false
x.ref = 3; Similarly, |
@sjrd function toImmutable(value) {
const isObject = Object(value) === value;
return #{ isObject, value: isObject ? Box(value) : value };
}
function fromImmutable({ isObject, value }) {
return isObject ? Box.unbox(value) : value;
}
class SnapshottableStack {
...
push(x) {
this._stack = #[toImmutable(x), this._stack];
}
pop() {
if (this._stack === null)
throw new Error("empty stack");
const result = fromImmutable(this._stack[0]);
this._stack = this._stack[1];
return result;
}
...
} Btw, being able to rewrite this stack example that you posted a few months ago was what convinced me that primitives in boxes are not strictly necessary. |
But it's not To rephrase, |
@nicolo-ribaudo I was responding to @mhofman 's claim about a mutable container inside of an immutable object. Of course they aren't the same as JS |
Slightly off topic, but what is |
https://doc.rust-lang.org/stable/book/ch15-01-box.html https://doc.rust-lang.org/std/boxed/index.html In Rust all values are stack allocated by default, so you have to manually use And yes, in Rust there are use cases for heap-allocating primitives like integers, and also use cases for nested |
I have moved the discussion about primitives in boxes to #258, since it was happening in multiple parallel threads. Please let's keep this issue only about:
|
If boxes can, then
IMO it should throw like other primitives. |
I agree - either it should be a generic container (whatever you put into it, you can take out, no exceptions) or it should throw on all primitives, which includes box itself. |
+1. And I prefer the generic container. |
I'd like to note that I have a not-yet-presented proposal for a |
The current thinking is to find a new name to make it obvious this is not a generic container (presented as I also believe there are significant differences with your See #257 for some of the background. |
Note that I still think it's hugely valuable to make it a generic container, and am not convinced by arguments that it should be restricted to only meeting the motivating use case. |
I've demonstrated that generic containers can be built in userland, and if the goal is to have them as primitives, they can be combined with |
The primary value is the coordination point - which only exists if it's in the language. A generic container has been buildable in userland for a long time - it's an array :-) - but that has many usage models, while a standard single container builtin would only have the one (to be a container) |
But that's the thing. A global generic Box container would have many different usages, and would have meaning that differs depending on the application as well. If you want to ascribe meaning to something, that should be expressed explicitly. Plain objects or arrays don't allow you to do that, and neither would a new generic container. However that's what classes provide: a kind and recognizable instances of the kind. The wrapper registry I linked to uses this concept of kind and recognizable values of the kind that classes have, and mixes it with the stable wrapping pattern that These wrappers are objects, and with the help of |
We removed boxes from the proposal, since it was stalled because of them. I'm closing this issue, but we'll keep track of it if we'll bring them up again as a follow on proposal. |
I'd argue that making a Box of a Box should not be allowed but simply return the existing Box.
Aka
Box(Box(x)) === Box(x)
, the same wayObject(x) === x
whenx
is an Object.Originally posted by @mhofman in #233 (comment)
The text was updated successfully, but these errors were encountered: