Oct 1 meeting notes
On Oct 1, 2010, at 4:38 PM, Waldemar Horwat wrote:
Here are my raw notes from our second day.
Thanks again for taking these. Best tc39 note taker. ever. still.
Unique names Question: In what ways does this differ from weak tables?
- A client can look up a property p using a[p] without knowing whether p is a traditional string or a unique name.
Debate about private names leaking via proxies. It's trivial to get at private names by passing a proxy to code that accesses them.
It's not clear what private names are trying to do well. If they want to provide privacy, they can't be visible to proxies. If they want to provide extension without collision (i.e. namespacing), they should be visible to Object.keys, enumeration, etc.
Allen: private syntax is the primary usage of this proposal. Debate over whether there should be two independent concepts of scope or one with a flag.
I think Allen's position is more on the "unique name" rather than truly "private name" side of the trade-off. FWIW.
Brendan: list of questions:
- Private for sure? No: unique name weak encapsulation Yes: Private name strong encapsulation
Perhaps we want both "unique n;" and "private n;".
- Visible via for-in? Object.defineProperty Assignment via private
This bit included "either/or/both" with a dashed line, unlike the solid line between the "No: ..." and "Yes: ..." alternatives to 1, above.
- Visible via Object.keys/getOwnPropertyNames?
The answer here depends not on 2 but on 1: if private names are truly private, then they don't leak via Object.keys/getOwnPropertyNames; else they do, and no worries ("weak encapsulation", par for the JS course).
MarkM: Desugar private n; x.n = ... x.n() into: const n = SoftField(); n.set(x, ...); n.get(x).call(x);
Analogously, desugar unique n; x.n = ... // Maybe make it nonenumerable by default? x.n() into: const n = Name(); x[n] = ... xn
It's useful to view soft-fields as a kind of property names, but then have to address what happens on preventExtensions, freeze, etc. If you think of these private fields as properties then they should be subject to freezing and such.
If you don't think of these soft fields as private fields on the object (so subject to preventExtensions etc.), then only inherited soft fields (not private name objects) can handle this novel use-case.
A proxy having the right to get at an object's private field names is equivalent to a proxy having the right to obtain all weak maps for which the object is the key. The security implications are the same. If a proxy can do a faithful membrane without one of these rights, it can do a faithful membrane without the other of these rights. If a proxy has no rights to get at an object's private field names, the membrane will still work as follows:
proxy[name] does not trap object[proxy] calls a trap on the proxy
Good point -- need a new trap here? Or is this get with an object-type property name?
be
A proxy having the right to get at an object's private field names is equivalent to a proxy having the right to obtain all weak maps for which the object is the key. The security implications are the same. If a proxy can do a faithful membrane without one of these rights, it can do a faithful membrane without the other of these rights. If a proxy has no rights to get at an object's private field names, the membrane will still work as follows:
proxy[name] does not trap object[proxy] calls a trap on the proxy
Good point -- need a new trap here? Or is this get with an object-type property name?
Cormac's paper on virtual values (linked from the harmony:proxies wiki page, see slang.soe.ucsc.edu/cormac/proxy.pdf) already provides a
solution for this case:
obj[proxy] traps the handler's "geti(obj)" trap obj[proxy] = value traps the handler's "seti(obj, value)" trap
We could argue about the precise names of the traps, but I like this design.
On 10/01/10 18:49, Brendan Eich wrote:
It's not clear what private names are trying to do well. If they want to provide privacy, they can't be visible to proxies. If they want to provide extension without collision (i.e. namespacing), they should be visible to Object.keys, enumeration, etc.
Allen: private syntax is the primary usage of this proposal. Debate over whether there should be two independent concepts of scope or one with a flag.
I think Allen's position is more on the "unique name" rather than truly "private name" side of the trade-off. FWIW.
Oh, I see that sentence could be parsed in a way I didn't intend. Mea culpa. What I meant when I wrote it was:
Allen: The syntax itself is the primary usage of this proposal. (The syntax happened to use a keyword called "private", but the choice of words is less important.)
Waldemar
Waldemar, thanks for the great notes. One quick comment on the binary data notes:
int64's: Open issue. Reference semantics are annoying, but what's a realistic alternative? int128's? Those come up increasingly often in SSE programming.
We briefly discussed bignums as a realistic alternative, where equality would be based on numeric value rather than object identity. But for now we'll try to keep the binary data spec as orthogonal as possible from a bignums proposal.
Here are my raw notes from our second day.
Catch guard proposal, resurrected from 1998. Well-liked all around. Proposed to move into Harmony if there are no objections by the next meeting.
MarkM speculating: Could the if syntax be extended to pattern matching? Not easily. Pattern matching doesn't have a concept of failing; also, semicolon insertion would change the meaning of existing programs.
Unique names Question: In what ways does this differ from weak tables?
Debate about private names leaking via proxies. It's trivial to get at private names by passing a proxy to code that accesses them.
It's not clear what private names are trying to do well. If they want to provide privacy, they can't be visible to proxies. If they want to provide extension without collision (i.e. namespacing), they should be visible to Object.keys, enumeration, etc.
Allen: private syntax is the primary usage of this proposal. Debate over whether there should be two independent concepts of scope or one with a flag.
Brendan: list of questions:
Philosophical debate about weak encapsulation: Allen: Weak encapsulation is good enough for most use cases Waldemar: Encapsulation that almost works is worse than either strong encapsulation or no encapsulation. The reason is that folks will code until the code appears to work and then ship with vulnerabilities. This leads to most of the web security attacks. Allen: Functions and objects are roughly dual mechanisms. Why do you need both? Functions provide strong encapsulation; objects provide weak encapsulation. Waldemar: Why does executability have to imply the need for strong encapsulation and vice versa?
MarkM: Desugar private n; x.n = ... x.n() into: const n = SoftField(); n.set(x, ...); n.get(x).call(x);
Analogously, desugar unique n; x.n = ... // Maybe make it nonenumerable by default? x.n() into: const n = Name(); x[n] = ... xn
It's useful to view soft-fields as a kind of property names, but then have to address what happens on preventExtensions, freeze, etc. If you think of these private fields as properties then they should be subject to freezing and such.
A proxy having the right to get at an object's private field names is equivalent to a proxy having the right to obtain all weak maps for which the object is the key. The security implications are the same. If a proxy can do a faithful membrane without one of these rights, it can do a faithful membrane without the other of these rights. If a proxy has no rights to get at an object's private field names, the membrane will still work as follows:
proxy[name] does not trap object[proxy] calls a trap on the proxy When a name object itself (not merely an object merely containing privately named fields) is passed through a membrane, the membrane wraps the name in a proxy p and then uses object[p] traps to maintain the membrane. Private names would be used completely on either the wet or dry side of the membrane, so they'd stay private. Public names that cross the membrane get proxied by the membrane.
Binary data
Given const Point2D = new StructType({ x: uint32, y: uint32 }); const Color = new StructType({ r: uint8, g: uint8, b: uint8 }); const Pixel = new StructType({ point: Point2D, color: Color }); p = new Pixel({point: {x: 3, y:8}, color:{r: 100, g: 50, b: 0}});
p.point.y returns the primitive integer 8 p.point returns an alias (lvalue) to the Point2D substructure (block) of p. Modifying data visible via such aliasing is visible to all aliases.
Debate over whether values such as 3.8 or -1 should be assignable to a uint32 field. Current proposal says no and provices a uint32(x) coercer method: uint32(-1) = 0xFFFFFFFF.
Does calling p.point twice return block references that are === to each other? Dave says he's agnostic. Produced a bit of debate.
Special updateRef feature to avoid creating lots of block wrapper objects (the blocks themselves are aliased so are less of an issue):
var T = new StructType(...); var A = new ArrayType(T, 1000000); t = T.ref(); // allocate a homeless struct object for (i = 0; i < 1000000; i++) { t.updateRef(a, i); // no allocation of struct wrapper }
Given this feature, it's no longer possible to be agnostic on ===. One can't make block references a === b if and only if a and b are aliases of the same block.
One way out would be to have T.ref() return a separate updatable block type, while p.point or a[3] would produce nonupdatable block type objects. Nonupdatable block type objects would have the above natural definition of === and not support updateRef.
The counterpoint to the above is the creation of two kinds of block types, similar to each other.
Debated desire for having blocks inherit from Array.prototype. Debated desire for having some Array generic methods on blocks.
Endianness is invisible to users except when using blocks for file i/o, in which case the routines doing the i/o will take a parameter specifying the endianness of the file and convert as needed.
How does one represent variable-length strings?
Why no character types for fixed-size arrays? Punted from proposal for time reasons for now, but are desirable.
When using this for file i/o, handling of variable-length strings will become important. How to do this? No specific design, but some ideas are:
Given struct {x: uint32, y: uint32, z: uint32, name: String}, an idea would be: string = new Layout({ length: uint16, contents: function(me) {return new ArrayType(uint8, me.length)} }); color = new Layout({ x: uint32, y: uint32, z: uint32, name: string })
How to store these in memory? One can't have a pointer to a string inside a block.
int64's: Open issue. Reference semantics are annoying, but what's a realistic alternative? int128's? Those come up increasingly often in SSE programming.
Waldemar: Reify uint64's as 4-char big endian strings and uint128's as 8-char big endian strings. A significant advantage is that these would have value semantics, and ==, ===, <, <=, etc. would all work correctly. Would need to call methods to do arithmetic or signed inequality comparisons on them.
More debate about block ===. Reached consensus that two (nonupdatable) block references a and b should be === if and only if they are the same type and alias to the same data. Updatable block references will have separate rules.
Discussion on the use of put vs. defineOwnProperty in the ES5 spec as they relate to proxies and library classes. Efficiency concerns with creating descriptors for defineOwnProperty. Most array classes turn out to use put for objects which can be proxied; the places where they use defineOwnProperties tend not to be proxyable because they're done on locally created Array objects.
Can a proxied [[construct]] return a primitive? Yes.
Brendan: How do we do feature detection and top-level patching in ES-harmony? if (!window.fooQuery) var fooQuery = ....
let clarifications: let x and var x at same scope: error let x at top scope of a function or catch block with parameter x: error let x at script top leve: ok module M { export let x; } var binding that would hoist across a let binding: error
let has a read barrier before the let statement gets executed: { let x = 1; if (x) { alert(x); // Read barrier error here because inner x hasn't been initialized yet let x = 2; } }
{ x = 42; // Write barrier error because x hasn't been initialized yet print(x); let x = 99; } Questions about whether let x; should be treated as: let x = undefined; so it has a barrier too. Could take either position on this one.
Brendan: What if let and const don't hoist at all (as in C++ scoping)? This was Waldemar's position before the grand ES4 scoping compromise a few years ago.
Mutually recursive example (wouldn't work with const without some extension): let f, g; f = function() {... g ...}; g = function() {... f ...};
Another alternative for mutual recursion: let [f, g] = [function() {... g ...}, function() {... f ...}];
This assumes that f and g are visible in the initializer, which would bring the barrier issues back. Read barrier would be needed. Debate about whether write barrier would be needed, even if we have expressions for typed/guarded let.
Discussion of pragma syntax: use bignums; use harmony; use modules {A, B, C};