Close review of Language Overview whitepaper
Brendan Eich
brendan at mozilla.org
Wed Nov 14 23:56:29 PST 2007
On Nov 14, 2007, at 9:51 PM, Kris Zyp wrote:
>>> JSON: Sounds good.
>> This proposal is withdrawn and another API is being considered for
>> reinclusion later. See http:;//json.org/json2.js.
> toJSONString and parseJSON are going away? I was actually wanting
> to write
> and suggest the removal of these, with Douglas's recent change in
> his JSON
> API. I am glad to see these will be going away. Will ES4 include
> the other
> API (JSON.parseJSON / JSON.stringify)?
(It's JSON.parse, not JSON.parseJSON.)
I thought I answered that above, sorry if I was unclear. We like
Doug's new API and hope to include it in due course into ES4. JSON
was already an accepted proposal, so it has a foot in the door.
Various interested parties favored something like the json2.js API
already, and I think everyone will rally round it and beat on it, to
make sure it has the right usability and knobs. I'm hopeful.
>> You miss the main difference: structural types are not equated by
>> name, so
>> they avoid the inheritance trap of classes, which consists
> I would love to understand the purpose of structural types better.
> I don't
> understand how base class evolution is constrained in ways that
> super record
> types aren't.
You can have an object {p: 42, q: "hi"} match an annotation of the
form 'like {p:int, q:string}', but there is no class with which that
object is compatible in any well-defined sense except for Object itself.
While you can make a subclass of Object, say class C {p:int,
q:string}, be a structural subtype of a record type, say {p:int} or
{q:string} or {p:int, q:string}, you cannot do the reverse. {p:int,
q:string} is not a subtype of C.
Evolution is constrained when you want to inherit from two classes,
which ES4 does not allow. Assume no name conflicts, since those can
arise with nominal and structural types, and namespaces stand ready
to help.
The problem in general is that Bob's classes and Alice's classes were
written without anticipating Carol's combination of the two, but
Carol cannot use MI. Nor can she provide objects that match
structural types. She has to inherit from both Bob's and Alice's
classes.
This inevitably means duplication, peered objects delegating to each
other, even if Bob and Alice used interfaces well (interfaces help,
but bring in other problems and can't be used everywhere).
This also means conflicts are hard to resolve without writing more
(and more verbose) nominally typed code, instead of writing some
untyped code that satisfies like-types or that can be wrapped with
tolerable performance, or some structurally typed code (see below for
an iterator example).
> I also don't understand how the goal of applying types to existing ES3
> objects can not be achieved with wrap operator and nominal types. I
> know
> there something I am missing here.
You do not want to let any old object masquerade as a nominal type
(class, in this case). Imagine if you had class C { var p:int,
q:string ; final function must_not_override() {...} }. Would {p:42,
q:"hi", must_not_override: function() "rm -rf /"} be safe to pass off
as 'like C'? How about as 'wrap C'?
Remember, the users of C as a type annotation are not interested in
the details of C's implementation (even if final; or if not final, of
a subtype of C coming in as-a C). They care about behavior, in
general. But at the limit, and in essence (i.e., what the type
means), they really want a type *named* C, and nothing but that type
(or of course a subtype, if C is not final).
Ok, ignore final classes and methods, say you are willing to special
case (I'm kidding, but play along ;-). If you let untyped objects
wrap into nominal types, you've killed another important use-case.
Say you want to return instances of a type that no one can construct
except your code. You don't want anyone wrapping some alien object.
You really need your own instances and only those instances. Think of
those capability objects created by private constructors that David
Teller was asking about the other week (thread head).
There are other use-cases that want nominal types to have this
"branded type" integrity property, e.g. hybrid information flow,
which must see all the subtypes in the system in order to analyze all
effects along implicit flows.
But just for the sake of argument, who cares? Let's say you are hell-
bent on minimizing even if it undermines your system's security (not
that this has ever happened before :-P). If you thus reduce the
integrity of classes until they are named record types, and subtype
judgments are record-width subtype judgments decoupled from the
class's extends clause, then you've reinvented structural types. But
look at the costs.
While like and wrap can be handy for dealing with untyped objects,
and efficient when the actual value passing through the annotation is
well-typed, if you want to avoid the potential costs of like (a deep
shape-test) and wrap (a deep read and write barrier), you really do
want to allow a type with C's structure, not C's name -- you want
structural types for the actual arguments flowing in, not like or
wrap types.
The IteratorType is an example. You don't have to extend a built-in
class, or implement a built-in interface, to make an iterator. All
you need is a next method of the right type:
type IteratorType.<T> = {
next: function (): T
};
You can implement iterators using object initialisers. From the RI's
builtins/Map.es (with a bug-fix not yet in the monotone source):
helper function iterate.<T>(f: function(*,*,*):*) {
let a = [] : [T];
informative::allElements( tbl, limit, function (k,v) { f
(a,k,v) } );
let i = 0;
return {
next: function () : T {
if (i === a.length)
throw iterator::StopIteration;
return a[i++];
}
} : IteratorType.<T>;
}
You don't need classes, nor should you. You can retrofit and migrate,
starting with wrap (or like if in the same trust domain), and moving
to annotate object initialisers over time.
To restate the problems above as tersely as possible:
1. Class integrity means that no one can forge an instance using an
untyped object, even with wrap.
2. You shouldn't have to use wrap, it costs enough that it must be
explicit, and not mandatory for all cases where untyped meets typed.
3. Other cases where untyped meets typed can use like.
4. Structurally typing untyped code is lightweight compared to
subclassing and implementing nominal types.
Modula 3 had branding for making nominal types from structural types,
but going the other way, "unbranding" a nominal type to get a
structural type, has no precedent I know of, probably because it
undermines the integrity property that motivates branding or nominal
typing in the first place. A class C can mean many things, depending
on its members, finality, etc. But its meaning is bound to its name,
not its structure.
Does this clear things up?
/be
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://mail.mozilla.org/pipermail/es-discuss/attachments/20071114/cb56b1f4/attachment-0002.html
More information about the Es4-discuss
mailing list