typed objects and value types
Brandon Benvie
bbenvie at mozilla.com
Wed Apr 2 11:21:08 PDT 2014
On 4/2/2014 8:01 AM, Andreas Rossberg wrote:
> That is very helpful, thanks! I agree with a lot with what you say.
> But to expand on my reply to your blog post, I have one fundamental
> concern: I think the idea that value types should be objects would
> require stretching the notion of object too far, and violate one of
> the fundamental properties that objects currently have. Namely,
> objects have generative identity.
>
> ...
>
> Either way, I think we should refrain from introducing a completely
> new, hybrid form of value that behaves neither like a primitive nor
> like a proper object. To me, it seems a much more elegant and economic
> model to extend the notion of primitive type. That is, make value
> types primitive-like, use the existing wrapping semantics, let them
> have an implicit prototype link, and typeof different from "object"
> (whether something fixed or actually user-defined is debatable).
It seems like we're trying to have our cake and eat it too; we want
these binary data objects to act both like primitives and objects at the
same time. We want them to be comparable by value, but we also want them
to be mutable. The second article proposes doing this by drawing a line
in the sand between mutable object-like versions of a Type and immutable
primitive-like versions of a Type. I think this is problematic because
it seems like a kludge to get something kinda-sorta like what we want
but not really, and also because it introduces two things that look very
similar but that act completely different when it comes to identity.
```js
const Color = new StructType({ red: uint8, green: uint8, blue: uint8 });
const ColorV = Color.valueType();
const blue = Color({ blue: 255 });
blue === Color({ blue: 255 }); // false
const bluev = Color({ blue: 255 });
bluev === ColorV({ blue: 255 }); // true
blue === bluev; // false
// I would expect to end up needing helpers like:
function eq(a, b) {
var TypeV = a.constructor.valueType();
return TypeV(a) === TypeV(b); // eww
}
eq(blue, bluev); // true
```
What I *really* want to be able to do is something like:
```js
const red = Object.freeze(Color({ red: 255 })); // immutable
const blue = Object.freeze(Color({ blue: 255 })); // immutable
let myColor = Color({ red: 255 }); // mutable
myColor === red; // true
myColor.red = 0;
myColor.blue = 255;
myColor === blue; // true, OOPS new identity!
```
Making Color produce either objects or primitives is going to break
strong existing invariants. If it makes objects then it breaks
invariants about object identity. If it makes primitives then it breaks
the invariant that primitives don't have mutable state. For either, it
breaks the invariant that for any two values A and B, the result of
doing `A === B` will never change. I don't think we can/should violate
any of the above invariants and so that leaves us with two options:
reject mutable value types, or give up on using `===` to structurally
compare them. Choosing the latter seems to naturally lead to the idea of
introducing a structural comparison operator.
If we introduced a new configurable equality operator we might be able
to find a path out of this mess. For the purpose of example, let's name
this operator EQ for now. I imagine `EQ` is to `===` as `for-of` is to
`for-in`. With for-in and for-of, they both are about iterating over an
object, but they define "iteration" very differently. With === and EQ,
they both are about comparing the equality of an object, but they define
"equality" differently.
Since === already has identity covered, EQ should focus on structural
comparisons. I would expect to see it working something like:
```js
[5, 10, 20] EQ [5, 10, 20]; // true
({ x: 10 } EQ { x: 10 }); // true
({ x: 20 } EQ { x: 10 }); // false
true EQ true; // true
// from above
let myColor = Color({ red: 255 }); // mutable
myColor === red; // false
myColor EQ red; // true
myColor.red = 0;
myColor.blue = 255;
myColor EQ blue; // true
```
This also opens up the door to how comparing different numeric value
types can work:
```js
0L === 0; // false
0L EQ 0; // true
```
I know it's cringe-worthy, adding more operators for something that is
ostensibly already well covered by JS, with its already two equality
operators and another (or two?) definitions of equality used in the
spec. But really, all the existing operators are inextricably linked
with identity and having nothing to do with structural comparison, so
this really is a different kind of thing.
More information about the es-discuss
mailing list