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.

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:

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:
[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:
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