Closed ericelliott closed 9 years ago
Thinking of Open Standard.
Functions are the first class citizens in JavaScript. I was worried all the time that stamps cannot produce functions as object instances.
There is always a workaround via .init(() => { return function () {}; })
But as Eric says
it's a work-around, not a solution. Now we have a user education problem
I believe that the .create()
function should accept not refs
object, but the object itself.
MyStamp(); // creates new object
const plainObject = { key: value };
MyStamp(plainObject); // returns the plainObject object
const myFunc = function whatver() {};
MyStamp(myFunc); // returns the myFunc object
Thoughts?
More thinking about Open Standard.
stamp.create()
argument is not an object (lodash check should be used _.isObject
) then no refs or props should be added obviously. But all the init()
functions should be called regardless.In other words, stamps should flawlessly process any object. No exception, no warning, nothing. The type of the processed object should be up to the user. (Just like promises do.)
In the following example I'm using sound processing terminology:
const Cutoff = compose(
refs({ min: 5, max: 250 }),
init(function({ instance }) {
return instance < this.min ? this.min :
intance > this.max ? this.max :
instance; })
);
const Aplify = compose(
refs({ factor: 2.5 }),
init(function({ instance }) { return this.factor * instance; })
);
const Amplificator = compose(Aplify, Cutoff);
Amplificator(200); // returns 250 because 200*2.5 is more than 250 cutoff upper limit
Amplificator(20); // returns 50 beacuse 20*2.5 is within the cutoff range 5-250
Amplificator(0); // returns 5 because 0 is less than 5 cutoff lower limit
Agreed. .create()
should take an object to be augmented. Any type of extensible object should work.
What about the following idea.
Take a look at this stamp:
const Connection = compose(
refs({ url: { host: 'localhost', port: 80 } }),
init(function({ stamp, instance }) { this.clone = () => stamp(instance); }),
methods({ connect() {...} })
);
I see that refs
, init
, and methods
are repetitive.
init
always receives a function.refs
always receives an object with data.methods
always receives an object with functions.Why won't we simply ducktype those? The order and number of the arguments should not matter. The following should produce the same stamp as above.
const Connection = compose(
function({ stamp, instance }) { this.clone = () => stamp(instance); },
{ url: { host: 'localhost', port: 80 } },
{ connect() {...} }
);
Such a nice shortened syntax gives the idea that the compose
is not the best name for the function. I believe stamp
would serve better.
Thoughts?
Another proposal which I strongly believe is for the best.
Rename methods
to proto
.
someObject.create.proto
Why?
methods
object becomes the prototype of instantiated objects.Take this code
const Connection = compose(
function({ stamp, instance }) { this.clone = () => stamp(instance); },
{ url: { host: 'localhost', port: 80 } },
{ connect() {...} }
);
It looks very much alike class declaration: constructor, property url
, method connect
.
But what if we had only single function stamp
which does it all - creates new stamps, composes existing?
const Connection = stamp(
function({ stamp, instance }) { this.clone = () => stamp(instance); },
{ url: { host: 'localhost', port: 80 } },
{ connect() {...} },
SecondStamp
);
And then the stamps can be composed:
var ComposedConnection = stamp(Connection, ThirdStamp);
I like this syntax very much. :)
P.S. Ducktyping rocks. :)
Proposal syntax.
import {stamp, deepProps, statics} from 'stamp';
const Connection = stamp(
function({ stamp, instance }) { this.clone = () => stamp(instance); },
{ url: { host: 'localhost', port: 80 } },
{ connect() {...} },
statics({ printUrl: function () { console.log(this.url); } }),
deepProps({ url: { protocol: 'https' } }),
);
Alternatively, statics can be added directly to a stamp. It should be identical to the statics above:
Connection.printUrl = function () { console.log(this.url); };
const ComposedConnection = stamp(Conneciton, SecondStamp);
ComposedConnection.printUrl(); // <-- should not throw
@ericelliott descriptors should be implemented using decorators I believe.
Can someone put up an example that showcases the creatables
in action?
Rename methods to proto.
This was brought up in another issue. I voted no. Putting anything other than functions on the prototype is a code smell. We should not encourage it.
Ducktyping rocks. :)
I like ducktyping, but your proposal doesn't sit well with me. The structure is too loosey goosey. :/
@koresar:
import {stamp, deepProps, statics} from 'stamp';
const Connection = stamp(
function({ stamp, instance }) { this.clone = () => stamp(instance); },
{ url: { host: 'localhost', port: 80 } },
{ connect() {...} },
statics({ printUrl: function () { console.log(this.url); } }),
deepProps({ url: { protocol: 'https' } }),
);
It's a clever idea, but code should be optimized for reading, not writing. This is definitely less readable than:
import {stamp, deepProps, statics} from 'stamp';
const Connection = stamp(
init(({ stamp, instance }) => { this.clone = () => stamp(instance); }),
refs({ url: { host: 'localhost', port: 80 } }),
methods({ connect() {...} }),
statics({ printUrl: function () { console.log(this.url); } }),
deepProps({ url: { protocol: 'https' } }),
);
I'm cool with the name statics
and deepProps
, and while we're on the subject of readability, with deepProps
providing a more descriptive name, I think we should rename refs
back to state
.
Also, re: letting people hold state on the delegate prototype, is there a significant difference between doing that, and holding state in the statics
?
IMO, if you need shared state (which you should avoid), a static seems like a much better place to put it... doesn't it?
Can someone put up an example that showcases the creatables in action?
Stampit is an example of createables in action. We're just talking about publishing a spec so that other libraries can implement standard createables, too.
Eric, I agree to all your suggestions. Especially
code should be optimized for reading, not writing
But this one I tend to disagree with because it ruins readability you've just mentioned:
I think we should rename
refs
back tostate
.
"refs" is explicitly saying that "I'm going to shallow copy your data". "state" doesn't say if it gets shallow copied or deep copied.
Take promises for example. They just work. The only global variable which got introduced is Promise
solely to create new promise chains.
You don't need to import Promise from 'Promise'
if all you need is to continue execution:
db.getById(123).then( bla bla bla ); // <-- See? No need to import stuff
We also don't want people to import stamp
each the time they need to extend a stamp with one more method.
Moreover, "createable" should sound like "I can create", but actually sound like "I can be created". Confusing a little. Right?
So, maybe we should rename "creatable" to "stampable", i.e. an object which can be stamped?
Benefits:
1) Any stamp can be extended/composed by simple MyStamp.stamp({ init: function() {} })
or alike.
2) No need to extra import stamp
.
3) I feel that functionality is duplicated by MyStamp()
and MyStamp.create()
calls. We don't need both. One is enough.
4) People familiar with promises will understand the idea of stamps easier.
5) The create
word is used in programming more often than stamp
. There'll be less collisions.
IMO this would be an ideal world if such thing would exist.
Thoughts?
Another good name for "stampable" could be "composable". This one is even better!
Analogies:
somthing.then()
~ something.compose()
composable
has a nice ring to it) - specifies what a stamp is, and how to turn a stamp description into a working stamp.compose(baseObject, stamps...)
is the default stampit export.create(baseObject, stamps...)
take a base object and any number of stamps. Return a new baseObject
instance with the capabilities of the composed stamps. If no arguments are passed, it uses an empty object as the base object..compose(baseObject, stamps...)
stamp method identical to compose()
, except it prepends this
to the stamp parameters. If no arguments are passed, it just uses an empty object as the baseObject
, and the parent object as the stamp.create(baseObject, stamps...)
create()
should be able to take multiple types of objects and simply extend them, e.g., a function or an array. However, stamps with function or array base objects should not extend the built-in prototypes for Object
or Array
.
compose(baseObject, stamps...)
Compose always takes any number of stamps as arguments and returns a new stamp composed from the passed in stamps. If no stamps are passed in, it returns an empty stamp.
compose(stamps)
: Composes stampsassign(objects...)
: Creates objects like object.assign, but always creates a new stamp rather than using the first param as destination. This will be familiar to anybody who uses ES6+ or lodash.assign()merge(objects...)
: Like assign()
, but merge deep propertiesinitialize(functions...)
: Functions to run during instance creation. Return value gets used as the current instancedescribe(propertyDescriptors...)
: See original postconfigure(options...)
: Creates a new stamp with stamp options. e.g. descriptor presets. Users can use this to implement custom stamp types via initializer.set(pathString, value)
: Creates a mergeable stamp using a deep property setter string. e.g. set('obj.with.deep.properties', value)
reactComponent(componentDescription)
: Creates a stamp that describes a react component. See react-stampithtml(htmlString)
: Take a DOM node HTML string and returns a stamp that produces DOM nodeslegacyStampit()
: Return an instance of the v2 legacy API for flunt chaining.@koresar addressing some of your thoughts:
It's very important that we don't conflate the creation of stamps with the creation of instances. compose()
returns a stamp. .create()
returns an object instance as described by the stamp. These are two very different things.
Any stamp can be extended/composed by simple MyStamp.stamp({ init: function() {} }) or alike.
We should only use one property on the created stamp. I strongly feel that property should be called .create()
- implying that we're creating an object instance (not a new stamp).
I have no argument against exposing myStamp.create.compose()
, but I think most users will prefer just to import compose()
, instead.
I don't think we should call .compose()
".stamp()"
. I like that .compose()
is a verb, and it implies that you can compose stamps.
Eric, please hear me.
MyStamp()
and MyStamp.create()
That's why we should drop the .create
and have a .compose
instead.
"Less is more" you said in your recent tweet.
Ah, I see what you mean. Updated.
:+1:
One of the shortfalls that I've experienced with v2 has been with descriptors, as Object.assign()
doesn't correctly copy over items like { get foo() {}, set foo() }
whereas class
does. Would descriptors solve this and how would it solve this (in both ES5 and ES6)?
Also, would creating a stamp with a descriptor { writeable: false }
be able to safely be composed with another stamp with the same descriptor?
compose()
, should it noop instead of defaulting? Would it follow the principle of least astonishment for it to be a clone()
if only one stamp is passed?merge
the replacement for deepProps
? I commonly refer to it as _.merge
due to lodash.Edits: spelling, pronouns, derps
One of the shortfalls that I've experienced with v2 has been with descriptors, as Object.assign() doesn't correctly copy over items like { get foo() {}, set foo() } whereas class does.
Yes, it would. The ability to add descriptors means that we will have full support for getters and setters, which are included in the descriptors API.
Also, would creating a stamp with a descriptor { writeable: false } be able to safely be composed with another stamp with the same descriptor?
Yes, provided that the other stamp's definition does not attempt to write to the property. I imagine it would throw the first time you try to write to it, so as long as your unit tests are thorough, you should be able to catch such collisions in your programs.
If only one stamp is passed to compose, should it noop instead of defaulting? Would it follow the principle of least astonishment for it to be a clone() if only one stamp is passed?
No, it will return a new stamp based on the stamp you passed in. Stamps are immutable since 2.0.
Is merge the replacement for deepProps? I commonly refer to it as _.merge due to lodash.
Yes. I think it's a much less confusing name. =)
I like @koresar's comparison to the Promise spec.
Me too. He's full of good ideas lately. =)
This is looking good, lots of discussion. With the recent ideas, does the below look accurate?
/* composable === some object */
_.isFunction(composable); // true
_.isArray(composable.initializers); //true - optional
_.isPlainObject(composable.references); //true - optional
_.isPlainObject(composable.properties); //true - optional
_.isPlainObject(composable.methods); //true - optional
compose(baseObject, composables...)
Takes a base object and any number of composables.
Return a new composable with the capabilities of baseObject
and the passed composables. If no arguments are passed, it uses an empty object as the base object.
composable(baseObject, composables...)
Takes a base object and any number of composables.
Return a new baseObject
instance with the capabilities of the passed composables. If no arguments are passed, it uses an empty object as the base object, and the parent object as the composable.
composable.compose(baseObject, composables...)
Takes a base object and any number of composables.
Identical to compose()
, except it prepends this
to the composable parameters. If no arguments are passed, it just uses an empty object as the base object, and the parent object as the composable.
Note: Am I thinking too much about stampit specific implementation? What should be included in the actual specification?
You're missing descriptors
& symbols
in the tests.
You're composable
function has an identity crisis. There is no parent object when not invoked as a method. It should use an empty composable. Otherwise, looking good!
We could start writing composable unit tests. We should host spec tests in a composable spec repo. I'll set it up later. :)
Typos from phone. :)
I'm confused with the Tim examples.
What's "base object"? AFAIK there's no such thing as base or parent object.
I'll get home and will post a complete set of examples as I see it.
On Sun, 12 Jul 2015 14:59 Eric Elliott notifications@github.com wrote:
Typos from phone. :)
— Reply to this email directly or view it on GitHub https://github.com/stampit-org/stampit/issues/151#issuecomment-120687335 .
Stamp()
import Stamp from 'stamp'; // alternatively the Stamp can be global like `Promise` in ES6
const composable = Stamp(
{ // a stamp-description object with the following properties (names are far from final)
methods: ...
assign: ...
run: ...
merge: ...
statics: ...
},
anotherComposable // a composable. There can be more arguments
);
composable()
run()
functions._.isFunction(composable); // true
const obj = composable(baseObject, args...); // creates objects.
composable.compose()
_.isFunction(composable.compose); //true
var combinedComposable = composable.compose(
anotherComposable, // a composable
{ // a stamp-description object
methods: ...
assign: ...
run: ...
merge: ...
statics: ...
},
thirdComposable // one more composable, etc. etc. etc.
);
// stamp-description properties (far from final list and names)
_.isArray(composable.compose.initializers); //true - optional (format of the object is yet to decide)
_.isObject(composable.compose.references); //true - optional (format of the object is yet to decide)
_.isObject(composable.compose.properties); //true - optional (format of the object is yet to decide)
_.isObject(composable.compose.methods); //true - optional (format of the object is yet to decide)
_.isObject(composable.compose.statics); //true - optional (format of the object is yet to decide)
----- The end. Finita. Basta. Fin. -----
The above is the entire Standard. Do you see how short the Open Composables Standard can be?
Less is more (C) Eric Elliott
The things described in the initial top message of this thread are handy ecosystem utilities similar to Promise.all()
or Promise.reject()
or Promise.resolve()
etc. And I actively like it and support it very much! Although, they should not be part of the core specs.
You're composable function has an identity crisis.
I'm not sure I understand what you mean.
What's "base object"? AFAIK there's no such thing as base or parent object.
"base object" is your "stamp-description object". "description object" does sound better. "parent object" is just that, the parent from which the .compose
method is attached. (aka "parent description object")
Your write-up looks good, but I think we should drop any uses of "stamp" from the spec and use "composable". Also composable()
returns object instances, we should clarify that.
I believe we have to define the terms we use in the spec, otherwise the interpretation is arbitrary (e.g. merge
, references
vs properties
etc...).
Also, initializers must be described in the spec to remove ambiguity. Any guesswork here by the implemenation will mean that different composable libs won't work together. For example, all A+ compliant Promise libs must conform to .then()
's specification...so everyone can:
// dummy code
(new Promise()).then(function(onFulfilled, onRejected) {});
var Promise = require('promise');
(new Promise()).then(function(onFulfilled, onRejected) {});
var Promise = require('bluebird');
(new Promise()).then(function(onFulfilled, onRejected) {});
var Promise = require('q');
(new Promise()).then(function(onFulfilled, onRejected) {});
Even if the answer is "nothing/you decide" we should answer:
Outside of that:
assign
/merge
/statics
are merged. Which are deep, which are not, which use Object.assign
and which use Object.defineProperties
etc...foo
in .compose({ foo: 'bar' })
.@troutowicz
I think we should drop any uses of "stamp" from the spec and use "composable"
Think of an analogy: Stamp=Promise, Composable=Thenable. (Also "composable" is too long word as for me.)
@JosephClay
Define how assign/merge/statics are merged. Which are deep, which are not, which use Object.assign and which use Object.defineProperties etc...
I have most of that in my head. Should be written down instead though.
Define what happens if "invalid" configurations are passed. What happens to foo in .compose({ foo: 'bar' }).
TypeError
should be thrown in most (if not all) cases.
@JosephClay
What's the context of initializers?
The initializer function binding:
1) If the object instance _.isObject
then the initializer will be bound to it.
2) If the object is not _.isObject
then the initializer will be bound to the mergeUnique(Object.assign({}, references))
temporaty object (the mergeUnique
is the supermixer function).
What parameters are passed?
Initializers receive the same thing as they receive in stampit v2 - { stamp, instance, args }
object.
@JosephClay
I believe we have to define the terms we use in the spec, otherwise the interpretation is arbitrary (e.g. merge, references vs properties etc...).
Definitely! Any proposals?
(please, speak if you don't like)
The word composable
is we mutually agreed with I believe.
I strongly like the word Stamp. Firstly, because it's short. Secondly - because it's a brand name.
I propose to use stamp-description object or simply stamp descriptor. Ok?
I like assign
and merge
words because they imply what will be done to my data. How do you feel about it?
I'm okay with the methods
word. We all agreed on that. (Although, I secretly like proto
.)
I'm okay with statics
. I doubt someone might find a better name for it.
I'm still thinking of better name for the initialize
. That's why in my above examples you can see run
.
Did I miss anything?
Think of an analogy: Stamp=Promise
OK, fine with me. I just wasn't sure how much stampit lingo we were wanting to use in a spec.
The factory implementation pseudocode (not taking into account the defineProperty
stuff, because it's hard):
function factory(instance, args...) {
const context = _.isObject(instance) ? instance : {};
_.merge(context, this.compose.merge);
_.assign(context, this.compose.assign); // references are taking over deep props
context.__proto__ = this.compose.methods;
this.compose.initializers.forEach((init) => {
const result = init.call(context, { instanace, stamp: this, args });
if (!_.isUndefined(result)) {
instance = result;
}
});
return instance;
}
A map containing the following properties and values:
TODO: find a place for this to sit
initializers
references
object.merge
, assign
, statics
TODO: define the expectation of the end result of each object. e.g.
{writeable: false}
will throw an exception on merge when xObject.defineProperties
for assign
Object.assign
for statics
_.merge
for merge
Object.assign
to combine merge
and assign
into properties.^ please correct if anything is amiss
I've tried to capture discussion RE: terminology in the description above.
I have not yet captured the parameters for initializers.
The more I see "initializer" used instead of "init" or "run", the more I like it, simply because it provides an obvious vocabulary. "runners" don't quite have the same ring, do they? =)
I'd like to stick to verbs for utility function names, e.g. assignStatic()
instead of statics()
. Users are free to import them with whatever names they like, e.g.:
import statics from 'stampit/utils/assignStatic';
@JosephClay I believe you are largely confused on what stamp is. :)
composable: a factory function that creates stamp instances.
Composable and Stamp are essentially the same thing. Have you heard of Thenable and Promise? It's the same.
Composable is a function which have property .compose
which is also a function.
Stamp is a Composable with predefined structure (which we are discussing here).
compose: a function that combines stamps and plain objects into a new stamp descriptor.
into a new stamp, but not descriptor. IMO stamp descriptor is a part of stamp, but not the stamp itself.
stamp: an object returned from a composable factory using a stamp descriptor.
I didn't quite caught this sentence.
assign: a shallow map of property name and values attached to the stamp on stamp creation.
attached to the stamp, but assigned to new instance objects created by the stamp. Correct? Same goes for merge.
methods: a map of property name and function values that are added to the stamp's prototype on stamp creation.
Wrong. Stamps do not have prototypes. Object instances created by the stamp do have prototypes.
statics: a map of property name and values attached to the composable.
attached to a stamp (aka composable).
initialize: a function to be executed against the stamp's scope on stamp creation.
Very wrong. :) Stamp is a factory function, it creates objects! a function to be executed against the stamp's scope on object instance creation.
instance: the current context of the initializer.
Nope. Instance is the object being created (stamped). Most of the time instance is the initializer's binding context.
{writeable: false} will throw an exception on merge when x
I'm hugely against any exceptions. Long to explain, but this is to implicit. Sometimes object creation will throw, sometimes will not... Inconsistency sucks. Better skip conflicting properties.
I believe you are largely confused on what stamp is. :)
I am! After reading through your comments and re-reading this thread and the stampit README, I'm starting to see where I'm going wrong. My brain is tangled :smile:. I'm using stamp and object instance in the wrong places (or worse, interchangably).
IMO stamp descriptor is a part of stamp, but not the stamp itself.
I see. I was seeing them as the same thing due to the specifics of the descriptor being accessible from the stamp.
Composable and Stamp are essentially the same thing. Have you heard of Thenable and Promise? It's the same.
I can see the relationship you're trying to define here. They're not quite 1:1, but pretty close concept.
I'm hugely against any exceptions. Long to explain, but this is to implicit. Sometimes object creation will throw, sometimes will not... Inconsistency sucks. Better skip conflicting properties.
I'm not on either side of this one...but it shouldn't be an idle decision. Seems that it could work either way.
The rest of your comments are spot on. Between your comments and EE's updates above, I think I'm on track.
I added a stamp descriptor section. Please look it over & comment.
@ericelliott we need to discuss this radical and largely confusing method.
stamp(baseObject, stamps...) => objectInstance
How come we pass both user data and composables one next to another?
What about easy private state (arguments passed to the init functions)?
@koresar You're right.
I went through the top message of this issue. I think I like every part of it.
There's one function name choice which will not lint in most projects. The set
function name.
Any better names you have in mind people?
There's one function name choice which will not lint in most projects. The set function name. Any better names you have in mind people?
Why won't set()
lint? It should.
Let's move our discussions to the special repo: https://github.com/stampit-org/stamp-specification/issues
Please, create new issues to disccus each particilar topic.
Why won't set() lint? It should.
Yep. Double checked with couple of linters intalled on my machine. They all accept such properties. Good.
Seems like we have migrated all the idea we had here. Closing this one in favour of the new repository. Initial post updated accordingly.
Tahnks people for the productive discussion. Cheers.
UPD: moved to the new repo.
Stamp Composable Standards
Definitions
Default exports
compose(stamps...) => stamp
Creates stamps. Take any number of stamps and return a new stamp that encapsulates combined behavior. Is the default stampit export.stamp(baseObject, args...) => objectInstance
Creates object instances. Take a base object and any number of arguments. Return a newbaseObject
instance. If no arguments are passed, it uses an empty object as the base object..compose(stamps...) => stamp
Creates stamps. a method exposed by all composables (i.e., stamps) identical tocompose()
, except it prependsthis
to the stamp parameters. If no arguments are passed, it uses thethis
object as the stamp. Stamp descriptors are attached to the compose method., i.e.stamp.compose.*
stamp(baseObject, arguments...) => objectInstance
stamp()
should be able to take multiple types of objects and simply extend them, e.g., a function or an array. However, stamps with function or array base objects should not extend the built-in prototypes forObject
orArray
.compose(stamps...) => stamp
Compose takes any number of stamps as arguments and returns a new stamp composed from the passed in stamps. If no stamps are passed in, it returns an empty stamp.
The Stamp Descriptor
The names and definitions of the fixed properties that form the stamp descriptor. The stamp descriptor properties are made available on each stamp as
stamp.compose.*
methods
- A set of methods that will be added to the object's delegate prototype.references
- A set of properties that will be added to new object instances by assignment.deepProperties
- A set of properties that will be added to new object instances by assignment with deep property merge.initializers
- A set of functions that will run in sequence and passed the data necessary to initialize a stamp instance.staticProperties
- A set of static properties that will be copied by assignment to the stamp.propertyDescriptors
- A set of object property descriptors used for fine-grained control over object property behaviorsconfiguration
- A set of options made available to the stamp and its initializers during object instance creation. Configuration properties get deep merged.Core stamp creation utilities
compose(stamps) => stamp
: Composes stampsassign(objects...) => stamp
: Creates objects like object.assign, but always creates a new stamp rather than using the first param as destination. This will be familiar to anybody who uses ES6+ or lodash.assign()merge(objects...) => stamp
: Likeassign()
, but merge deep propertiesinitialize(functions...) => stamp
: Functions to run during instance creation. Return value gets used as the current instanceassignStatic(objects...) => stamp
Assigns arguments as static properties on the stamp (as opposed to instances).describe(propertyDescriptors...) => stamp
: Property descriptors with presets.configure(options...) => stamp
: Creates a new stamp with stamp options. e.g. descriptor presets. Users can use this to implement custom stamp types via initializer.Extended utilities - Useful utilities not part of the standard specifications.
set(pathString, value) => stamp
: Creates a mergeable stamp using a deep property setter string. e.g.set('obj.with.deep.properties', value)
reactComponent(componentDescription) => stamp
: Creates a stamp that describes a react component. See react-stampithtml(htmlString) => stamp
: Take a DOM node HTML string and returns a stamp that produces DOM nodeslegacyStampit() => compatableStamp
: Return an instance of the v2 legacy API for fluent chaining.Original Post / Idea Follows:
@koresar has proposed yet another great solution for Stampit: a standard
composables
specification inspired by the Promises specification:This is a great start. We should probably attempt to support all of the object features afforded by ES6/ES7, including the ability to configure properties and use symbols:
Maybe
props
andrefs
should be converted to property descriptors? So this:Gets translated to this:
Implementations should cache when possible
Most of the time, properties will use the default assignment settings. Implementations (like stampit) should cache them so that we can copy quickly as we do today.
Presets
We could create presets. The above is the default setting for properties created by assignment, e.g., using
Object.assign()
, so this could be a preset that means the same thing:Custom presets
Another cool feature is that users could define and register their own presets:
Which you'd select like this:
Now you have a non-enumerable
instance.foo
.Formalizing names
The fixed locations don't need shortcut names (user's won't be typing them all the time), so let's spell them all out: