How much sugar do classes need?

Mark S. Miller erights at google.com
Sat Nov 29 21:49:33 PST 2008


On Sun, Nov 23, 2008 at 10:17 AM, Peter Michaux <petermichaux at gmail.com>wrote:

> > In ES, "instance private" means
> > lexically captured variable. ES has no other encapsulation mechanism, and
> > the essence of the classes-as-sugar proposal is to employ that mechanism
> to
> > create encapsulated instances.
>
> Indeed. I thought that hiding that mechanism is part of the sugar's
> objective.
>

Clearly, different people seek different objectives, but there are some
objectives on which there seems to be general agreement. However, I don't
know that there's been any previous attempt to capture this fledgling
consensus. Since everyone likely to disagree is on es-discuss, I'll take a
shot here.

1) Integrity: An abstraction mechanism that supports and encourages higher
integrity abstractions, by contrast with ES3's general loose (everything can
mess with almost everything) approach. In particular, it should be possible
to determine from the source code of the class, and not much else, what
invariants are maintained by instances of that class.

2) Encapsulation: Clients of an instance should be able to utilize its
public interface but not access its private state. (If you want to consider
this point to be required by the Integrity bullet, I have no argument.)

3) Avoid accident prone constructs: For example, in the traditional ES3
style of class-like programming, methods can be extracted and applied to
other objects or even invoked as constructors, binding their this to
whatever. This was the motivations for the binding-on-extraction behavior of
ES4 methods. (Again, feel free to consider this implied by the Integrity
bullet.)

4) Minimize new kernel semantics: As demonstrated by various proposed
desugarings, ES3.1 already contains adequate mechanism for the kinds of high
integrity programming people expect from ES-H classes. Ideally then, classes
would add no new kernel semantics at all to ES-H, but merely desugar to
other elements already present. (The one exception we will probably make is
more direct support for nominal types and type constrained variables and
properties.)

5) (Brendan's phrase) "Syntax as user interface": When the desugared form is
either a) too verbose or hard to read/write, or b) too much harder to
understand than the sugared concept it represents, then some kind of
convenience or abstraction, respectively, is called for. This may take the
form of syntactic sugar, additional libraries, or some combination. However,
in the interests of simplicity, we should minimize the introduction of
sugar, and should prefer libraries to the introduction of new syntax. Hence
the title of this thread.

6) Guidance for future optimizations: As language specifiers, we must
constrain our specifications to those that can be implemented well. But
specification by desugaring can specify a naive inefficient desugaring, so
long as we are confident that an obervably equivanlent but efficient
implementation is practical by other means. Ideally, classes should be
easier to make efficient than the existing class-like pattern people use in
ES3. This was one of the motivations for ES4's fixtures.


Let's start with Crock's original objects-as-closure pattern applied to the
present example (without statics or nominal typing) and updated to use
remaining elements of ES-Harmony, and then refine it according to the above
objectives:

function Point(privX, privY) {
  let privInstVar = 2;
  const privInstConst = -2;

  const self = {
    toString: function() {
      return '<' + self.getX() + ',' + self.getY() + '>';
    },
    getX: function() { return privX; },
    getY: function() { return privY; },
    pubInstVar: 4,
    pubInstConst: -4
  };
  return self;
}

Now I can answer the specific question you ask above. Crock's use of
lexically catured variables to represent private instance state is
beautiful, and needs no sugar. If its semantics were adequate, I would argue
that it needs no sugar at all, as none of the proposed class syntaxes, and
none of the class syntaxes from other languages, are any clearer than this.

By our criteria above:

1) Integrity: Bad. A point instance is simply a mutable record. If two
clients share access to a point, either can arbitrarily replace, delete, or
extend any of its properties, violating any invariant any other client of
the same point may be relying on. Likewise, Point, pubInstConst, and the
three methods are needlessly mutable. pubInstVar is needlessly configurable.

2) Encapsulation: Good. Clients of a point instance cannot access its
encapsulated state. Period.

3) Avoid accident prone constructs: Moderate. On the positive side, because
self is simply a lexically captured variable, extracted methods are already
bound to their instance without any new mechanism. Because they do not
mention 'this', little damage arises if a function/method/constructor is
invoked as a constructor/function/method, etc. OTOH, All five properties are
enumerable though probably only the two data members should be.

4) Minimize new kernel semantics: Perfect.

5) Syntax as user interface: Good. I would have given this one a "Perfect"
but for your point about access public members from inside as simple
variable accesses. Obviously, one could instead write

  function getX() { return privX; }
  const self = {
    ...
    getX: getX,
    ...

but this isn't perfect either. I'll come back to this in the next email.

6) Guidance for future optimizations: Moderate. The cost of the
objects-as-closures technique on current implementations is a closure
allocation per method per instance. Until we address the pervasive loose
mutability of everything, this extra cost would be difficult to optimize
away.


The desugaring I presented at the beginning of this thread addresses all the
weaknesses above, primarily by using the new ES3.1 Object methods to control
the attributes of properties and the extensibility of objects.
Unfortunately, in so doing, it weakened #5 considerably. The result is chock
full of so much verbose boilerplate as to look more like compiler output
than human written code. As far as I am concerned, that verbosity is the
only reason classes need sugar.

In looking back over the original before and after, we can separate two
issues:
* The need for an easier syntax for frozen functions.
* The need for a more expressive way (than the current object literal) to
create an instance.

Both "class" and "to" in my earlier proposal serve to declare a frozen
function. The "static" block was a way to add properties to a function
before it is frozen. The "public" block was my first attempt at a more
expressive object-literal-like notation. In retrospect, I probably should
use the term "object" for now to avoid confusion, and will below. The "to"
says to treat the following function as a method: using its name as a
property name, and making this property non-wriatble, non-enumerable, and
non-configurable, and with a freezing of this function as its value.


> I understand your point. I don't think this sugar is the sugar for
> which people are hoping.


I hope this thread provokes people to explain what they hope for. That would
be great.


> If making a public method means explicitly
> adding a property to an object then your proposed sugar is only
> nutrasweet ;-) I mean that jokingly. I understand you are trying to
> make a minimal proposal to see how minimal it can be.


Indeed. A sweet syntax that doesn't cause the growth of fat! ;)



> I think part of the appeal of writing the following is it is declarative.
>
>  public pubClassConst = -3
>
> Writing the following is imperative.
>
>  Public.pubClassConst = -3


If the first desugars to the second, then it is equally imperative. You're
just obscuring the imperative nature from the programmer. Since the order of
side effects is one of the main sources of programming hazards, again, I
don't think such cosmetic declarativeness is helpful.



>
> I think your idea of adding properties to mean "public" is also the
> reason for the "return self" in your example which I saw and didn't
> understand. Why did you use "self" and not "this" which is a
> constructor's automatic return value? If "look ma, no "this"' is
> possible only because "self" is used in it's place then that seems
> quite a superficial claim.


The "return self;" is there because the "class" sugar and the "object" (aka
"public") sugar are distinct mechanisms that can be used separately or
together. I use "self" because ES's semantics for "this" are at odds with
goals #1, #2, and #3. By explicitly naming "self", lexically nested objects
can choose distinct names, giving inner objects a clean way to refer to
outer objects without confusion.



> Why did you use the "public" keyword at all in the following line
>
>  public self implements Point {
>
> For consistency with this properties idea, should/could that section be
>
>  var self = {
>     to toString() {
>      return '<' + self.getX() + ',' + self.getY() + '>';
>    },
>    to getX() { return x; },
>    to getY() { return y; }
>     let pubInstVar: 4,
>    const pubInstConst: -4
>  };
>   return self;
>
> I've removed the implements because that is related to type checking
> and not this properties vs "public" issue. I'm guessing the "to" is a
> typo and there is a missing comma. I don't understand the need for the
> "let".


You still need to preventExtensions on "self" before returning it. Hence
"object" aka "public".

Yes, let's leave aside the "implements" and related type checking issues for
now.

Your "var" above should be a "const", so that we'll have a read barrier
preventing any use of self before it's constructed.

The "to" isn't a typo, but wasn't explained. It creates a non-writable,
non-enumerable, non-configurable property whose value is a frozen function.

The "let" is probably misconceived, but was to indicate that the property is
non-configurable.

I am not attached to the specifics of my "object"/"public" block. But what
this thread has already taught me is that it is instances that need sugar,
not classes. Enhancing the object literal notation is one way to get there.


If there is to be inheritance (not necessarily single inheritance),
> and something like Java's "protected" modifier for subclass-only
> access, then how would that fit into your system?
>

See the first message of the "Look ma no this" thread where I presented a
similar scheme that could do single inheritance or linearlized multiple
inheritance. (Of course, use of instanceof for nominal typing can only do
single inheritance.) I light of the lack of interest in inheritance, I have
dropped that aspect from further discussions.


-- 
   Cheers,
   --MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20081129/39cdc5de/attachment.html>


More information about the Es-discuss mailing list