-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: ObjC-style classes as a superset #1205
Comments
I don't understand the proposal or even the usecase. "add more abstraction" is pretty vague. How is message passing different from function calling? I see OO inheritance in the examples. See #130 for the discussion about OOP in zig. I see named parameters in the examples. See #479 for that proposal. |
@thejoshwolfe c++/java basically use vtables for runtime dispatch methods where the exact method is not known at compile time. The Smalltalk/ObjC model the actual function to call cannot be uniquely determined during compile time and everything is resolved during runtime. Dynamic scripting languages like Ruby work like this as well (obviously). I didn't want to drag the current crop of oo languages into the discussion, since they go all in on objects. The point of ObjC is that the OO part is only used as high-level glue, rather than an attempt to make everything OO. Message passing give the right idea: encapsulated domains inside which everything is low level, but can communicate with other domains using messages. |
So... |
vtables are discussed in #130.
What kinds of domains are you talking about? And how would message passing solve this problem differently from function calls? |
A GUI library is an example where this really shines: the dynamic nature of message passing allows the generation of the UI to be partly/completely specified by a file without any recompilation. Consider the problem of a button sending a callback to the logic when it is pushed. How would you solve this problem in C/C++? With Java you can do some runtime reflection and ”sort of” get it to work. With C/C++ either generate source code, use a scripting system or add reflection on top of everything with macros etc. With ObjC it’s straightforward because you can say ”send a call to the owner of this object using the method with the name ’foo’” in a straightforward way. All object properties can be reflected during runtime etc. This differs from C++/Java, where both languages use classes as very fine-grained components. C++ in particular even make them value object. In ObjC they are more of a wrapper around C code, and doing ”objects all the way down” doesn’t really work well. |
Or dependency injection. Or a Qt-like build step. |
What would you mean is the difference between ”generating code” and a ”Qt-like build step”? Also, how would dep injection help in C/C++ unless you are doing some sort of pre-wiring? Which essentially would be some kind of reflection. |
#130 is about inheritance / object hierachies in the context of a GUI. That is not what I’m talking about. I’m talking about the capability to hot-load/reload. It’s also about being able build a generic UI tool that does not know anything about the specific code but still is able to wire together and store the UI design, which then - during runtime - is assembled from a file. That is not to say that this is not possible with, say C++, but you will end up with a lot of code just giving the UI components limited reflection capabilitied (typically: instantiating a class from name, accessing certain properties by name) Again, it’s not to say that this is not possible with C/C++, it’s just so much more work since an ObjC style superset solves those issues without extra code. |
For a smaller ObjC implementation than Apple’s, see Portable Object Compiler: http://users.telenet.be/stes/compiler.html |
see #68 for hot code swapping. A WYSIWYG gui editor is certainly outside the scope of the compiler and probably outside the scope of the standard library. But we do want to make sure we don't make it impossible or unnecessarily hostile to implement such an editor. When we implement a GUI for #130, we can think about how that GUI could be created with an editor. I don't see this as a language problem, but a userspace problem. I still don't quite understand the proposal. What I've seen so far is:
These are either userspace problems or already have open discussions. |
@thejoshwolfe I had to read this section of the Wikipedia page on Smalltalk, but it clarified @lerno's points a lot: https://en.wikipedia.org/wiki/Smalltalk#Reflection.
That said, I am not a fan of this in Zig, seeing as it almost guarantees a semi-interpreted or extremely slow language. |
@thejoshwolfe No, the whole point is whether you make a system more ”objecty” like C->C with objects->C++ but still mostly static, or if you add a scripting-like optional dynamic, very loosely coupled OO system on top, or if one plans on doing dynamics through an embedded scripting language. This is also the answer to @isaachier: like in ObjC the whole OO part is 100% optional and is used where you would use scripting or inject your own dynamic hooks. It’s the 5% glue, not the 95%. This is unlike Smalltalk where everything was an object. Again, the proposal is an ObjC solution, which is an optional smalltalk-like OO (not the Simula style OO of C++) as a superset of the normal language. |
I still don't understand the proposal. (Is this even a proposal, or just a discussion?) If the proposal is to add dynamic types to Zig, then that's probably not going to happen. I'm not familiar with Smalltalk, but that wikipedia excerpt sounds to me like JavaScript's prototype mechanism. That's way out of scope for Zig to support as a first class language feature. Of course, you could accomplish that functionality in userspace by writing your own data structures, but dynamic typing goes against the Zen of Zig in many ways. |
It seems very few here are familiar with Objective-C / Smalltalk. Basically what is needed for an implementation is (as outlined before)
So basically think of this as the language allows a special kind of struct, that has a fixed initial part which points to a class definition. For this special kind of struct there is a special calling syntax which routes the call through a special runtime instead of executing a normal function call. The runtime itself handles everything except the special struct parsing and converting the dynamic call syntax. The point is that this makes the object system 100% optional and apart from the rest of the language. It is also extremely simple, and since the object is a struct, it's actually possible to treat is as one and inspect the memory and poke around into the internals. This is extremely different from C++/Java where the OO is integrated into the language. The reason why I'm lifting this is because unlike adding vtables etc (the C++ route), this requires a much more smaller change to the language and consequently if this OO solution is picked then:
|
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
@isaachier I think you might misunderstand my motivation. Here I am just offering a different type of OO that would be a plug-in superset rather than something integrated in the language itself. The reason for raising this is simply that if this particular approach feels worthwhile (as opposed to the C++/Simula way) then one could skip the vtable approach entirely, and there would be no need to implement it in the core language. This is not me suggesting that OO should be added to Zig. This is not me saying that an ObjC OO layer is the right fit for Zig. This is saying that there is an alternative approach to OO which should be investigated exactly because ObjC worked so flawlessly for adding dynamics to C. The classes in C++ add no dynamic to C, and is little more than a tool for abstraction. So once one makes the decision, the ObjC approach should have been evaluated as well. Personally I see little value to adding C++ style OO to Zig, as the struct handling is mostly sufficient as is. An ObjC style OO would add dynamics, but Zig already covers a lot with the compile time code. |
Correct but there is no need to add either OO style IMO. The current Zig language doesn't use explicit vtables, but manages to implement dynamic dispatch using composition. I think this solution is complete as is. |
how is that supposed to work? |
@isaachier Can you elaborate on how you'd implement late bound dynamic dispatch in Zig? |
Sure. Essentially, you add the base class to the subclass as a field. This field serves as the type's vtable. It contains functions that must be set in the subclass initialization. Furthermore, these methods use a pointer to the base type as the
|
sorry if that sounds rude but thats unusable/ unreadable IMO I mean I'd rather use cpp than wade through that kind of code to get stuff done. |
You get used to it. Also, it gives you more control over the OO behavior than C++ would. Anyway if you want a C++ replacement, why not use Rust? Zig is supposed to compete with C, which has no concept of inheritance/polymorphism. CPython uses a similar mechanism to implement inheritance in its source. |
I actually disagree here. Just because it makes sense in some cases doesn't mean you have to provide for it in your language. You shouldn't try to do everything. Then you quickly end up with something like C++ which does a little bit of everything, and it's basically a giant mess. If it only makes sense in some cases people can easily work with it in inside the language. |
I'm not saying there should be no extension to the language to make inheritance simpler. However, I personally believe Zig would be complete whether or not it had those extensions. The difficulties you speak of @monouser7dig encourage the developer to use |
here is how I would implement a "getter" trait in zig at this point in time
I do not think it is just about how much you do but much more about HOW you do it. I also think we just have to wait for Andrew to build the gui app and then we will have some traits. |
You can even do it without the explicit type.
of course you can also just call .get() :P I'm actually not sure what the function does for you tbh. As for the C++ comment I meant that everyone has their own opinion of how to do stuff and so every C++ code looks quite different. While for example with C it's often quite similar, because you don't have that many (straightforward) options to do things. |
sure but the same is true for go although they have options for getting some OO into your code if you really want to
thanks !
|
@monouser7dig I think you are referring to type constraints. You would like a compile-time limitation on the type of
Instead of using the type |
@monouser7dig I see. I've never done any Go programming, so I wouldn't know about that. |
@BarabasGitHub @monouser7dig Go struct embedding is actually lifted from an extension in the Plan9 C compiler and I've love to see Zig use that. See more here: https://gcc.gnu.org/onlinedocs/gcc/Unnamed-Fields.html. |
@isaachier yes I may mix up polymorphism and interfaces here a bit, sorry For me there really are two issues:
type Person struct {
name string
age int32
}
func (p Person) IsAdult() bool {
return p.age >= 18
}
type Employee struct {
position string
}
func (e Employee) IsManager() bool {
return e.position == "manager"
}
type Record struct {
Person
Employee
}
...
record := Record{}
record.name = "Michał"
record.age = 29
is just like type Getter interface {
func get() float32
}
func getter(instance Getter) f32{
return instance.get();
} the only difference is that once you have interfaces or traits or something, you can use that interface as a function parameter type instead of just So the main thing that interfaces could bring to zig is more readable and self documenting code. |
@monouser7dig sorry but I must stress interfaces are not type constraints either. The big difference between polymorphism and type constraints is whether it is a runtime or compile-time concept. Type constraints are in Haskell and to an extent in C++, and occur in Java as variance. They are compile-time constraints on the type |
And then one wonders about higher order types. This seems way too bikeshed-able for the current proposal. A compare/contrast with alternatives like in Go's proposal process alternatives may help. |
I mean the dynamic polymorphism you are talking about which can only be determined at runtime can only happen if you have inheritance which nobody wants to have.
|
You haven't shown how ugly the current Zig solution would be. |
isaachier has provided those examples |
What about GUIs and runtype type creation? It would be nice to see the discussion captured in the proposal comment. @lerno? |
According to @lerno, the Objective-C compiler does a lot of work to prettify its dispatch mechanism. The Zig compiler would need to add this new layer for him to provide an example. |
what kind of code should that be? I'm afraid that is absolutely not something that is desirable If you have a GUI and you have an Interface with a draw method then all your buttons and sliders can happily implement that draw method etc. and everything that wants to do work with those GUI elements just takes a Drawable or any other needed Interface or currently in zig a |
I mentioned runtime types because Smalltalk was brought into the conversation. |
I won't rehash in detail how ObjC can work with an GUI. It's straightforward to look at some simple tutorial on UIKit. The interesting part is how straightforward UIKit / Cocoa uses reflection and dynamic dispatch:
If we compare this to C++ and possible solutions:
or:
Similar for callbacks, any names need to be registered somewhere, and obviously argument types etc needs to have some sort of way to encapsulate them etc. Also notice how messaging to "id" allows duck typing:
Creating proxy objects is consequently also near trivial for ObjC since an object can pretend to be of a different class and dynamically respond to requests. Note that much of this is possible in Java. The difference being that Java's reflection is x100 as heavy to use. |
Zig has two things you can already leverage here:
@thejoshwolfe pointed out far above the discussion that in issue #130 there was a discussion of many implementation techniques for things like vtables that could be done now. That is just one way to implement dynamic dispatch. There is some sample code. If you can figure out a way to support duck typing then I think you can cover most of what you are asking for. Mock/proxy objects are covered. The archetype used for OO inheritance discussion, the Shape object with the draw method can be covered (just make all the concrete shapes implement draw). Etc. I suggest trying to see how far you can get. If there are small additions to Zig that would be necessary to implement most of this in library code, those would have a pretty good chance of getting accepted because they would have a good use case and would be minimal. Look at the code in #130 and I think it may help spark some ideas. |
@kyle-github was that directed towards me or someone else? The discussion is a bit hard to follow right now... |
@lerno yes, that was toward you :-) |
@kyle-github without syntax, the code would be rather boring:
|
I'm just close this one then. |
This is a rather crazy suggestion, but I just wanted to make it so that it's all discussed. This is more of a 3.0 feature than 1.0, but I think it would be worthwhile to discuss, since it shapes how one thinks of structs. If they're the only tool, then it's necessary to add a lot of features, otherwise any dynamic feature might be caught by this proposal, if this is the way to go.
Objective-C in it's original version is essentially a pre-processor on top of C using the message passing paradigm of Smalltalk.
The only building blocks are:
= Foo : Bar { unsigned int memoryAddress; }
into a special type of struct. Add a registration of the class at startup.- name:part1:part2 { ... }
into C functions with an implicit self pointer, and add registration of the function at startup+ name:part1:part2 { ... }
into C functions with implicit self pointer set to the class, and add registration of the function at startup.[foo name:a part1:b part2:c]
=>send_msg(foo, "name:part1:part2", a, b, c)
Note that NeXT/Apple version added more bells and whistles to this, but the basic idea that Cox had was that this OO was large scale OO, and everything else was plain C. Unlike C++, which was an extension to C, this was more about making an in-language scripting language to C. A way to glue C code together easily. For C / C++, dynamic code is like pulling teeth, whereas for ObjC it's straightforward. What ObjC SHOULDN'T be, is for doing low level programming.
My suggestion is considering adding an OO message passing layer on top of Zig using a similar scheme as Brian Cox's original OO extension to C. For example:
This would compile to something a bit similar to:
Note that this is also the opposite of Swift's approach (unifying structs / dynamic classes)
P.S. Please overlook the poor examples here and try to consider it on a more general principle:
"How should the language handle cases where you want to add more abstraction?"
The text was updated successfully, but these errors were encountered: