Value objects: roll your own?
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.
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);
}
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
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.
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.
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
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
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.
"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
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.
what I meant is that you require('point3d') before requiring point2d .. is that a concern? Is that like the point4d coming along unexpectedly?
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,
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?
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.
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.