Hacker News new | comments | show | ask | jobs | submit login
Is Go an Object-Oriented Language? (spf13.com)
73 points by spf13 1141 days ago | hide | past | web | 69 comments | favorite



The problem with the author's case for Go's is-a relationships is that it breaks down the moment you want to pass the object to a function expecting the original object.

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.


In my experience its always really telling that there are no incredibly simple is-a relationships to use in inheritance justifications (as opposed to tons of goto real world examples for just about every other feature under the sun). Its always either incredibly abstract (B is-a A), or incredibly contrived (Triangle is-a Shape). I've spent a lot of time in inheritance heavy code, and I've yet to find something that wouldn't be just as, if not more, elegant without inheritance. I've spent the most time in Cocoa (which I believe to be very well designed BTW), but the inheritance there is clearly not needed IMO. I usually find one of the following to be true:

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.


Views in UI toolkits are the best example I know for an is-a relationship. A button is a view, a slider is a view. A container may have heterogenous children, but all of them are views. An interface doesn't work, because you would have to implement basic properties like containment, geometry, etc. separately for every widget.

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.


> In my experience its always really telling that there are no incredibly simple is-a relationships to use in inheritance justifications (as opposed to tons of goto real world examples for just about every other feature under the sun). Its always either incredibly abstract (B is-a A), or incredibly contrived (Triangle is-a Shape). I've spent a lot of time in inheritance heavy code, and I've yet to find something that wouldn't be just as, if not more, elegant without inheritance.

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 :)


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

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.


> Most our perf issues were with limiting draw calls and optimizing shaders, not method calling.

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 don't know what you mean by functions on that work on plain structs; can you elaborate?


Inheritance is usually a mess because people use it to model objects when they should instead be modeling types: a triangle is not a shape, but a Shape is a sum type of Circle, Square, Triangle, etc.


Shape is definitely much more accurately modeled as an interface than a base class, since it has no functionality on its own.


I see 'is a' and think algebraic data types.


IOW, the Liskov substitution principle is unsatisfied. The language has chosen by design not to resolve "methods"† up the "hierarchy"†, therefore the only way to reinstate it is to implement func save(B) { }, which has either the benefit of making you think whether you need to persist more fields or make explicit that you don't. And as you said, implementing proxy accessors to satisfy an interface just to simulate inheritance is obviously not the right choice.

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


Just call save(b.A) or save(&b.A).

No need for methods, is performant. I can't think of a reason--besides extra typing--why this wouldn't be sufficient.

http://play.golang.org/p/7vE_wN6EBv


It's sufficient, in that it does what you want - it calls save() on b's A.

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.


This becomes difficult to discuss in the abstract. In my experience, save() is a clear example of exactly why you want composition and not inheritance. When I am saving the B record, I want it to let the A record's save do its thing without interfering. Let's for the sake of argument use Person and Employee instead of A and B so we can use a concrete example. We'll say Person has a name field and an address field. Employee owns a Person, and has an EmployeeID as well as Salary. You want:

    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.


Why not?

    type Saveable interface {
        ...
    }

    func save(a Saveable) { .... }

    save(a);  // ok, assuming A is Saveable
    save(b);  // necessarily ok, given than A is Saveable


I would say the defining property of OO, is polymorphism. It's the property that allows some code to call a Bar() method on object Foo and not be concerned with the exact type of object Foo. Without polymorphism, there is little differnce between Bar.Foo() and Foo(Bar).

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


> I would say the defining property of OO, is polymorphism.

Make it dynamic polymorphism, and I agree.


Subtype polymorphism. Dynamic dispatch.

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.


subtype polymorphism


I have to say, I like Go model better than "standard" OOP (as in Java, C#..).

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.


I like that Go and Rust reexamined some of the underlying traditions of the C++/Java/C# approach to OOP rather than reflexively repeating them -- and while I think Go and Rust both, on a high level, took good (but very different) approaches, I think the one thing that Rust did right that would be better even with the rest of Go's approach than the way Go did it is explicit and detached declaration of interface implementations for data types.


So you're saying you'd prefer it if you had to explicitly tell Go that your type implements an interface? Something like this?

  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.


> So you're saying you'd prefer it if you had to explicitly tell Go that your type implements an interface?

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.


Why do you care if you accidentally implement an interface?


Because conforming to the signatures necessary to implement an interface doesn't imply confirming to its semantics, so accidental interface "implementations" are a type-safety problem.


They're really not. You still have to have someone pass your type into something expecting an interface. It's no different than passing a string into something expecting a path when you pass it an html document... at some point the programmer needs to decide if passing the value into the method makes sense.

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.


> You still have to have someone pass your type into something expecting an interface.

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.


One could say that Go errs on the side of caution, making sure it doesn't have too many of the trappings of OOP.


The author of this blog post is a little confused about embedded structs. His examples of has-a and is-a are both has-a's, and the syntax change involved (leaving off a name for the embedded type) doesn't actually do anything.

  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/aKH3YxT5Mb

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


Reminds me of the (possibly) apocryphal story of Nickolas Wirth telling his audience at Apple that Modula-2 was OO and having one of the audience members object. To which Dr. Wirth replied, "Who are we to say what object oriented means exactly?" and the objector, who turns out to be Alan Kay, says, "Well I invented the term so I get to define it, this isn't object oriented."

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?


>I'm sure there specific things that some people require before they will label something as OO.

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


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

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.


Understood.

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


Rob Pike does claim that Golang is object-oriented. But I think I agree with the subtext that this is a very unproductive debate to have.


I've long since given up thinking about a precise definition of "object oriented" - for example, CLOS (which I used for years and thought was awesome) doesn't sit very well with most peoples ideas around what constitutes "object oriented" which seem to be largely define by experience with C++, Java or C# (which all look the same if you squint hard enough).

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 entities that have names and you can talk about their interactions as if they had behavior of their own? If so, then you have objects.

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.


Well, it's been a while since I wrote CLOS for living - but I don't think I found it useful to regard behaviour as being tied to a single class - that sort of implies limited single dispatch whereas CLOS has the wonders of full multiple-dispatch so your behaviors are really attached to tuples of classes and other types.

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.


It seems it was someone who had worked with Wirth, rather than Wirth himself, who was the victim, and he was talking about Oberon rather than Modula-2, but there seems to be an eye witness report of it:

http://c2.com/cgi/wiki?HeInventedTheTerm


Something I've done in some golang code recently is to fake some OO features. I'd appreciate some commentary on the approach.

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?


If it works well, it's not feeling like a square peg in a circle hole, if it makes your life easier; I'd say it's fine.

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


To me, it feels like you're implementing too much of the OO behavior yourself. That is essentially the same as saying that the language isn't really object oriented.


I think you'd do better making the stages into plain functions that take an interface... why do the stages need to be structs at all?


It's really the code to connect the stages. I want to support 1->many, so a single stage can fan out it's output to multiple consumers.

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.


> Since a standard definition doesn’t exist, for the purpose of our discussion we will provide one.

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


Kay's definition is by no means "standard". "Standard" usage of the term started deviating from Kay's definition almost as soon as it was conceived. By time the term was widespread, it already meant something different to what he envisioned.

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.


The blog post gives us, "To me this feels very much like an object. I am able to create a structured data type and then define methods that interact with that specific data." Defining OOP as functions that consume structured data fails to exclude a lot of languages we don't consider object-oriented.


You can do this same thing in C to get Objects.

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.


C is not an object-oriented language because it has no language features intended to make objects easier to use or work with. You can't actually attach "methods" to structs in C.

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.


Thank you for clarification. I've not really used Go, so I don't know how it differs from C for objects. I'm a little familiar with using structs in C for OOP, I apologize if my comment came across as to lessen the article, because I found it pretty interesting, and I love to see things like this where a new programmer might have their eyes opened to something those of us who've been around a while might already know from somewhere else.


C has nice infrastructure for building v-tables that I found very useful back when I was writing in C. Heck, I wasn't even aware that I was merely reinventing objects at the time.

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't actually attach "methods" to structs in C."

You can have structure members that are function pointers which is basically attaching a "method".


In that case you get something not typically associated with "OO", which is the ability to override a method on a per-object basis.

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


You still have to explicitly pass a `this` to that function, which is kind of my earmark for telling methods and functions apart. (In this regard, Python treads a fine line where it has an explicit `self` parameter that is passed in automatically).


You can manually pass in the `self` parameter to unbound methods if you like.

    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()


I think the "object"-less OO tag is a bit overblown. Structs are objects. Just because Go creators didn't choose to name them that doesn't mean there's a fundamental difference between Go structs and other languages' objects.


The fundamental difference (and there is one), is that Go omits the concept of inheritance, preferring composition instead. So in contrast to many mainstream languages today - Java, Obj-C, C#, C++, Ruby, Python, PHP , etc. there is no concept of inheritance, only composition of objects and conforming to interfaces.

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.


>>The fundamental difference (and there is one), is that Go omits the concept of inheritance, preferring composition instead

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.


"O-O is important because it provides uniformity of interface. Subtype inheritance encourages non-uniform interfaces." - Rob Pike http://www.infoq.com/presentations/Go-Google 43:00


Yea, I liked the idea of what go was waning to do. They wanted classes and objects without the classes and inheritance, but all they've done is replace that headache with an interface headache.


Surprised there is no mention of hiding or message passing, which are two more common aspects of Object Orientation. Without them, I think the idea that methods are "attached" to objects is rather incomplete.


More common, but certainly not required. For instance, JavaScript does not provide hiding at all as part of its OO mechanisms. (Implementations that allow hiding use closures to provide it.)


Exactly, that would fit with the tone of the article, which does away with the idea of OO defined in terms of axioms like inheritance, etc. and instead explores how these pieces of the OO picture work and relate to each other.


What have’t we done.

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.


To do that in Go, you just add:

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


> Go has no further overloading based on type or anything else

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


That's embedding. If it's a replacement for any traditional OO concept, it's "inheritance", not overloading. (It isn't a replacement, but if you found yourself needing inheritance, embedding is what you'd use to get the closest.) Overloading would allow multiple definition of the same function name that are dispatched in various ways based on the types being called.

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.


You confound the two orthogonal terms "overloading" and "overriding".

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.


This is honestly kind of a dumb question, because the answer depends on who is asking the question. Object oriented has become such an overused and little understood term, that you can't just give an answer without trying to discern what the person asking is looking for. There's almost always a better question to ask than "Is x language object oriented?".




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | DMCA | Apply to YC | Contact

Search: