Proposal: Metaclasses #195
Replies: 21 comments
-
So you want to move the responsibility from the factory to the classes that only need to have knowledge of one thing and focus on one thing and that is representation? why reducing and simplifying the set of classes is a clear advantage? it might be a clear advantage when you don't mix responsibilities, otherwise; I'd say it isn't.
Factories shouldn't know anything about how things work and if they do then it's a design smell; if there's a leak in the implementation it might mean that this class is doing too much and may need to be refactored. Besides, allowing subclasses to know about their parents may break the inheritance chain. inheritance is already a strong relationship and you wouldn't want to make this relationship even stronger by essentially turn every inheritance into a God object. Besides this I don't know how virtual and static fits together, what would it mean in terms of implementation? |
Beta Was this translation helpful? Give feedback.
-
There are extension static member and static interface that would easily enable factory implementation on any class and I think that would be better |
Beta Was this translation helpful? Give feedback.
-
It seems to me that shapes (#164) would solve this problem, even if they use different syntax (types are passed out as generic parameters, not as values). |
Beta Was this translation helpful? Give feedback.
-
The complexity of a system is a function of the number of parts. Less parts means less complexity. Less files to control version, less code to review, etc. Of course a component can become cluttered, but the specification is not prohibiting developers to build factory classes. :-)
Yeah, right. And the
Can you elaborate? I don't see any difference between a subclass overriding a non-static method (as allowed today), and overriding a static method, provided the base class properly declare the method as |
Beta Was this translation helpful? Give feedback.
-
You reduce the overall complexity in the expense of increasing the coupling between classes and also lower their cohesiveness so I don't think that this is a good trade off in terms of maintenance.
Just because a type is marked with I'll cite you:
A solution exists, the other part can be solved through a library, isn't?
Use
In C# instance method calls are polymorphic whereas static method calls aren't so what exactly does static virtual/abstract mean? |
Beta Was this translation helpful? Give feedback.
-
You seem to be new to the concept of metaclasses in OO world. You should check Smalltalk, Delphi, Python, Objective-C and TypeScript. They all support metaclasses in one way or another. I picked Delphi style because IMO it's the most elegant and compatible with C#, and is extremely successful. That said, I do agree that #164 might replace metaclasses, although the scope of that is much greater than this, in one or two orders of magnitude. Now to your comments!
There is no such thing as "between classes", because instead of factory and item classes, you have a single class definition - the item class. Note that any
I'm not talking about
Ok, it seems that here is where lies the greatest pain point. Regarding a method definition, there are two dimensions to consider: scope and type resolution (of course there are other dimensions like visibility, but those are irrelevant in this discussion).
These two dimensions are independent, giving four combinations:
The fact that C# doesn't support Some people talk "if is static, it can't be virtual, doesn't make sense!!!". This comes from the wrong assumption that Delphi Object Pascal, which was designed by Anders Hejlsberg, avoided the
The above is 100% equivalent to C#:
As you can see, Delphi removes all confusion surrounding the static term. IMHO, Delphi respects lectures better, as "static linking" traditionally also applies to instance methods. A virtual method invocation used to be called a dynamic dispatch, and it doesn't matter if the scope is class or instance. Since Delphi does support metaclasses, it also does support static virtual - yes, it's been 20 years since Delphi supports all four combinations! There is only the idiomatic and syntactic difference - methods are called "class methods" there, not "static methods". Frankly, I don't care much if C# will ever support metaclasses. I just wanted to add it to discussion. The shapes proposal looks superior indeed, but there are far more challenges to get that working. Metaclasses are much less risky and can be an alternative if shapes get into some blocking issue, or if it gets trimmed down to a point where metaclasses are still relevant. |
Beta Was this translation helpful? Give feedback.
-
That said, a language like C# can work around that limitation. Oxygene does, for example, support metaclasses. It does so by emitting two types for every class, the class itself and the metaclass on which the static members are defined as virtual instance members. When inheriting from that class an additional two classes are created, and the metaclass overrides those virtual instance members. When you pass the metaclass around you're actually passing an instance to the metaclass type, and when you invoke its "static" methods you're making virtual calls on that instance. It serves Oxygene but of course it's not at all seamless to other languages. I'm sure that you mentioned Anders due to his role in designing both Object Pascal and C#. It's not like C# was designed in ignorance of metaclasses. |
Beta Was this translation helpful? Give feedback.
-
I'm not new to the concept, I just can't see the value in it.
I meant that if you make calls from one class to another directly then you increase the coupling between them which most of the time isn't desired.
Yes, static method can access private members given that the static method exists inside the class it creates an instance of so in this case you're right.
Yes, you can call static methods using reflection, what does it mean? does it mean that static methods are or can be polymorphic? Can you elaborate how #164 solves the problem exactly? I know how it can be "solved" but don't understand what exactly "call using the generic type parameter" means.
Did I say otherwise? I said that in C# it isn't the case.
Yes I know that but I wouldn't really call it a static call, I mean as far as I know neither Assembly or IL have an explicit "static" instruction call but what's the point in this statement? I mean this fact alone doesn't provide any sort of polymorphism so how does it help?
It's all great but C# isn't Delphi and then there's the CLR that I think doesn't support this but I could be wrong unless static virtual would be instance methods in disguise or something. :) All in all there's much jargon and not an answer to this simple question: "A solution exists, the other part can be solved through a library, isn't?" |
Beta Was this translation helpful? Give feedback.
-
Please tell something new. If you actually read this spec, you may encounter things you find interesting:
So yes, I did think on vtable. Anyway, thanks for going deep into this. Sometimes I miss this level of knowledge in these discussions.
I never said that. What I'm not sure is if metaclasses were left out deliberately, or due to budget limits. I would bet in the second alternative. It's clear that C# were designed to allow any Java code to be ported, as there is a 1-1 mapping to almost every Java feature! The object type even has hashcode! It would be nice if it allowed porting from other languages as well, such as the ones that support metaclasses! |
Beta Was this translation helpful? Give feedback.
-
Thanks Mr Obvious, but I can't see how this is related to this spec, as we are talking about a single class. I'm not sure if you internalized the following idea: Sometimes people don't want to use the Factory pattern. There are cases where the Factory class is nothing more than a virtual method calling a constructor. The only reason to use a separate class is precisely the lack of metaclasses. Many factory classes are nothing more than manual implementation of a metaclass. This spec explicitly tells that without metaclasses, people are forced to use two classes, even if that is not required. Side note: among factory pattern and metaclasses, which concept do you think is older?
I agree that the (overly exposed) simplification value might be low enough to left this out. But how about porting code from languages that support metaclasses? The capacity to accept migrations is an important value in any language.
Consider the following call:
Without optimizations, this is roughly translated as:
Did you note how the generic type parameter
Repeating my previous comment, #164 allows a call to a static method through the generic type parameter. It became a polymorphic call without explicit use of reflection. Did you get it now? There are still big challenges on #164, for instance, how to make binding quick enough, how to provide a fast reflexive version of |
Beta Was this translation helpful? Give feedback.
-
Have a nice day. Edit: Just two tips that may or may not be so obvious to you:
|
Beta Was this translation helpful? Give feedback.
-
@eyalsk Thanks for the feedback. I try to show respect by carefully reading, understanding other people PoV, researching, answering in details and letting clear what I agree upon. I might have lost patience with a few demonstrations of lack of consideration for what I thought was very explicitly written. This impatience resulted in some hostility from my side, which I mistakenly took for nothing else than informal talk. But I never meant anything in detriment of anyone. So please accept my apologies. |
Beta Was this translation helpful? Give feedback.
-
It's not about that you failed to express your opinion or the concept behind the proposal but you need to understand that when you create an open/public discussion different people will have different opinions and may disagree with you, you should be open for good/negative feedback and the replies may not be the kind you expect and so by being defensive you only shoot yourself in the foot thus missing the opportunity to learn or otherwise, interact with people with different opinions. Finally, if you want to end a discussion you can always state that you disagree or don't reply at all, personally I prefer the former approach but either is better over getting personal and aggressive, after all I think that we're all here to participate in the design of a language we love, contribute, learn, have fun and form a great community, at least that's why I'm here. :)
You state that the only reason we separate the factory class from the class it needs to create is done due to lack of metaclasses but there are more reasons to it, for example, breaking responsibilities. Patterns emerge after a solution to a recurring problem is made and finally identified as such so I'd say metaclasses probably preceded the pattern. My issue with this proposal is not strictly with factories but with the fact that any class is a potential factory and is offered through static calls, promoting this idea in my view, just in my own view doesn't lead to good design as far as implementation goes. I honestly think that this proposal will require a lot more than just a change to the language such as the CLR and maybe the framework itself if reflection integration should be added so when you consider the benefit vs effort then in this case I think that the costs may outweigh the benefits but then I could be wrong.
Sure, but it really depends how far you want to go with this and who is your primary audience.
You're right you wouldn't have to use reflection because the generic type parameter is of a specific shape but you're wrong about the polymorphic call part, it would still be the same normal call to a static method. |
Beta Was this translation helpful? Give feedback.
-
I'm not being defensive, I just lack patience sometimes. I expect people to spend time studying and researching as I do, but that's not a good way to deal with a discussion. So thanks again, lesson learned.
The "only reason" is in the context of that paragraph, which tells "Sometimes ...", "There are cases...". Let me explain again in a more detailed way: Sometimes the creation logic is too small to justify breaking responsibilities. In these scenarios, the only reason to split in two classes is to add polymorphysm, for which metaclasses would suffice. Also, you are focusing too much on the Factory pattern, but that is only one of three scenarios where metaclasses can be useful. The two others are Singletons and Metadata (please see the spec).
What is a polymorphic call for you? For me, is a call where the destination type is resolved at runtime (see here). It's not something limited to virtual method calls. Virtual method calls are subsets of polymorphic calls. There are even people telling that C++ templates are polymorphic despite being statically linked, though I don't think is appropriate. A "normal static call" is a call where the type is known at compilation time. In this sense, #164 precludes that call to be "normal". The caller and callee might be at different assemblies, preventing the compiler to know each other. Stronger: The caller can make the call through an interface, making impossible for the callee to know the destination type before instantiation, which might even happen through reflection. Setting jargon aside, the benefit of polymorphism is that a program will work with different types. So even if we disagree about what is a polymorphic call, we are talking about the same benefit, which should be enough to settle the proposition value. EDIT: Rephrased to cover responsibility break. |
Beta Was this translation helpful? Give feedback.
-
The assumption that people don't spend the same time on the topic and research it as you do is one that you can't prove and more importantly puts you in a bad position because it makes you think that you know more thus closing yourself for further input. There are plenty of people here from researchers to developers that not only read it but live it everyday.
I disagree with some of your definitions, I think they are far from accurate but if #164 fits your needs then I guess there's nothing to discuss. 😉 |
Beta Was this translation helpful? Give feedback.
-
That's not quite correct. Delphi distinguishes between static and non-static class procedures. The former are much like static methods in C#, but the latter have an implicit
This could be replicated in C# as follows, with the metaclass made explicit:
Static class procedures are indeed equivalent to static methods in C#, but non-static class procedures are represented as instance methods of the metaclass; so are virtual and non-virtual constructors. A subclass would look like this in Delphi:
And in C#:
Type registration in Delphi:
...in C#:
Usage in Delphi:
...in C#:
|
Beta Was this translation helpful? Give feedback.
-
Proposal that would add metaclasses to C++: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0707r0.pdf |
Beta Was this translation helpful? Give feedback.
-
I like the general idea from the OP, but cant agree with the implementation. In Python for instance, everything is a first class citizen, so Animal and Class are actually objects themselves. With this in mind, the example code can be rewritten as below:
You see, this updated implementation has no static methods, the implementation is vastly similar to the python example you can find from wikipedia. Unfortunately, it will not be possible in recent future since this requires C# to allow classes to be first class citizen like Python. By definition, metaclasses are classes of classes, so classes themselves are objects. Without classes as first class objects, it wont really be possible to implement true metaclasses, and you can only use static methods as inferior workaround. So the point to take here is that, dont even worry about metaclasses until one day C# will have classes as first class citizen, which I aint sure when it will happen. We will see. Reference: https://en.wikipedia.org/wiki/Abstract_factory_pattern#Python_example |
Beta Was this translation helpful? Give feedback.
-
IMO there are places where metaclasses might be a benefit but I think nearly all use cases are already solvable through other abstractions. public abstract class Animal {
protected Animal(string name) { }
public abstract string Speak();
}
public class Dog : Animal {
public Dog(string name) : base(name) { }
public override string Speak() => "Woof";
}
Func<string, Animal> factory = name => new Dog(name);
var myPet = factory("Rover");
Console.WriteLine(myPet.Speak()); As far as I can tell that C++ proposal is unrelated to this one. It'd be more closely related to source generators implemented via templates on steroids, C++ code generating C++ code to augment types at compile-time. |
Beta Was this translation helpful? Give feedback.
-
I agree that in many circumstances it is totally possible to replace metaclass by another technique, but anyway my response was just a direct message to the OP on the implementation details for metaclass as a feature. It assumes that if we are going to implement metaclasses, what the syntax we want it to look like, and what prerequisites are needed for it to work. I am not here to discuss the use cases for metaclasses, or whether we should implement metaclasses or not. If anything, I hope C# will consider first class types, so classes will become first class citizens just like objects. But it will be a different topic. |
Beta Was this translation helpful? Give feedback.
-
The main benefit of metaclasses is that it removes verbosity, ambiguities and incorrectness of factory pattern implementations. It's not a must-have language feature, but the benefit becomes clear when you replace lots of factory classes by metaclasses in your code base. Of course, this requires language support for metaclasses in first place. It's like migrating from VSS or CVS to GIT: you have to actually use in order to feel the benefits. The difference is that GIT is there for anybody to try. Metaclasses are not there, at least in C#. With such low external demand, I don't think this feature will be implemented in the short or mid term. I have added this because there is at least one programming environment that makes heavy use of metaclasses with very clear benefits: Delphi Object Pascal and its visual control library. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Smalltalk-80 introduced metaclasses, and Delphi made them elegant enough. See also here.
The following examples explain a metaclasses proposal for C#.
Metaclasses vs Factory classes
Without metaclasses, we are forced to develop factory classes:
With metaclasses, the class itself can be a factory:
The clear advantage is reducing and simplifying the set of classes. Another advantage is that since static methods can access private non-static members, there is less exposure of implementation details that would otherwise be visible to factory classes.
Metaclasses vs Singletons
Without metaclasses, one needs to verbosely describe singleton instances and prevent objects from being instantiated with a private constructor:
With metaclasses, there's no need for the singleton pattern verbiage:
Metaclasses vs Attributes and Reflection
Without metaclasses, one can use attributes or reflexive members as way to capture class-specific information:
Attributes have the disadvantage of being non-flexible - the value cannot change after compilation. They are also an additional artifact. Reflection solves that problem, but makes code difficult to read, as the editor can't find any reference to a member accessed through reflection.
Metaclasses solves both issues:
Specification draft
Foo
, there is optionally an additional type calledmeta Foo
, which is the metaclass ofFoo
.meta Foo
is derived fromSystem.Type
. Therefore, a variable of typemeta Foo
can be used to retrieve type name, assembly, etc.typeof(Foo)
returns an object of typemeta Foo
. Since that derives fromSystem.Type
, there is no hard compatibility break.Bar
derives fromFoo
, thenmeta Bar
derives frommeta Foo
.System.Type
.meta Foo
can be used to access static members ofFoo
.Foo
can have overridable static members. The accessFoo.Member
is static, however the access((meta Foo) someObject).Member
is polymorphic. This is the key feature of metaclasses.Foo
, the compiler only generates a metaclass ifFoo
declares some overridable static member, or if some base class is generating a metaclass. This prevents cluttering the assembly with unused types.meta
is used with a class that does not produce a metaclass.Beta Was this translation helpful? Give feedback.
All reactions