Is Go an object-oriented programming language?
sobyte.net
sobyte.net
These are probably trivial things to a lot of people but after being brought up on Java, and working in rather OOP-heavy frameworks for years, it's liberating.
I think it's a Java problem, not an OOP problem.
Java is a multi-paradigm language, and is absolutely on a different level of expressivity compared to Go.
EDIT: Java is really not what people remember from 20 years ago. You can write as functional code as you wish stopping just before Monads. It’s type system is also a different league than Go’s. So it has OOP, imperative and FP.
I used Smalltalk/V before Java came to be, by the way.
Personally, when I have to use it, I make abstract classes or better, interfaces. I avoid direct inheritance like the plague. But I've worked on personalized Jenkins where I had to inherit classes from weird modules, so maybe I got burned and my experience isn't the average experience (also, I hate Java now).
I think traits and interfaces are more supportive of a composition-like programming style, one might call that object-oriented but I still think it misses several key concepts of classical object-oriented languages.
type Animal interface {
MakeNoise()
}
type Dog struct{}
func (Dog) MakeNoise() {
fmt.Println("Woof!")
}
type Cat struct{}
func (Cat) MakeNoise() {
fmt.Println("Meow.")
}
func MakeNoises(as ...Animal) {
for _, a := range as {
a.MakeNoise()
}
}
func main() {
d := Dog{}
c := Cat{}
MakeNoises(d, c)
}No, that's not an inherent property of OOP, that's a property of C++.
To be OOP, you must support inheritance (which Go does, through struct embedding) and polymorphism (which Go also does, through interfaces). The polymorphic inheritance you're talking about is strictly a C++-specific implementation detail.
In C++, composition differs from inheritance because when you embed an object inside another object, you cannot call its method directly, but through a wrapper:
class Dog {
Animal a;
bool IsAlive() {
return this->a.IsAlive()
}
}
However, in Go, you can call their methods directly: type Dog struct {
Animal
}
func main() {
Dog.IsAlive()
}
So the distinction itself isn't really meaningful. In Go, composition is inheritance.How else do you define inheritance, if not as ability to call methods of the inherited type?
> If you embed an "Animal" in a "Dog" struct, the "Animal" struct doesn't have any means to find out it's a "Dog", i.e. it can never find out at runtime that it's embedded in a "Dog" object or rely on functionality defined in the "Dog" object
I'm not quite clear on what you mean. How exactly does this work in C++? How do you "detect" it's a Dog on runtime, or "rely" on functionality defined in Dog?
If you can provide code example, that'd make things much easier for me to understand.
I disagree, I think that's actually bundling the concept of polymorphism with the concept of inheritance. Okay, C++ does it, but is there any other reason why inheritance must support polymorphic methods in order to be "real" inheritance?
C++ uses virtual classes to achieve polymorphism through inheritance. Go simply uses interfaces. I don't see any reason why Go's approach would be "less OOP" than C++.
Yes, but I'd argue that's because all those languages just blindly copied what the massively successful C++ was doing at the time. Nobody gave it a second thought.
From the perspective of language capability, polymorphic inheritance doesn't give you anything that you cannot achieve with polymorphism and inheritance alone. In fact, there was no polymorphism outside of virtual methods in C++. If you want polymorphism, you have to implement it through virtual methods, there's just no other way.
Being able to specify that an argument conforms to an interface is not what's generally meant by "object oriented". Interfaces are really only a formality. What matters is inheritance.
No there isn't and that's the point. Say you're building a lib and someone else wants to reuse it and its methods and extract functionality into a more generic interface; as the original author you don't need to go and modify your lib to accommodate for every single third party dev out there.
Basically, the interface might be in another package, and that's fine.
> with no compile-time type checking?
The compiler will still check if Dog and Cat implement the interface, because at compile time everything is known.
Though this is used fairly rarely in Go.
If you need a common function, what you want is another structure, say Pet, and use structure embedding
type Pet struct {
func IsCute() bool {return yes}
}
type Dog struct {
Pet # note how it is not named
}
func (Dog) MakeNoise() {
fmt.Println("Woof!")
}
func main() {
d := Dog{}
d.IsCute() # yes
# A third party lib
type Chihuaha struct {
Dog
}
c := Chihuaha {}
c.IsCute() # yes
c.MakeNoise() # Woof!
}It will make sure that only values implementing Animal will ever be used as an Animal anywhere. It's fully statically type-checked.
It's arguably one of the defining features of Go in general, and is what shapes much of the idiomatic code style.
In this case, Animal is an interface - which means, a set of methods. The Animal interface in the example contains only a method "MakeNoise()". Any type (struct) that implements the method "MakeNoise()" implements the interface, and can be used whenever Animal is required.
I personally wished Go had full OOP, ideally with multiple dispatch. It's what I miss most from CommonLisp and Racket when I write Go. The only arguments against it I've seen so far were rather silly and patronizing arguments along the lines of "we have to protect the programmers."
* Encapsulation: Bundling data and functions syntactically together (in which case they're commonly called classes and methods)
* Dynamic dispatch: Calling a method may call a different function implementation depending on the runtime type of the class instance
But that's it.
C++ inheritence is actually two features (at least), which you seem to be conflating: it supports dynamic dispatch through virtual methods, but also allows using the implementation of base class methods in derived classes (whether virtual or not). But only the first part is actually needed to be called object oriented. Even VBA is technically OO because its use of COM interfaces allows dynamic dispatch - even though it doesn't support inheriting method implementations at all.
(A couple of notes: If a language only supports encapsulation but not dynamic dispatch then it's "object based" rather than object oriented. And, when encapsulation is supported, the language usually also supports data hiding (that's public/protected/private in C++ speak) but technically that's a separate thing.
Yes, but only in your personal opinion. I disagree. As I've said clearly, for me a language is not OOP without inheritance. Go is not an OOP language. This is important for me because I like Go and think it's a great non-OOP language. In contrast, if Go was an OOP language, then it would be a really bad one - no traits, no mixins, no classes, no multiple inheritance, no multiple dispatch, no true dynamic method dispatch (see below), etc. Besides, treating Go as if it was an OOP language often leads to horrible Jave-in-Go programming style.
Btw, I wasn't talking about C++ inheritance, I had things like CommonLisp CLOS and Ada's tagged types in mind. CL's OOP is among the most powerful because it has multiple dynamic dispatch and a metaobject protocol. Go lacks both, and it also doesn't really have dynamic dispatch. Dynamic dispatch means that superclass and subclass methods can be called at runtime depending on what is needed. You cannot call various superclass methods of a Go struct because Go structs do not have a type hierarchy/superclasses as there is no concept of inheritance. You sometimes get something that looks similar because of the duck typing (which is dynamic, but it's still not a full method dispatch) and because you can downcast at runtime using .(type). But that's still not the same as dynamic dispatch in an OOP language. Here is an explanation of the difference: [1]
However, if you insist on imposing your definition as the one and only right one, then that's okay for me. In that case, Go is the most limited and crippled OOP language I've ever used, and so be it.
[1] https://stackoverflow.com/questions/49114057/idiomatic-way-t...
But definitions aren't really a matter of opinion. If it's my opinion that apples count as a variety of orange, then that doesn't make me right. Instead, they're defined by what is widely understood by that term. I'm not saying what I've written is authoritative but it was what I was taught at university by someone who was around and academically active when OO languages were being invented (that course had a lot of SmallTalk D-: ) and certainly is a much closer match to how I've usually seen it used since compared to yours.
BTW I'm primarily a C++ (and Python and C#) developer, I hardly know Go, so I have no axe to grind here.
> Dynamic dispatch means that superclass and subclass methods can be called at runtime depending on what is needed.
No it doesn't: it doesn't require the notion of superclass and subclass. Using an interface to call a method still counts as dynamic dispatch.
> Here is an explanation of the difference: [1]
The thing is, the question asks "does Go support dynamic dispatch" but the answers are about the subtly different question "does Go support this particular type of dynamic dispatch". The second answer even says that "Go ... interfaces are its only form of polymorphism". Runtime polymorphism is a form of dynamic dispatch! (The only form I know to be honest but I don't claim to be an expert and don't have your experience of lisp-like languages.) The first answer talks about inheritence but never mentions dynamic dispatch at all. If you reduce the whole question down to two totally separate structs (none of that weird Go inheritence / composition stuff) with only one method each, both implementing the same interface, and you called the method through the interface, then that would be dynamic dispatch. Of course, it's not the dynamic dispatch the question poster wanted. And it's not inheritence (although I think that term is much more ambiguous but I still wouldn't dispute it).
var _ Animal = (*Cat)(nil)
var _ Animal = (*Dog)(nil)
and you're back to the same place with more verbosity, and with much more difficulty when trying to figure out which types implement which interfaces. Much more load on IDEs as well.type Animal interface { MakeNoise() }
Link to an implementation of scenario: https://go.dev/play/p/bSAFr9i4ex3
On the other side, message passing and dynamic dispatch are strictly related and you can do that with golang interfaces!
So yes, it does support full oop, what it doesn't support is class-based programming.
Does it matter at this point? Oop hasn't solved any of the big problems it claimed and fp did not either.
https://news.ycombinator.com/item?id=11966570
https://en.wikipedia.org/wiki/Object-oriented_programming?wp...
OOP as commonly understood requires some form of inheritance.
It's a matter of language construct organization. There are bare behaviors - functions, there is bare state - PODs, and then there are collections of state and behavior - classes. You can have these three distinct constructs, or you can have all them be subcases of class (or some other mix).
It's just that we are used to having distinct primitive and collection data types, but nothing (sans terseness) stops language from having all data be some form of collection, even if it is a single-member collection.
Nonetheless, thanks for the paper/video, will look into them!
There are only a few (two?) dogmatic OOP languages in common use. Every other language that supports OOP has better OO support than Go, as well as:
> If you just need a struct, you can have a struct. If you want a package scoped standalone function, you don't need to invent some made up "class" to attach it to.
While I like Go, a lot of Go code could be simplified by having keyword-supported OO (a 'class' keyword, maybe).
It's better to have inheritance and not need it, than need inheritance and not have it because it is applicable in a lot more places than "composition over inheritance" fanboys would have you believe.
The OOP paradigm, as far as I remember, relies on the four pillars:
- encapsulation
- abstraction
- inheritance
- polymorphism
All of which exist in Go (encapsulation: structs; abstraction: interfaces; inheritance: struct embedding; polymorphism: interfaces). Nobody said that inheritance must be polymorphic, like it is in C++ with virtual methods. Nobody said we must have the "class Class { members... }" syntax like we have with C++. Those are all just implementation details.
Go is a perfectly good OOP programming language, it just differs from C++, so C++-ians who can't see anything beyond the syntax claim it's not OOP.
Go supports both inheritance and polymorphism. It just doesn't have polymorphic inheritance, which doesn't really bring you any new abilities that polymorphism and inheritance themselves didn't bring.
Please see this comment for an example of Go's polymorphism and inheritance working together to do what you would need virtual methods for in C++: https://news.ycombinator.com/item?id=35784598
Polymorphim normally means dispatch based on type of the first argument to a function, sometimes called a method. Occasionally dispatch based on type of all arguments to a function.
Inheritance is a means of deriving types from other types. That's usually done as a means of controlling dispatch via the above polymorphism. I don't know of a different motive for inheritance. You claimed inheritance doesn't need to be polymorphic, as quoted above, so I hoped you had a different perspective on it.
Code reuse? You have a base class that does a bunch of stuff, and you want to add a single thing.
If there wasn't any motive for inheritance without polymorphism, then all methods in C++ would be virtual, wouldn't they?
edit: also by experience, the intersection of all common definitions of OO is the empty set. At this point I think it is best to leave the OO definition only for those languages that self define as such (which would btw exclude C++) and it is more of a philosophy of software design than an actual set of features.
Exactly, if you want polymorphism in C++, you have to drag in inheritance too. You can't have the former without the latter. In Go, you can have polymorphism through interfaces without ever embedding a single struct, so that would make Go more OOP than C++.
The beauty of inheritance is implementation reuse.
In a proper Alan-Kay-Blessed OOP language, the "interface" has no role. Why? Because there is extermely late binding everywhere.
What that means is that if we want an object to be substitutable wherever a Duck is needed, we just need to make it quack like a Duck. It doesn't have to inherit anything.
Interface inheritance is just syntax: a static type system imposed into OOP, which lets you do type checks. Oh! That object cannot be used here; though it quacks like a Duck, it doesn't inherit DuckInterface. Please add DuckInterface, then fix errors (if any) and try again.
Now inheritance is actually useful even when we have the above True Scotsman's Late Binding OOP that doesn't impose static interface cruft on us. Because:
1. It lets you use implementation. Say that quacking like a duck is a complex set of behaviors. Let the specialists develop (and maintain) DuckBase: you just inherit that (and pull in updates once in a while).
2. It gives you a real instance-of dynamic relationship: your object can be asked "are you a duck" at the dynamic type level, and the type system will assure the caller, that "yes". You can't get that if your duck behavior is coming by delegation to an object contained in your DuckLike by composition.
Also, only multiple inheritance is the real deal; single is seriously crippled.
The lookup mechanism is a implementation detail, just like in any OOP language with interfaces.
Basically it is like we are discussing how vtables work over here.
Also templates are of course polymorphic and do not require inheritance, but that's not runtime polymorphism which I guess was implied.
edit: s/is/can be/
The derived object has to be substitutable for the base object. If not, then you don't have the late binding needed for OOP.
I would say that the third pillar ther should be "multiple inheritance".
Single inheritance is fake/garbage inheritance, not suitable for full blown OOP.
If you just have single inheritance, and you've already committed a class to an inheritance base (out of some necessity), then you're screwed if you suddenly need an object of that type to be an "instance of" something else.
If you don't have it, that's an F grade.
Ruby modules get a D, and Java interfaces a C-.
C++ gets a D- with its base classes having to be declared "virtual" nonsense in order that your object isn't a Duck in four different ways due to diamonds in the inheritance lattice.
That's polymorphism, and isn't part of the definition of inheritance. Otherwise, inheritance and polymorphism wouldn't be separate concepts.
And even if you take the liberty of declaring that a more general definition paired with a more specific one, the more specific one should not so much distinguish on language features but on the standard lib: is your typical stdlib call something scoped to an object (subject parameter pulled to the left) or is it some global name or with just a namespace on the left? mystring.length() vs strlen(mystring)? Most languages these days have both, but if the "strlen" form is the dominant one no amount of OO features should allow you to call yourself OOP without twitching. And if object.method() is the dominant form, how you got there is just details. It feels embarrassingly lame to stay that close to the syntactic surface, but the dogmatic deep-dives really don't help.
I did bunch of Java before doing Go but also did bunch of non-OO plain vanilla scripting before that so have background in both OO and non-OO procedural approaches - I found Go's approach very "natural". It was not awkward but "new" and once I started coding, things just felt in place. I was productive quite quickly. If only they had solutions to reduce the if err != nil boilerplate plus support for some comprehensions to allow more concise code, Go would easily be my favorite language (even with all that, I still like Go).
One observation from my noob POV: Packages and structures functionalities seem to overlap at some points and it's not so easy to understand when to use each one at first.
Additionally "Traits - Composable Units of Behavior" as evolution from Smalltalk object model, or protocols and categories in Objective-C, functors in OCAML (nee Objective CAML), clusters in CLU.
As yet other set of examples where interfaces, modules and polymorphism drive the OOP story without classes as the main "actor".
No dog extends animal, cat extends animal, duck extends animal hierarchy, but a "bark" method that only dog and cat implement.
No "everything is an object", you get to decide what goes on the stack vs the heap based on your own insight. But also no pointer arithmetic footgun.
Also, favoring composition over inheritance has been a major mantra of Java since forever.
And while inheritance is a very prominent feature of object oriented languages, it is not the only option there. I think the way those languages are taught over-emphasizes inheritance, but there are plenty of arguments today around preferring composition. So I don't think this is an inherent property of the languages, they allow you to use inheritance but you're not forced to used it everywhere.
Seems modern language designers think inheritance is bad. I tend to agree: the relationship is so confusing to get right (is a Square a Rectangle?), that composition instead of inheritance and Go-style interfaces instance of explicit interfaces are what you actually want in almost all cases.
Also, go is modern only in age, the only remotely novel thing about it is goroutines, so I wouldn’t detract too much from its design choices.
These concurrency models can be placed on a matrix based on stackfull/stackless and based on preemptive/cooperative. I think there are/were examples for all of them — the more interesting property of goroutines (and Java’s virtual threads) is turning blocking calls into non-blocking, by using the OS’s async IO behind the scenes for everything.
2 - Interfaces can be combined into type relationships
3 - Due to structural typing, an interface can extend another one by having a common method set, allowing for Liskov substitution, any implementation that takes the base interface, will also take the extended one.
What Go cannot guarantee via structural typing is that the subtyping is desired and not a coincidence in method naming.
"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."