Value objects: roll your own?

# Axel Rauschmayer (11 years ago)

www.slideshare.net/BrendanEich/value-objects

One thing is not entirely clear from the slides: Will developers be able to define their own value object types? Without that feature I don’t see how overloading operators would be very interesting.

# Brendan Eich (11 years ago)

That is out of date compared to

www.slideshare.net/BrendanEich/js-resp

specifically slide 15:

image.slidesharecdn.com/jsresp-130914140214-phpapp02/95/slide-15-638.jpg?1379296961

which has:

value class point2d { // implies typeof “point2d”
   constructor point2d(x, y) {
     this.x = +x;
     this.y = +y;
     // implicit Object.freeze(this) on return
   }
   point2d + number (a, b) {
     return point2d(a.x + b, a.y + b);
   }
   number + point2d (a, b) {
     return point2d(a + b.x, a + b.y);
   }
   point2d + point2d (a, b) {
     return point2d(a.x + b.x, a.y + b.y);
   }
   // more operators, suffix declaration handler, etc.
}

The idea is to provide convenient declartive syntax for operator multimethods that dispatch along the lines proposed by Christian Hansen. Some work required to handle subclassing, Christian has been helping, writing it up is on my plate for this month.

# Axel Rauschmayer (11 years ago)

Nice! Should the operator (case) definitions really be inside the class? E.g., conceptually, number + point2d does not belong to a single class, it belongs to both. Hence, I’d use something more like a (global) function declaration:

    function number + point2d (a, b) {
        return point2d(a + b.x, a + b.y);
    }

Or, possibly:

    function + (a :: number, b :: point2d) {
        return point2d(a + b.x, a + b.y);
    }
# Andrea Giammarchi (11 years ago)

I had same thoughts on being defined inside the class ... it's quite common out there but here I see it's very easy to create conflicts between classes.

What if a generic point2d + point3d is defined in both point2d class and point3d one ?

IIRC Python just consider it's own representation during one operation, not associating it with "the caller" of such operation ... actually, I've always been envious of these: rgruet.free.fr/PQR26/PQR2.6.html#SpecialMethods

# Brendan Eich (11 years ago)

Axel Rauschmayer wrote:

Nice! Should the operator (case) definitions really be inside the class? E.g., conceptually, number + point2d does not belong to a single class, it belongs to both.

Or to neither. Still there's an advantage in using the class as the locus of multimethod definitions: all combinations point2d defines with other types, in particular with number, go in value class point2d. Another advantage: unary operators do belong here.

Or, possibly:

    function + (a :: number, b :: point2d) {
        return point2d(a + b.x, a + b.y);
    }

Nope, guard syntax is losing to bind, and we don't want guards here anyway.

# Brendan Eich (11 years ago)

Andrea Giammarchi wrote:

I had same thoughts on being defined inside the class ... it's quite common out there but here I see it's very easy to create conflicts between classes.

What if a generic point2d + point3d is defined in both point2d class and point3d one ?

The definitions need to be special forms, associated with one (if unary operator) or two value classes -- if you include number as a value class. Also, the value class declaration being memoized allows cross-frame typeof equivalence for free. I forgot to mention this in my last reply.

IIRC Python just consider it's own representation during one operation, not associating it with "the caller" of such operation ... actually, I've always been envious of these: rgruet.free.fr/PQR26/PQR2.6.html#SpecialMethods

Value objects do not use double dispatch for dyadic operators, though.

# Andrea Giammarchi (11 years ago)

not sure I understand that ... what if both point2d and point3d specify the + ? is that possible ?

talking about

   function + (a :: point2d, b :: point3d) {
        return point3d(a.x + b.x, a.y + b.y, b.z);
    }

specified differently in both point2d and point3d classes

# Andrea Giammarchi (11 years ago)

recap ...

in class point3d

   function + (a :: point2d, b :: point3d) {
        return point3d(a.x + b.x, a.y + b.y, b.z);
    }

in class point2d

   function + (a :: point2d, b :: point3d) {
        return point2d(a.x + b.x, a.y + b.y); // ignore b.z
        // or do something else with z
    }

which one would be the result and why? what if both defines a and b arguments differently too ?

Thanks for clarifications

# Brendan Eich (11 years ago)

We're not abusing guard :: syntax, are you trying to get my goat? :-P

Andrea Giammarchi wrote:

recap ...

in class point3d

   function + (a :: point2d, b :: point3d) {
        return point3d(a.x + b.x, a.y + b.y, b.z);
    }

in class point2d

   function + (a :: point2d, b :: point3d) {
        return point2d(a.x + b.x, a.y + b.y); // ignore b.z
        // or do something else with z
    }

which one would be the result and why? what if both defines a and b arguments differently too ?

Those are distinct multimethods. One returns a point3d, one a point2d. Declaring both as you show (with the bogo-syntax changed to avoid guard-::) results in ambiguous method calls on instances: we get the left+ symbol's set from point2d.prototype, and the right+ symbol's set from point3d.prototype, for an expression of the form

var presult = p2 + p3.

We interset the sets. The result is not a singleton, but a set with two functions. Since there's no return-type dispatch, or indeed any clue what type is wanted for presult, the call is ambiguous and an error is thrown.

This points out another benefit of putting multimethod pattern-based definitions in value class declarations: we can raise an early error when the second value class is processed.

# Andrea Giammarchi (11 years ago)

"the call is ambiguous and an error is thrown" .. that's OK, but this is assuming you know upfront all libraries in the game, right?

Now, how about modules? Would this approach cause unknown possible conflicts all over?

I understand the Python approach might not solve this neither, but I am really wondering about this operators part ... I wasn't even aware of it and if it works I find it very awesome/interesting!

Thanks, not trying to get any goat though

# Brendan Eich (11 years ago)

Andrea Giammarchi wrote:

"the call is ambiguous and an error is thrown" .. that's OK, but this is assuming you know upfront all libraries in the game, right?

No, this is just a matter of loading point2d's value class declaration, then point3d's.

It's fine to have methods defined for various combinations, but defining them twice, with different bodies even (different dynamic return types), is an error.

Now, how about modules? Would this approach cause unknown possible conflicts all over?

It doesn't matter whether you put the value classes in modules or at top level.

I understand the Python approach might not solve this neither,

Double dispatch doesn't solve anything here, it requires writing point2d.add and point2d.radd as well as the same for point3d, and typecase'ing in all four on the other argument's runtime type. And then you're stuck sending a PR, when point4d comes along.

# Andrea Giammarchi (11 years ago)

what I meant is that you require('point3d') before requiring point2d .. is that a concern? Is that like the point4d coming along unexpectedly?

# Andrea Giammarchi (11 years ago)

OK, you actually don't have to reply those question ... I am very looking forward to put my hands on operators overloads. It feels to me almost as exciting as binary data ... I'll be waiting patiently until it's shaped and usable in some engine.

Thanks for all infos so far though,

# Brendan Eich (11 years ago)

Andrea Giammarchi wrote:

what I meant is that you require('point3d') before requiring point2d .. is that a concern? Is that like the point4d coming along unexpectedly?

I don't know what you mean, but in the error example, whichever loads second results in the early error. So what?

# Andrea Giammarchi (11 years ago)

the so what is an unordered require of modules .... but probably those two gonna be aware of each other regardless so it might be a no-real-world issue/problem/error.