Skip to content
This repository was archived by the owner on Jan 25, 2022. It is now read-only.

Reflection over fields #204

Closed
zenparsing opened this issue Jan 5, 2019 · 25 comments
Closed

Reflection over fields #204

zenparsing opened this issue Jan 5, 2019 · 25 comments

Comments

@zenparsing
Copy link
Member

I would like to start a conversation on how we might provide a reflection mechanism over class instance fields in the future. I'm not proposing any changes for this proposal.

The general idea is that in many object oriented languages, the fields declaratively defined within a class body are available via reflection APIs. This proposal adds a [[Fields]] internal slot to constructors but does not provide a way to reflect over that list. I can see use cases potentially developing.

First, are there compelling reasons why we would not want to provide reflection? (We can assume from the start that nothing "private" will be made available by such an API.)

Second, regardless of the answer to the first question, what might such a reflection API look like? I'll throw a couple of possibilities out there:

  • Reflect.fields(obj): Presumably this would go along with an extension of the MOP (meta-object-protocol).
  • constructor[Symbol.fields]: This would require no extension to the MOP.

Thoughts?

@Igmat
Copy link

Igmat commented Jan 5, 2019

Why not both well-known Symbol.fields and Reflect.fields?

@bakkot
Copy link
Contributor

bakkot commented Jan 5, 2019

Hm. Is there a situation for which Reflect.ownKeys would not be adequate? The only thing I can think of is situations in which an instance of the class is not on hand, which I suppose might be enough motivation on its own.

First, are there compelling reasons why we would not want to provide reflection?

The only thing which comes to mind is that it would expose things which ought to be implementation details, namely, the difference between class A extends null { x = 0 } and class A extends null { constructor(){ this.x = 0; } }. I don't know if that's compelling.

@Igmat
Copy link

Igmat commented Jan 5, 2019

I think that reflecting over class, and its fields before creating new instance may be very usable, especially for some kinds of metaprogramming libraries.

@littledan
Copy link
Member

My hope was that decorators would be the basis for reflection over fields. In particular, a few years ago, @wycats proposed that we make a reflective mechanism to add fields to classes. I am very concerned about imperative mechanisms to change what happens inside a constructor later! Decorators now permit fields to be added, but only before the constructor function is born.

Ultimately, I see the set of fields as a sort of implementation detail of a class, similar to whether a function is async or a generator. We aren't keeping it super secret (you can tell with function.prototype.toString), but it's really the same thing to have a class with no field declarations that creates properties in its constructor, just like the difference between async functions and Promise-returning functions is an implementation detail, and generators vs iterable-returning functions.

@mbrowne
Copy link

mbrowne commented Jan 6, 2019

It might be helpful to make a distinction between merely getting information about fields and actually changing their values or descriptors at run-time. If the native reflection API were read-only, would that mitigate some of your concerns @littledan? Here's a simple example use case for read-only reflection of public fields—it could be useful to have this facility for private fields as well: #36.

But there are also use cases that require actually changing (or at least initializing) the value of private fields via reflection. I gave an example in tc39/proposal-decorators#191.

Let's think about the pros and cons of a native API vs. using decorators for this. One advantage of a native API capable of both reading and writing private fields is that you would never have a situation where you're switching back and forth or combining the native API with some userland implementation. And there are already native APIs for reflection of public properties, so all reflection-related facilities would be grouped together in a consistent way.

@mbrowne
Copy link

mbrowne commented Jan 6, 2019

Note: I edited my above comment—I remembered that my first example used only first public fields. I can write a new code example using private fields if it's unclear why someone would want this feature.

@ljharb
Copy link
Member

ljharb commented Jan 6, 2019

A reflection mechanism for public fields - that allows read-only access to their initializers - seems very useful to me. In the same way as i can extract a getter, i should be able to extract an initializer.

I feel like a (non-Reflect, since it’s not directly a proxy trap) method on Object would work just fine - Object.getOwnFields - but the tricky part is, what would you pass into it? An instance no longer has any fields - so the only options that make sense to me are the constructor and the prototype. Since changing the prototype doesn’t change the fields, that suggests the constructor is the proper place to have the fields “live”.

So, I’d expect Object.getFields(SomeConstructor), which throws on non-constructors, to return an object of property name -> descriptor object, where the descriptor object contained the initializer, and a companion getField(constructor, name) to get a single descriptor. An alternative API would be getInitializer(s), which directly returned the initializer function, but i suspect we’d want the object to be able to carry more metadata.

@zenparsing
Copy link
Member Author

Object.getFields has a nice ring to it, but if it were Object.getFields or similar it would have to bottom out at some essential method of the MOP. So either it would need to be available via existing essential methods (for which there are already Reflect functions) or we'd have to add a new one.

@ljharb
Copy link
Member

ljharb commented Jan 6, 2019

That’s a fair point; that suggests we’d need a proxy trap, and that thus Reflect would be the place to put the api method.

@mbrowne
Copy link

mbrowne commented Jan 6, 2019

@ljharb Is it still a possibility that there might be a way to declare private fields on object literals in the future? If so, then obviously a reflection API that requires a constructor as an argument could only be used with objects created by classes and not from object literals. Also, JS has historically been more object-oriented than class-oriented, meaning among other things that an object might acquire additional properties/methods after it's instantiated, beyond what's defined in the class. I don't know if similar freedom should ever be granted to fields (especially private fields), but in the interest of designing for the future (just in case), I think it might be important to offer reflection not only for classes but also individual instances.

I realize that field definitions would actually be stored in the class and not copied to each instance (which would be a silly thing for the engine to do). As a counterpoint to what I said above, it's quite reasonable to say that if you want to reflect on declarations inside a class, the class (specifically the constructor function) should be the target of your inquiry. I'm just not sure how that concept would extend to individually varying instances—trying to think ahead.

@ljharb
Copy link
Member

ljharb commented Jan 6, 2019

@mbrowne we're talking only about reflecting on public fields, and it wouldn't make sense for an object literal to have public fields. There must not be any reflection on private fields :-)

@mbrowne
Copy link

mbrowne commented Jan 6, 2019

There must not be any reflection on private fields :-)

I agree with the policy of no reflection on private fields by default. But why not as an opt-in feature (where you would explicitly opt-in a particular class)? Or would you view that as redundant since it will probably already be possible with decorators?

@ljharb
Copy link
Member

ljharb commented Jan 6, 2019

If one makes their own class’s private fields reflectable, then why use private fields at all? (or, why not use a decorator to make them symbols, or something)

@rdking
Copy link

rdking commented Jan 6, 2019

Here's a thought. What of a means only accessible within the class itself to get the entire collection of fields, public, private, and inherited?

@mbrowne
Copy link

mbrowne commented Jan 6, 2019

@ljharb Using a decorator to make them symbols would probably work for the use cases I'm thinking of (in fact that's probably how I would implement a @reflect decorator [or maybe just call it @symbol to be more to the point]). But I though the premise of this thread was to explore the possibility of a native reflection API and not necessarily leave everything to decorators.

Also, can anyone think of a reason why you might want to have both reflectable private fields and symbol-keyed properties (for some other purpose) within the same class? I can't think of anything and it seems a bit odd, but thought I'd ask in case I overlooked something.

We can return to private fields later if that's more constructive; I didn't mean to start a distracting side discussion.

@rdking
Copy link

rdking commented Jan 6, 2019

@mbrowne

Also, can anyone think of a reason why you might want to have both reflectable private fields and symbol-keyed properties (for some other purpose) within the same class?

There's a more core question that has to be answered before your question has an answer. Does making a private field reflect-able mean it is also publicly accessible?

If so, then there's no difference between a Symbol-keyed field and a reflectable private field. If not, then there is a distinct difference, and we'd have to define what it means for a private field to be reflectable.

@ljharb
Copy link
Member

ljharb commented Jan 6, 2019

Reflectable is publicly accessible.

@rdking
Copy link

rdking commented Jan 6, 2019

@ljharb In most cases I would agree with you, but I was thinking of a case (like testing) where reflectable would only mean visible (as in readable, but not writable).

@ljharb
Copy link
Member

ljharb commented Jan 6, 2019

Publicly accessible doesn’t mean publicly writable, it means accessable. Read-only counts.

@rdking
Copy link

rdking commented Jan 6, 2019

Fair enough. Then there's no difference between a reflectable private field and a Symbol-keyed private field.

@littledan
Copy link
Member

Yes, read-only reflection is different from a field-adding API; I shouldn't falsely conflate those. But, I am missing something: what are some intended use cases for this API?

@littledan
Copy link
Member

@zenparsing How would you feel about pursuing reflection in a separate proposal? That's what we are doing for my preferred proposal in this space, decorators.

@zenparsing
Copy link
Member Author

I think the discussion here has satisfied the goals of the OP. Thanks everyone!

@mbrowne
Copy link

mbrowne commented Jan 6, 2019

I would like to follow future discussions about adding a reflection API for fields. It sounds like a separate proposal might eventually be created for that. It would be great if someone could post a comment here if/when that happens (although I realize you might not remember so I'll try to keep an eye out for future proposals regardless).

@robpalme
Copy link
Contributor

robpalme commented Jan 6, 2019

To answer the very first question: another reason to not offer reflection on public fields is that it would reveal which constructor in the inheritance chain defines the field, which may impede refactoring.

IMO it is slightly more JavaScripty to just deal with the final instances, rather than to be able to inquire in detail about the ancestry of each property.

This is a very mild opinion.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants