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
_______________________________________________
Es4-discuss mailing list
Es4-discuss@mozilla.org
https://mail.mozilla.org/listinfo/es4-discuss

Reply via email to