Enriched Descriptors, maybe ES7 ?

# Andrea Giammarchi (11 years ago)

Yesterday, after my DHTMLConf talk, some developer asked me to present/propose my idea about introducing optional types in an ES5 compatible way.

Here a quick overview of properties which aim is to guard types or methods signatures, compatible with overloads for both number of arguments, and acepted type per each argument and each "guarding group".

propertyName: {
    // ES5
    writable: true/false
    enumerable: true/false
    configurable: true/false
    get: Function
    set: Function
    value: any

    // my complementary, non obtrusive, info
    type: string(as typeof obj)|
          Function(as instanceof)|
          Object(as isPrototypeOf)
    returns: type|[type1, type2, typeN]
    arguments: [type]|[[type], [type]]
}

The return type can be different too, but I didn't forget to include the special type 'any' as white flag.

All types and what they mean are better described here.

The most handy use case is probably Object.defineProperties(Class.prototype, typedDescriptrs) since each instance will be automatically guarded inheriting the guarded behavior.

As mentioned in this project goals, an ideal/dream goal would be having tools capable of documenting/understanding typed properties such IDE or even "transpilers" and in a parallel universe JS engines capable to natively use such info per each descriptor when available and both speed up properties get/set operations guarding natively the specific type.

As example, this system made possible to implement a StructType shim in few lines of code.

I don't expect this to be discussed soon but I wanted to point at this library now that is just officially born, despite I've proposed something similar in my blog already in 2010, so that if there's any big gotcha/mistake/nonsense in it I am in time to fix/improve or change.

Thanks for your patience reading till the end and for any thoughts or hints that might come out.

Best and happy JS Fest!

# Allen Wirfs-Brock (11 years ago)

Note that ES6 proxies as specified permits Object.defineProperty/getOwnPropertyDescriptor to pass custom attributes in property descriptors and even passes them along (not sure if anybody has actually implemented this yet) wherever there is a internal get descriptor/set descriptor sequence. Proxies that support custom attributes might be an interest way toi experiment with some of these ideas.

# Andrea Giammarchi (11 years ago)

As it is now, Proxies could indirectly benefit from these enriched descriptors.

My current implementation wraps directly Object.create, Object.defineProperty and Object.defineProperties so that:


function Person(name) {
  this.name = name;
  this.children = [];
}

Object.defineProperties(
  Person.prototype, {
  name: {
    type: 'string',
    writable: true,
    enumerable: true,
    value: 'unknown'
  },
  age: {
    type: 'number',
    writable: true,
    enumerable: true,
    value: 0
  },
  children: {
    type: Array, // of Persons
    writable: true,
    enumerable: true
  },
  birthday: {
    returns: 'number',
    value: function () {
      return ++this.age;
    }
  }
});

var me = new Person('ag');
me.birthday(); // yay, I'm 1 now!

Ideally, if this would ever be adopted, descriptors should be stored with custom properties so that Object.getOwnPropertyDescriptor(O, k) could pass these around ... well, the good part is that my proposal works out of the box so even if ignored these properties are just fine as describing the intent.

Long story short, you add this script on top and if you do this it'll throw Errors:

me.age = me.name;
// throws 'expected number, received ag'

me.children = {};
// throws expected Array received object

The Proxy is able to guard in a similar way but it's an explicit layer/men in the middle while having typed descriptors supporting overloads and returns for function properties go further than just getting and setting properties, i.e.


Object.defineProperty(
  window,
  'sum',
  {
    arguments: [
      ['string', 'number'],
      ['number', 'number'],
      ['number', 'string']
    ],
    returns: ['number'],
    value: function (a, b) {
      return parseFloat(a) + parseFloat(b);
    }
  }
);

sum(1, '2'); // 3
sum('3', 2); // 5
sum(4, 5);   // 9
sum('6', '7'); // throws


Back to 2009, dojo.lang.typed used a similar approach for its own classes definition but that was before ES5 introduced descriptors which are not so commonly used, being very verbose, but absolutely beautifully crafted to describe "classes" properties, IMO.

Having a standardized way to offer this natively and having a drop-in/off polyfill on the way could only, still IMO, benefits in the long term with tools possibly capable to understand type a-la TypeScript in VS and engines could take advantage of these descriptors to boost up with or without proxies around.

I am sure I am missing some case and being an early refactoring of an old proposal I am trying to do it as OK as possible.

Thanks again for your time and thoughts.

Best

# Brendan Eich (11 years ago)

Tom has experimented, IIRC. I don't know which implementations preserve extended property descriptor properties.

# Andrea Giammarchi (11 years ago)

my quick tests say no implementation does, all seem to create from the scratch a new object with only known/described descriptor properties.


Object.getOwnPropertyDescriptor(
  Object.defineProperty(
    {},'test',{value:123,type:'number'}
  ),
  'test'
).type // undefined

I'd love to know more about Tom experiments on this!

Best

# Brendan Eich (11 years ago)

Andrea Giammarchi wrote:

I'd love to know more about Tom experiments on this!

With his polyfill on the original proxy implementation, now that I think about it.

tvcutsem/harmony-reflect

but originally

es-lab.googlecode.com/svn/trunk/src/proxies/DirectProxies.js

Tom knows all.

# Andrea Giammarchi (11 years ago)

Uhm .. that looks just like Proxy and descriptors with known specd properties ... might wait for Tom to know if he had anything to do with types and custom properties in descriptors too.

Although my idea is more about types and less about proxies or descriptors themselves, these are fine as they are now.

# Brendan Eich (11 years ago)

Andrea Giammarchi wrote:

Although my idea is more about types

You are starting on the wrong foot there. "Types" mean something in CS. Best if you start with use-cases instead of abusing the term. What code do you want to write, and how exactly would it operate?

# Andrea Giammarchi (11 years ago)

As written before, and explained in the project, I can write descriptors with optional extra properties such as:

type, describing as string the typeof, as Function the expected instanceof, or as generic object as prototypeOf(expectedType)


var o = {};
Object.defineProperty(o, 'num', {
  value: 0,
  type: 'number'
});

o.num = 123; // ok

o.num = '123'; // throws an error, '123' is string, not number

The meaning of type here is the same MS choose for TypeScript but no breaking syntax is necessary plus types can be optionally used to described methods or functions too.


var o = {};
Object.defineProperty(o, 'increment', {
  value: function (much) {
    this.num += much;
    return this;
  },
  arguments: ['number'], // 1 arg as number
  returns: Object // just an instanceof object
});

o.num = 0;
o.increment(1).increment(2);
o.num; // 3

o.increment('1'); // thorws since the argument is not a typeof number

The received arguments can have overloads so that:


var o = {};
Object.defineProperty(o, 'increment', {
  value: function (much) {
    this.num += parseFloat(much);
    return this;
  },
  arguments: [['number'],['string']],
  // 1 arg as number or 1 arg as string
  returns: Object // just an instanceof object
});

o.num = 0;
o.increment(1).increment(2);
o.num; // 3

o.increment('1'); // OK
o.num; // 4

o.increment(new Number(1)); // throws, not typeof number/string

Either the amount of arguments is wrong or the type won't match ... the signature is guarded during development time.

In production, you just do not include upfront the proposed script and everything will work as regular ES5 preserving all behaviors and descriptors ... in few words, I am proposing an alternative to TypeScript that integrates well down to ES5.

If engines would like to use types and descriptors provide such info, this could have ideally performance benefits too plus the code will be most likely less "undefined is not defined" prone.

The library I have there, already provides all I've said behind 64 tests that verify the behavior is guarded and preserved with or without the library upfront.

Compatibility with the test link here

Best

# Andrea Giammarchi (11 years ago)

about the first example, I forgot the writable for the num property so please read that as if I wrote:

var o = {};
Object.defineProperty(o, 'num', {
  writable: true,
  value: 0,
  type: 'number'
});

the library makes possible to create a partial shim of StructType in few lines of code, using same mechanism to guard types: WebReflection/define-strict-properties/blob/master/src/StructType.js#L40

In this case, types are downgraded to 'number' ... and that's just an example

# Andrea Giammarchi (11 years ago)

last link is the test file which better than examples in here describes what happens when the library is around, how ES5 descriptors can guard properties and methods within objects or prototypal inheritance.

I hope what is this about is a bit more clear.

Best

# Tom Van Cutsem (11 years ago)

2014-03-09 21:05 GMT+01:00 Brendan Eich <brendan at mozilla.com>:

Tom has experimented, IIRC. I don't know which implementations preserve extended property descriptor properties.

As Allen mentioned earlier, proxies should receive in their defineProperty trap the full descriptor object as it was passed in by the user, including any custom attributes. The proxy is free to store it, and return it from any subsequent getOwnPropertyDescriptor trap (which is also specced so that it can return a non-normalized descriptor object with custom attributes).

Normal (non-proxy) objects of course cannot store non-normalized descriptors, so any custom attributes are lost. Changing this would presumably have a big impact on implementations (which probably don't hang on to descriptor objects at all).

If you want to experiment with custom attributes, my harmony-reflect shim supports this for proxies:

var props = {}; var o = new Proxy({},{ defineProperty: function(target, name, desc) { props[name] = desc; return true; }, getOwnPropertyDescriptor: function(target, name) { return props[name]; } }); Object.defineProperty(o,"p",{value:42, configurable:true, hello:true}) JSON.stringify(Object.getOwnPropertyDescriptor(o,"p")) // prints // {"configurable":true, // "value":42, // "writable":false, // "enumerable":false, // "hello":true } // <- custom attribute preserved

Using Firefox's built-in direct proxies implementation I get a TypeError. I'll investigate further and file a bug.

, Tom

# David Bruant (11 years ago)

Le 10/03/2014 08:02, Tom Van Cutsem a écrit :

Using Firefox's built-in direct proxies implementation I get a TypeError. I'll investigate further and file a bug.

You already did bugzilla.mozilla.org/show_bug.cgi?id=601379 ;-)

# Andrea Giammarchi (11 years ago)

Thanks Tom, I see what you mean but using proxies is a performance killer, while I am trying to propose something that theoretically could also give a performance boost.

I am not proposing custom descriptor attributes here, I am proposing type, arguments (or parameters) and returns as specified/extra descriptor properties capable to bring "typed properties" to, maybe, ES7

My repository already does this and it works even copying objects, if this is done through getOwnPropertyDescriptor

So, custom descriptor properties a part, would ES7 consider to implement types not only for StructType but also for runtime defined shapes so that Object.defineProperty({}, 'num', {value: 123, writable: true, type: int32}) works same as my polyfill does already?

# Tom Van Cutsem (11 years ago)

2014-03-10 11:10 GMT+01:00 David Bruant <bruant.d at gmail.com>:

Using Firefox's built-in direct proxies implementation I get a TypeError. I'll investigate further and file a bug. You already did bugzilla.mozilla.org/show_bug.cgi?id=601379 ;-)

Thanks for refreshing my memory on that one ;-)

But that was actually not the bug that caused my earlier code snippet to fail. Instead it's this bug (already reported by Eddy Bruel): bugzilla.mozilla.org/show_bug.cgi?id=793210