Google's Code City is a social programming environment. It offers a comic book inspired virtual world where programmers can write code collaboratively.
What features do we need, and how should they work?
Here are some features we've discussed as being potentially useful / necessary, along with a discussion of open questions about how they should be implemented.
Do we want objects to have a 'fertile' attribute that, if false, prevents them from being made the prototype of another object without sufficient authorisation?
An important use-case is the $.user hierarchy. Are there others?
Non-fertile by default probably breaks most libraries.
Maybe automatic .prototype objects should be fertile, but ones created from object literals or by Object.create() are not?
What about RegExps created from literals?
Fertile by default is going to make hierarchy-based permissions checks (proto.isPrototypeOf(instance)) risky: it would be pretty easy to accidentally create a fertile offspring.
Maybe objects should inherit their fertility from their initial prototype?
Naming:
Object.getFertilityOf / Object.setFertilityOf?
By analogy with isExtensible: Object.isFertile?
By analogy with preventExtensions: Object.preventOffspring?
Object Readability
Do we want objects to have a 'readable' attribute that, if false, prevents obtaining or iterating over the list of property names without sufficient authorisation?
Main use cases at the moment is $.userDatabase, which is keyed by login cookie. We could work around this with a closure (at the expense of needing to implement dumping closures in Dumper), and making it rather harder to change the database implementation). Do we have other uses, long-term?
If property readability also hides the name of the property, do we need this?
Should non-readability also prevent unauthorised users from obtaining [[Prototype]], [[Owner]], or other internal slot info (esp. Date value, RegExp source)?
Or should readability and fertility both be properties on an object descriptor (by analogy with property descriptors) passed to Object.setAttributes and returned by Object.getAttributes?
Property-level attributes
The three main features we've discussed are:
having some way to hide the value (and possibly the name) of a property,
having some way of allowing (super)classes to write to properties on instances owned by other users, and
The former approach is more in line with how the existing attributes (writable/enumerable/configurable) work.
The latter approach is conceptually less complicated, but creates challenges deciding what should happen prototype chains are mutated.
Property readability
Do we want properties to have a 'readable' attribute that, if false, prevents obtaining or iterating over the list of property names without sufficient authorisation?
Should non-readable properties still show up in the output of Object.getOwnPropertyNames for unauthorised users? Making non-readable properties completely hidden might obviate the need for object-level readability. (DOS hidden files vs. UNIX non-readable directories…)
Not sure we have any immediate use-case, but if we're going to have any kind of in-DB message storage service we probably want this for privacy reasons.
If non-readability is not forcibly inherited, do we want the attribute to be inherited per-descendant when overriding?
When creating a new property by assignment?
When creating using Object.defineProperty, if not specified explicitly?
Again: closures could be used instead.
But whether we use non-readable properties or closures, there is a potential problematic Interaction with the ability to obtain a list of all the children (or owned objects, or references to ) of an object.
Equivalent to internal slots like [[Prototype]], [[PrimitiveValue]] (Date) or [[Match]] (RegExp).
Pros:
This is going to be standard JS very soon.
Already available in V8 / node v12 (not that we would use it when implementing it.)
No issues with inheriting invalid values, since prototype chain never examined when doing private field lookups.
Cons:
Requires either class syntax or some equivalent mechanism to specify private fields and control access to them.
In any case requires a constructor call to create objects with such fields.
No way to change structure of object after creation.
No way to change which methods can access after the fact (if using class syntax).
Can't be made immutable (like the internal slot of a Date object!)
Not useful as a kind of 'final' attribute for methods.
Less "dynamic" than properties.
Doesn't provide anything like final method—private fields not accessible outside class, so not useful even as a non-final method!
WeakMaps
Alternatively, what if we use WeakMaps, which are equivalent?
We could have a convention that any WeakMap used to implement what would otherwise be e.g. .bar on $.foo be accessible as $.foo.__map_bar; the object browser could then surface the value returned by $.foo.__map_bar.get(x) as if it were x.bar when browsing x.
Pros:
No changes to server required.
Pretty much functionally equivalent to private fields, but
No need for special syntax to create private fields, and
No need for special introspection facilities for IDE.
Changing object 'structure' at after creation is trivial.
Cons:
As with private fields, setting prototype of an object outside the hierarchy of a prototype that defines a 'private field' doesn't cause associated WeakMap entries to be cleared (so gc does not clean them up).
Cleanup could be done manually if using an IterableWeakMap—or, for physicals, a regular Map.
Using WeakMaps cries out for getters and setters to encapsulate map access.
It's so ugly.
Permissive access
What if we allow write (and maybe read-of-non-readable) property access based on the prototype chain?
Simple approach: if u owns prototype p of object o, then functions controlled by u have full access to o as if u owned o.
Fancier: if function f is specially tagged as being a method on prototype p of object o, then f has full access to o as if f{owner} controlled o.
No problems defining a clear set of semantics in the face of mutating prototype chains: access would be granted/revoked automatically.
Minimal changes to implementation of properties in the server.
The tagging implementation (option 2, above) would also enable us to implement the super keyword, letting methods 'pass' without knowing what their superclass is by doing super.method.apply(this, arguments) (rather than `$.specificClass.method.apply(this, arguments) as they must at the moment).
Tagging implementation could also be used as the basis of a semi-automatic isPrototypeOf type checking—e.g., if function body begins with "use autoTypeCheck", it would verify that this has [[HomeObject]] in its prototype chain.
Cons:
This gives pretty wide-open opportunities for abuse. A programmer would have full access to every object that inherits from one of their own.
Provides none of the protection from meddling by subclasses/instances afforded by private fields in JS or !c properties in LambadaMoo.
So offers none of benefits of being able to declare a method final, either.
Tagging implementation is possibly confusing for novices; we'd need to make sure IDE took care of the details fairly reliably.
Restrictive access: reserved, with full hierarchy survey on-demand
What about implementing something like a c bit (call it reserved attribute, or maybe heritable), following the LambdaMOO implementation as closely as possible?
We'd need to examine all the descendants of an object when using Object.defineProperty to create a reserved property (or reserve an existing one), or when using Object.setPrototypeOf to change its prototype.
Pros:
Conceptual model is not too complex, and LambdaMOO refugees will already grok it.
Gives (with readable attribute) something as good as private fields, but more dynamic, since it can be added (e.g. to descendants) after object creation.
Get final methods for free.
Cons:
Will need IterableWeakSet of all children of every object (but we were planning to do that anyway).
Certain operations will become very expensive.
Certain defineProperty or setPrototypeOf operations will fail for (to normie JS programmer) very mysterious reasons.
Remedying such will require a tool equivalent to LambdaCore / mceh-core's @disinherit—even absent fertile bit on objects.
Not completely clear how reserved properties will interact with arrays.
Owner of prototype could (probably) use this to test for existence of hidden properties (properties on non-readable objects) on descendants.
Could make it harder to do this by auto-disinheriting.
Could prevent this by making it illegal to reserve properties if object has any descendants.
Holy grail: reserved, without need for to full hierarchy surveys
Could there be some way to get the semantics of the previous option, without the cost of having certain operations be very slow and/or inconvenient?
Would reserving a property effectively hide any existing properties of the same name on descendants?
If so, would there be some way (e.g., via defineProperty / getOwnPropertyDescriptor to access the hidden values?
Alternatively, would reserving a property effectively blow away properties of the same name on children—albeit perhaps only detected and implemented when the child property is accessed?
This could create a weird situation where reserving a property temporarily would result in some descendants having it deleted, but not others which happened not to be accessed…
Idea: private fields without special syntax
Here we would implement private fields, except:
Rather than being created permanently at construction time, they could be added and removed like regular properties.
They would be accessed, by methods on the prototype which declares them, as .foo instead of .#foo.
Outside those methods, they could be shadowed by any normal properties of the same name, (or by reserved properties of the same name declared on subclasses)—but if no such properties existed, then the reserved property would "show through" as if it were a regular property.
This would mean that a reserved property would be approximately like having a #private field plus automatically-created getters and setters with matching names (and suitable security checks)—i.e.,
class C {
#foo;
get foo() {return this.#foo;}
set foo(value) {/* security check */; this.#foo = value;}
}
Except that, in methods tagged as belonging to C, accessing this.foo would always really access this.#foo, even if .foo were overridden on the actual this object.
Pros:
Most of the benefits of private fields, but without the disadvantages of static allocation and special syntax.
Probably implementable.
Cons:
Doesn't provide final methods.
Kind of confusing that, if object o derives from p2 which derives from p1, and p1 and p2 both declare .foo reserved, then o could itself have three separate values for .foo simultaneously.
Though that's not much worse than in ES6, where o could have P1's #foo, P2's #foo, and a regular .foo too)
What features do we need, and how should they work?
Here are some features we've discussed as being potentially useful / necessary, along with a discussion of open questions about how they should be implemented.
Object-Level Attributes
The two main attributes we've discussed for objects are fertility and readability, equivalent to LambdaMoo Object's
f
andr
properties.Object Fertility
Do we want objects to have a 'fertile' attribute that, if false, prevents them from being made the prototype of another object without sufficient authorisation?
$.user
hierarchy. Are there others?#101:bf_chparent
..prototype
objects should be fertile, but ones created from object literals or byObject.create()
are not?proto.isPrototypeOf(instance)
) risky: it would be pretty easy to accidentally create a fertile offspring.Object.getFertilityOf
/Object.setFertilityOf
?isExtensible
:Object.isFertile
?preventExtensions
:Object.preventOffspring
?Object Readability
Do we want objects to have a 'readable' attribute that, if false, prevents obtaining or iterating over the list of property names without sufficient authorisation?
Main use cases at the moment isDo we have other uses, long-term?$.userDatabase
, which is keyed by login cookie. We could work around this with a closure (at the expense of needing to implement dumping closures inDumper
), and making it rather harder to change the database implementation).Date
value,RegExp
source)?Object.isReadable
/Object.setReadability
?Object.setAttributes
and returned byObject.getAttributes
?Property-level attributes
The three main features we've discussed are:
r
,!c
attributes and verb's!o
attributes, respectively.Cross-cutting concerns
+c
when it was created, even if the prototype property was subsequently made-c
—while Moo Canada overrode#101:bf_set_property_info
to try to ensure that child objects' properties would always be consistent with the initial definition.Property readability
Do we want properties to have a 'readable' attribute that, if false, prevents obtaining or iterating over the list of property names without sufficient authorisation?
Object.getOwnPropertyNames
for unauthorised users? Making non-readable properties completely hidden might obviate the need for object-level readability. (DOS hidden files vs. UNIX non-readable directories…)Object.defineProperty
, if not specified explicitly?Property heritability/reservedness/finality
Private fields
Should we just implement the private class fields proposal and be done with it?
Pros:
Cons:
final
method—private fields not accessible outside class, so not useful even as a non-final method!WeakMaps
Alternatively, what if we use WeakMaps, which are equivalent?
.bar
on$.foo
be accessible as$.foo.__map_bar
; the object browser could then surface the value returned by$.foo.__map_bar.get(x)
as if it werex.bar
when browsingx
.Pros:
Cons:
Permissive access
What if we allow write (and maybe read-of-non-readable) property access based on the prototype chain?
u
owns prototypep
of objecto
, then functions controlled byu
have full access too
as ifu
ownedo
.f
is specially tagged as being a method on prototypep
of objecto
, thenf
has full access too
as iff{owner}
controlledo
.class
methods, if we implement them.Pros:
super
keyword, letting methods 'pass' without knowing what their superclass is by doingsuper.method.apply(this, arguments)
(rather than `$.specificClass.method.apply(this, arguments) as they must at the moment).isPrototypeOf
type checking—e.g., if function body begins with"use autoTypeCheck"
, it would verify thatthis
has [[HomeObject]] in its prototype chain.Cons:
!c
properties in LambadaMoo.final
, either.Restrictive access:
reserved
, with full hierarchy survey on-demandWhat about implementing something like a
c
bit (call itreserved
attribute, or maybeheritable
), following the LambdaMOO implementation as closely as possible?We'd need to examine all the descendants of an object when using
Object.defineProperty
to create areserved
property (or reserve an existing one), or when usingObject.setPrototypeOf
to change its prototype.Pros:
readable
attribute) something as good as private fields, but more dynamic, since it can be added (e.g. to descendants) after object creation.final
methods for free.Cons:
IterableWeakSet
of all children of every object (but we were planning to do that anyway).defineProperty
orsetPrototypeOf
operations will fail for (to normie JS programmer) very mysterious reasons.@disinherit
—even absent fertile bit on objects.reserved
properties will interact with arrays.Holy grail:
reserved
, without need for to full hierarchy surveysCould there be some way to get the semantics of the previous option, without the cost of having certain operations be very slow and/or inconvenient?
defineProperty
/getOwnPropertyDescriptor
to access the hidden values?Idea: private fields without special syntax
Here we would implement private fields, except:
.foo
instead of.#foo
.reserved
properties of the same name declared on subclasses)—but if no such properties existed, then thereserved
property would "show through" as if it were a regular property.reserved
property would be approximately like having a #private field plus automatically-created getters and setters with matching names (and suitable security checks)—i.e.,would be approximately equivalent to
Except that, in methods tagged as belonging to
C
, accessingthis.foo
would always really accessthis.#foo
, even if.foo
were overridden on the actualthis
object.Pros:
Cons:
final
methods.o
derives fromp2
which derives fromp1
, andp1
andp2
both declare.foo
reserved, theno
could itself have three separate values for.foo
simultaneously.o
could haveP1
's#foo
,P2
's#foo
, and a regular.foo
too)