Is Go an Object-Oriented Language?(spf13.com) |
Is Go an Object-Oriented Language?(spf13.com) |
For example, http://play.golang.org/p/EmodogIiQU
type A struct { }
type B struct { A } //B is-a A
func save(A) { //do something }
b := &B{}
save(b); //OOOPS! b IS NOT A
If Go had is-a relationships, the code above would be valid. Instead, Go only implements has-a relationships, and simply provides shortcuts to calling B.A.foo() as B.foo().One could create B.save(), which would call save(b.A), but the very reason you're now proxying the call to save() is because there is no is-a relationship in Go.
We all know about interfaces, but the problem is that is-a relationships do exist, and you can't always use interfaces, because often you want to share the data encapsulated by the objects, not only the behavior. One ends up creating methods to fetch each piece of data, but in code that is supposed to be performant, calling methods instead of accessing fields is suboptimal.
1) Very shallow inheritance trees that could have very easily (and more logically) been replaced with interfaces. For example, NSResponder being everything's superclass even though just about all of its methods are empty implementations ("subclassers responsibility"), aka, it clearly should have been an interface.
2) Confused/strangely complex is-a relationships (mutable array is-a immutable array, what? so if I specifically specify NSArray, I may still get a mutable array and the type system will be happy???).
3) Strange rules around what methods are overridable, and more importantly, how specifically they can be overridden. Why can't I in a UIView subclass override -subviews to ensure that it always has the same subviews? Well, implementation detail, that's why. Normally this would be fine, but since anyone is allowed to muck around in a subclass, suddenly I need to know the way it was implemented. This of course conflicts other parts of the framework where you are definitely expected to override the method and not use a setter.
I have yet to be presented with one of these "killer" is-a relationships that must exist. If the sole excuse is "performance", then sure I'll conceded. I guess I don't work in environments where member access is the performance bottleneck so I guess I can't relate.
To my knowledge, nobody has found any better design than this, though some have found worse ones (e.g. HTML and its bizarre input element).
To your specific points:
1. Yes, NSResponder should definitely have been an interface. See for example the weird way that documents and delegates are spliced into the responder chain.
2. The fact that NSMutable* is-a NS* is a lovely design. Mutability can be understood as adding setters to a class that doesn’t have them. The alternative seems to be weird duplicative splits like ArrayList/Array, or String/StringBuilder/CharSequence.
3. Agreed; it’s a failing of ObjC that it’s usually unspecified whether a method is designed to be called, to be overridden, or both. I wish this were enforced at the language level.
The flow tree (render object tree) in Servo (or any other browser engine) must use inheritance: we have a heterogeneous tree of objects that all share a common set of fields (position, intrinsic widths, collapsible margins, some various bits that store state during reflow), but they all use virtual methods because they must lay out their contents differently.
We can't use composition because we wouldn't get virtual methods. We can't use an interface because then we would be forced into virtual dispatch for all of those fields that are shared between flows.
Rust doesn't have OO yet either, so we're forced to hack around it in weird ways (usually via a small amount of unsafe code to simulate inheritance).
> I have yet to be presented with one of these "killer" is-a relationships that must exist. If the sole excuse is "performance", then sure I'll conceded. I guess I don't work in environments where member access is the performance bottleneck so I guess I can't relate.
A browser engine is exactly that sort of environment. Forcing all member access to go through virtual dispatch would murder the performance of any browser.
Note that this was exactly the sort of thing that OO was designed for in Simula: heterogeneous trees of objects that all share some common fields but have different virtual methods. This generalizes to GUI libraries, game worlds etc—in short, simulations :)
† neither of which exist, since there's no objects and no inheritance. In simplifying things, this brings a constraint. Given how method resolution is a pain point WRT implementation complexity and performance (see e.g Ruby), this is a reasonable tradeoff. I am glad we have such an interesting choice of languages.
No need for methods, is performant. I can't think of a reason--besides extra typing--why this wouldn't be sufficient.
But it's not sufficient if what you want is to treat a B as an A. There is no is-a, so you have to keep treating a B as something different from an A.
type Saveable interface {
...
}
func save(a Saveable) { .... }
save(a); // ok, assuming A is Saveable
save(b); // necessarily ok, given than A is SaveableImplementation inheritance is a property of _some_ OO languages and one that is hard to imagine separate from OO. Which is why perhaps so many insist upon it being a required property for some language to be called OO. I am firmly in the camp that thinks implementation inheritance is a bad idea and it is best to avoid it even in langauges that support it. Thus I don't agree with anyone who claims that it is an important characteristic of a language.
Whether object instances find their genesis in classes, factories or prototypes are IMO the least important aspect to consider when discussing whether or not some language is truly OO. It's the object instances that do the important work. Where they came from is not so interesting.
Make it dynamic polymorphism, and I agree.
http://en.wikipedia.org/wiki/Subtype_polymorphism
http://en.wikipedia.org/wiki/Dynamic_dispatch
Polymorphism is a feature of the type system, and thus inherently static. Dynamic dispatch is something you often wind up with as a consequence of subtyping, but neither requires the other, strictly speaking.
Class is a single construct used for three different abstractions, namely:
- modularity/hiding
- inheritance
- polymorphism
This eventually turned out to be a bad idea (as evidenced by all the mess with virtual methods, multiple inheritance and structural patterns), and interfaces (and namespaces) were added to partly remedy this.
In Go, you instead get three orthogonal constructs:
- modules
- embedded structures
- interfaces
These directly correspond to basic principles of OOP. Nice and clean.
type Shape interface {
Area() int
}
type Square struct {
sideLen int
}
// Somehow denote that this function is implementing
// Shape's Area function
func (s Square) Shape.Area() int {
return sideLen * sideLen
}
Because, one of the things I like best about Go's interfaces is that you don't have to do that. type Person struct {
Name string
Address Address
}
is equivalent to type Person struct {
Name string
Address
}
And in fact, these are equivalent too: p.Address.Zip = "01313"
p.Zip = "01313"
http://play.golang.org/p/aKH3YxT5MbGo doesn't really support is-a for structs, as pointed out elsewhere in the comments here. Interface implementation is the only way to get the sort of "this type can be substituted for this other type" idea that is-a inheritance provides in other languages.
I would say that similar objections would be made about Go calling it self 'object oriented' however I also don't know what is being asserted.
Go has many constructs that make abstraction easier, and that is what many programmers want out of the OO idea, so its fine. I'm sure there specific things that some people require before they will label something as OO. Is it a functional question or a religious question as to whether or not Go is Object Oriented?
So I want a few different, but similar things. These are actually stages in a processing pipeline, each stage doing different processing steps.
What I'm currently doing, which mostly works well, is to have a struct type ('Stage') which does all the generic work (equivalent to an abstract base class in C++). The Stage contains a function ptr ('Each') to actually do the processing step.
I can then have various 'derived' types which embed 'Stage'. Each one is assembled via a ctor which sets up the 'Each' function ptr. Effectively this provides inheritance with method overloading for the Each function.
I also have an interface ('Stager') which is satisfied by the Stage type, and so consequently by all the derived types (since they embed Stage).
So, I seem to have most of the benefits of C++ abstract base class and 'inheritance' (of data and methods), including overriding of methods by subclasses (using explicit assignment to function ptr).
It feels pretty nice to work with. The main concern I have is the slight klunkiness of the Stage/Stager duality. I also don't think I'd like this if I had many overridden methods (I just have one atm).
Anyone care to comment on a better way to this or other critique?
Who said a standard definition doesn't exist? From Alan Kay, who 'invented' object-orientation and coined the term:
> OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things. It can be done in Smalltalk and in LISP. There are possibly other systems in which this is possible, but I’m not aware of them[0].
Yes, you can make the argument that the term has evolved in common parlance beyond what Kay originally conceived of, but it's silly to propose a "modern" definition of object-orientation and not at least mention the original definition.
[0] From a 2003 email: http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/doc_kay...
[1] Note that he does not mention Java, even though he write this during the height of Java's popularity: http://www.tiobe.com/index.php/paperinfo/tpci/Java.html
It may be "silly" not to mention the original, but in this context the original a distraction: the original definition would exclude pretty much every language we today tend to consider object-oriented.
Also I believe there may be a typo in your last paragraph
>while staying clear of the brittle mess than is inheritance
It should be "that" instead of "than", I think.
You can use objects in C, and there's numerous in-the-wild big examples. But you have to implement it yourself or use some library, and there isn't one "standard" for the language.
Go clearly does have some features designed to make objects a first-class element. Equally clearly there are some other languages that have "more" such features.
This post I'm making is merely descriptive; there's no positive or negative attached to these statements.
Go is definitely an object-oriented language. It lacks classes, but those aren't required. We might argue that structural subtyping isn't really very OO. But given Go's lack of generics, this is a moot point.
You can have structure members that are function pointers which is basically attaching a "method".
There's a good quote in the article about this under 'Inheritance Is Best Left Out' - James Gosling responds to someone asking what he'd change in Java in retrospect - “I’d leave out classes,” he replied.
Right, but that doesn't mean Go is not object oriented, it simply eschews inheritance for composition. Go also offers something else n lieu of inheritance that many other OO languages don't have, embedding.
Attached that behavior to the struct itself so that invoking a struct's "area" function could give any of several different behaviors depending on exactly what "rect" struct you have.
type Area interface {
area() int
}
though I'd note I'm deliberately just copying the article as written, as an "area" that's confined to an int is awfully weird.Note you can indeed just add that, and you're able to take "Area"s anywhere you like, and you can even do it in a different module entirely; nothing has to "declare" than a rect implements Area.
To forstall the usual next question, no, Go has no further overloading based on type or anything else. My personal advice if you want to use that a lot is to use a different language. I like Go, but I look on the people trying to use it for machine learning or matrix math or other intensely mathematical computational loads with a bit of mystification. It isn't what it's good for, and I see no sign the core devs even consider it a marginal use case (to say nothing of a core use case) and have no intentions of changing the language to make this easier. If you really, really need overloading for your core use case, pick something else. Go is built for the environments where overloading is generally dangerous and used by people to do excessively "clever" things on the server, not for the environments where it is necessary. (Or perhaps "environment", singular, since "intensely mathematical code" is the only such thing I know of; everywhere else I've ever seen it it's asking for trouble.)
You can overload in Go: see for example how a zlib compressor is implemented [0].
The gist is that you take a standard io.Writer, embed it in your struct, and override the Write() method. This way you have a new io.Writer you can use wherever a io.Writer is needed. This pattern is actually standard in Go (and I guess in other languages where interfaces are more important than implementations)
Or maybe I didn't understand ?
[0] http://golang.org/src/pkg/compress/zlib/writer.go?s=4340:439...
For many folks, it's basically built-in language syntax for objects (data+methods) instead of using patterns, idioms, and conventions. The object-oriented nature is explicit with syntax of language keywords instead of implicit with code organization.
>Is it a functional question or a religious question as to whether or not Go is Object Oriented
I think it's more of a functional/pragmatic one and not religious. (I would substitute the word "religious" for "psychological" -- more on that in the next paragraph). If a language is designated as "object-oriented", I think it's reasonable to have some expectations that the programmer does not have to write idioms & patterns to emulate C++/C# type of objects.
That said, there are still psychological motivations for expanding "object-oriented" to describe what Go can do. The problem is that the term "object-oriented" has gained a lot of currency as something useful and desirable in the programming world. Therefore, if someone labels something (e.g. Go) as "not object-oriented", that has an implied judgement that Go is somehow "handicapped" and has less power than C++/C#/etc.
Ideally, all programmers would treat the following statements as something neutral and non-threatening: "Go is not object-oriented. C language not-object-oriented."
But since we can't (the psychology), we get articles explaining how C and Go are actually object-oriented after all. We do this, that, and the other thing, and voila, "C is object oriented."
But that is precisely the point of Go: it IS less powerful. It has no inheritance, no generics or templates, no macros or pre-processor, no raw pointer access, no exceptions etc. This is all completely intentional, and yes it will be a turn-off to many.
e.g. This discussion:
http://c2.com/cgi/wiki?HowObjectOrientedIsClos
[NB I now just tend to think whether something is useful, leaving ideological purity to others.]
Do you program with anonymous values without names independent of their structure, and you can then reason about them equationally? If so, then you have values and probably lambdas to plumb them through the program as they lack their own behavior (you can have lambdas over objects also, but we don't call that functional these days).
Does the language encourage you to think about named entities or does it encourage you to reasoning about values? Of course, not many languages beyond Haskell try to push you to reason about everything as values. And most OOP languages include some form of values these days (if not, we definitely program with immutable objects like points that lack names/identity and might as well be called values).
This is also why OOP is necessarily tied up with state: it doesn't make sense for an immutable container of something to even have a name as it can be only really be identified by its structure.
I've done that a few rare times, and I think in some case it makes a clean solution. See [1] for an example.
However I don't think that's how people should do _all of the time_ when they try to force an OO model onto Go.
[1]: https://github.com/aybabtme/loghooks/blob/master/hooks.go#L2...
The code which wants to be generic is in the handling of things like setting up channels between stages, handling data passing between them and cancellation (shutdown).
I also want to support a layer of abstraction, where I can compose a graph of stages into a single stage.
The goal is to have a number of primitive processing stages and allow abstraction and composition to build more complex processing.
Basically I could move to a pure interface and associated functions (not methods). That would perhaps be more idiomatic go. It's just a slight shame that a bunch of code which lives to my mind slightly more naturally as a set of methods on a type gets promoted to top-level package functions due to the inabillity to define methods on an interface. But that's probably OK.
Employee : Person
{
string employeeID;
double salary;
}
I want: Employee
{
Person person;
string employeeID;
double salary;
}
If we're saving into JSON, your save contents are mixing with the Person's save contents ("{name, address, salary, employeeID}"). What happens when a "basic income" law is passed, and all of a sudden the Person's implementor decides to add a salary field to the Person object? My method continues working without hiccup, because I was explicit about how I saved: save()
{
write("{ employeeid:whatever, salary:whatever, person: ");
save(person);
write("}");
}
My save file displays the same encapsulation as my code, and thus behaves correctly when a base class changes by default. On the other hand, in subclass-land you'd now have competing salary fields, so you'd have to explicitly prepare for that, instead of getting it semantically for free. For example, you could start defensively programming by name-spacing the save properties: "{person-name, person-address, person-salary, employee-salary, employee-employeeID}".In my experience this reliance on what "things are" is a bad way to think about programming. It doesn't help anyone to argue the philosophy of whether Employee is a Person or not, because I can easily sidestep the argument by saying: "OK fine, Employees are Persons... but I'm not longer writing the Employee class, I'm writing the EmployeeRecord class. And as such the person instance itself is part of the employee set if it has an associated EmployeeRecord. I have now satisfied the is-a relationship without an is-a language feature". Kind of like how certain Rectangle ARE squares regardless of whether they happen to be members of the Square class. Its a membership requirement, not an instantiation requirement. Its just words.
I show this only for example because it's horrifying Go code, but the closest go equivalent would be:
func OverloadedSomething(params ...interface{}) interface{} {
// (int, int) int
if len(params) == 2 {
a, isAInt := params[0].(int)
b, isBInt := params[1].(int)
if isAInt && isBInt {
return a + b
}
}
// (string) string
if len(params) == 1 {
aStr, isAStr := params[0].(string)
if isAStr {
return aStr + " world"
}
}
// etc etc
panic("OverloadedSomething not given something it can resolve")
}
Which could then be called like: sum := OverloadedSomething(1, 2).(int)
helloWorld := OverloadedSomething("hello").(string)
I may have the ... on the wrong side of the interface{} in the params.The Go thing to do is to declare separate functions for each implementation. Other languages have support for doing that sort of resolution at compile time, so you don't get the obvious run-time hit you'd take trying to do that in Go.
Overriding:
class Foo {
void f() {}
}
class Bar : Foo {
void f() {}
}
Overloading: class Foo {
void f(int i) {}
void f(string s) {}
}
Go doesn't allow overloading:
"method lookup is always by name only, not by signature (type) of the method. In other words, a single type can never have two methods with the same name. Given a method x.M, there's only ever one M associated with x. Again, this makes it easy to identify which method is referred to given only the name. It also makes the implementation of method invocation simple."
http://talks.golang.org/2012/splash.article#TOC_11.An awful lot can happen between you calling a generic function and your method(s) being called - and pretty much all of it is customizable - hence the "CLOS is best thought of as being implemented in CLOS", which I think is from the AMOP.
But then people can just shift the argument around to the word "powerful". Unlike electricity where "power" is composed of just 2 dimensions (voltage and current), "power" in a language has hundreds of dimensions.
If for one programmer, the "interesting" things in Go include ultra fast compile times, builtin concurrency primitives, network library, etc, then to him, "Go is more powerful than C++."
The disagreement over what dimensional components of "powerful" is worth comparing then feeds more debates (and implied judgments of language worthiness).
I say "not typically" because there are still languages conventionally called OO that can do that, like Python and Javascript.
I take a pretty expansive definition of OO, pretty much the same one the author of the post takes, on the grounds that it isn't that helpful to insist that Javascript or Go isn't an "object oriented" language, because then one must sit there and explain what they are. They clearly aren't "procedural", excepting to the extent that Java is "procedural" (i.e., used as opposed to functional or declarative). And in the end, while there are significant dialect differences within the family of OO languages, when you start your language with a binding between methods and structures (be it a class or a prototype or a whatever), you end up with the same sort of end language. When viewed from the perspective of Haskell or SQL, Go and Java are next-door neighbors, even if within the mighty city of Object Orientation they consider themselves distant. Really persnickety definitions of "OO" just don't match to the programming reality very well. Argue as much as you like about whether one must "really" have a particularly persnickety definition of Polymorphism or whether you "must" have a concept of "private", but in the end, a project optimally done in Java, Python, Go, and $OTHER_OO_LANGUAGE will often end up pretty similarly structured. (And I'd observe to the extent that is not true, the spoilers will be something unrelated to OO like concurrency support or runtime characteristics, not details of the OO.)
import itertools
class Test():
def __init__( self, number ):
self._number = number
return
def aaa( self ):
print 'AAA<%s>' % str( self._number )
return
def bbb( self ):
print 'BBB<%s>' % str( self._number )
def main():
instances = [ Test( n ) for n in range( 100 ) ]
functions = [ Test.aaa, Test.bbb ]
for instance, function in zip( instances, itertools.cycle( functions ) ):
function( instance )
if __name__ == '__main__':
main()Haven't used Servo, but one of the big eye opening composition experiences for me was Unity's Scene Graph. Whereas Cocoa uses an inheritance model for its view-tree, Unity has a tree of transforms that you do not subclass or change in any way, and then you add behaviors to those transforms. If you want it to render, you can attach a renderer, if you want to hit test, you attach a collider. If you want any arbitrary other thing to happen, you create that behavior. Its really nice, the idea of "tree" is completely separate from all other concepts. Rendering a 3D game, on mobile, at 60fps (on GC-ed Mono no less), makes me feel pretty good about its performance characteristics. Most our perf issues were with limiting draw calls and optimizing shaders, not method calling.
Similarly, I worked a lot on a browser engine in the past and virtual method dispatch was again not this clear cut performance killer.
Sounds like the work done by tree traversals weren't high overhead in general for your workload. But it does matter for some workloads.
> Similarly, I worked a lot on a browser engine in the past and virtual method dispatch was again not this clear cut performance killer.
We're seeing large gains from, as far as we can tell, having fewer virtual method calls than other engines. Eliminating virtual dispatch opens up a huge range of call-site optimizations since the methods can often be statically inlined (as well as reducing the load on the branch target buffer).
> Similarly, I worked a lot on a browser engine in the past and virtual method dispatch was again not this clear cut performance killer.
That doesn't match my experience. Devirtualization opens up lots of inlining opportunities, and inlining is one of the most critical optimizations that compilers can do (mostly because of the other optimizations that it opens up; e.g. const propagation, GVN, etc. etc.)
See this study: http://hubicka.blogspot.com/2014/04/devirtualization-in-c-pa...
Devirtualization optimizations improve Dromaeo by 7-8%. That's a significant win, especially since devirtualization is only a best-effort optimization and Dromaeo has a lot of JS in it.
We can't use composition because we wouldn't get virtual methods.
We can't use an interface because then we would be forced into virtual dispatch for all of those fields that are shared between flows.
we have a heterogeneous tree of objects that all share a common set of fields (position, intrinsic widths, collapsible margins, some various bits that store state during reflow),
You're description seems to me to scream functions: That work on plain structs.I'm not an expert in performance though so I don't if that is just as bad as the other alternatives you mentioned. But you didn't mention them so I thought I'd ask if you considered that as a valid approach and if so why you rejected them?
I'd prefer that to having to avoid using the most natural names for methods to avoid implementing an unintended interface -- it seems to me that Go's approach in this area makes easy things easier and hard things harder.
In the canonical example:
type Boat interface {
Launch()
}
func LaunchBoat(b Boat) {
// do some boat stuff
b.Launch()
}
type NuclearMissile struct{}
func (nm NuclearMissile) Launch() {
// launch nuclear missile
}
func main() {
rocket := NuclearMissile{}
LaunchBoat(rocket)
}
Sure, this compiles, but the code doesn't make any sense. Lots of things compile, that doesn't mean the code makes sense. At some point it's the programmer's responsibility to think a little.Yes, that's generally the case with type-safety problems, even with the type-safety problem existing, it takes an actual programming error for it to become a problem -- and that is, indeed, the standard response of people saying type-safety isn't important (usually, though, its not a reason people would say something isn't a type-safety problem.) And its not completely invalid -- there is a reason that in a world dominated by static-typed languages with limiting type systems, dynamic languages like Ruby and Python that don't offer type safety but do offer a lot of flexibility that the type systems of C++/Java/etc. made, at best, cumbersome to acheive.
OTOH, if you are choosing a language with the extra ceremony involved in static typing, its kind of a big step back to not even get the level of safety with interfaces that you'd get with C#/Java, much less a more modern, expressive static type system.