3. Value Types Review, 2
var ColorType =
ValueType(Symbol(“Color"), // or Symbol.for?
{r: Uint8, g: Uint8, b: Uint8, a: Uint8});
ColorType.prototype.average = function() {
return (this.r + this.g + this.b) / 3;
};
var color = ColorType({r: 22, g: 44, b: 66, a: 88});
color.average() // yields 44
assert(typeof color == “color”); // un-capitalized!
// ISSUE: some web JS hardcodes known typeof results
4. Literal Syntax
• Int64(0) ==> 0L // as in C#
• Uint64(0) ==> 0UL // as in C#
• Float32(0) ==> 0f // as in C#
• Bignum(0) ==> 0n // avoid i/I
• Decimal(0) ==> 0m // or M, C/F#
• Lexically transpose, e.g., 0L into L(0)
• Sanity: map short suffix to constructor name
• Reflect.defineSuffix(‘L’, Int64)
• or literalSuffixTable.L(‘0’) h/t @littledan
• or use imported names only? h/t @sebmarkbåge
5. Overloadable Operators
•| ^ &
•==
•< <=
•<< >> >>>
•+ -
•* / %
•unary- unary+ boolean-test!! ~
• ISSUES: lost invariants in spite of overloadable subset
6. Preserving Boolean Algebra
• != and ! are not overloadable, to preserve
identities including
• X ? A : B <=> !X ? B : A
• !(X && Y) <=> !X || !Y
• !(X || Y) <=> !X && !Y
• X != Y <=> !(X == Y)
• ISSUE: implicit-!!(x) vs. !(!x)
7. Preserving Relational Relations
• > and >= are derived from < and <= as follows:
• A > B <=> B < A
• A >= B <=> B <= A
• We provide <= in addition to < rather than derive
A <= B from !(B < A) in order to allow the <=
overloading to match the same value object’s ==
semantics
• And for unordered values (NaNs)
8. Strict Equality Operators
• The strict equality operators, === and !==,
cannot be overloaded
• They work on frozen-by-definition value
objects via a structural recursive strict
equality test (beware, NaN !== NaN)
• Same-object-reference remains a fast-path
optimization
9. Why Not Double Dispatch?
• Left-first asymmetry (v value, n number):
• v + n ==> v.add(n)
• n + v ==> v.radd(n)
• Anti-modular: exhaustive other-operand type
enumeration required in operator method
bodies
• Consequent loss of compositionality:
Complex and Rational cannot be
composed to make Ratplex without
modifying source or wrapping in proxies
10. Cacheable Multimethods
• Proposed in 2009 by Christian Plesner Hansen
(Google) in es-discuss
• Avoids double-dispatch drawbacks from last slide:
binary operators implemented by 2-ary functions
for each pair of types
• Supports Polymorphic Inline Cache (PIC)
optimizations (Christian was on theV8 team)
• Background reading: [Chambers 1992]
11. Binary Operator Example
• For v + u with either a value type instance:
• Let p = v.[[Get]](@@ADD)
• If p is not an OperatorSet, throw a TypeError
• Let q = u.[[Get]](@@ADD_R)
• If q is not an OperatorSet, throw a TypeError
• Let r = p.join(q) (r an Array)
• If r.length != 1 throw a TypeError
• Let f = r[0]; if f is not a function, throw
• Evaluate f(v, u) and return the result
12. Reflect API Example
function addPointAndNumber(a, b) {
return Point(a.x + b, a.y + b);
}
Reflect.defineOperator('+', addPointAndNumber, Point, Number);
function addNumberAndPoint(a, b) {
return Point(a + b.x, a + b.y);
}
Reflect.defineOperator('+', addNumberAndPoint, Number, Point);
function addPoints(a, b) {
return Point(a.x + b.x, a.y + b.y);
}
Reflect.defineOperator('+', addPoints, Point, Point);
// NB: Calling defineOperator with 3 args defines unary operator
13. Subclassing Problem
• Given class A and class B extends A,
• Reflect.defineOperator(‘+’, addAA, A, A);
• Reflect.defineOperator(‘+', addBB, B, B);
• a1 + a2 must select addAA
• a1 + b2 and b1 + a2 must select addAA
• b1 + b2 must select addBB not addAA
• How should OperatorSet implement this?
14. Subclassing Solution
• OperatorSet stores (depth, method) pairs
• Reflect.defineOperator(‘+', addBB, B, B);
copies B’s initial @@ADD and @@ADD_R operator-
sets from A’s @@ADD and @@ADD_R sets
• OperatorSet.join, on finding intersection not a
singleton, breaks tie by maximum prototype depth:
(2, addBB) > (1, addAA)