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.
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.
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.
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.
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.
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!