Hacker News new | past | comments | ask | show | jobs | submit login
Golang Object Oriented Design (nathany.com)
151 points by JanLaussmann on Sept 16, 2013 | hide | past | favorite | 73 comments



I keep wondering if Go couldn't have gone further in getting rid of the cruft that has accumulated around OO. Go doesn't have an implicit "this" pointer. It doesn't have members that are private to an individual object nor members private to a type. Encapsulation exists only at the package level.

Yet Go keeps that old OO concept of method receivers. Why? I never found it very plausible to have one special case parameter with its very own syntax, but with type level encapsulation it did at least have some justification.

Without this kind of encapsulation, the only remaining purpose of the receiver is method dispatch. But why dispatch based on the type of one parameter and not the others? Multimethods would have been the logical conclusion of the OO cleanup that Go's designers apparently intended to achieve.


Without method receivers you wouldn't have methods, only funcs. Then you'd end up calling your "methods" names like `Address_Compare(addr1, addr2 Address)`, `Address_ToString(addr Address)` and so forth.

How unpleasant that'd be! With method receivers, you can have `addr1.CompareTo(addr2)` and `addr.ToString()` etc..


> Without method receivers you wouldn't have methods, only funcs. Then you'd end up calling your "methods" names like `Address_Compare(addr1, addr2 Address)`, `Address_ToString(addr Address)` and so forth.

Methods are just a limited special case of functions. Also, there are various different semantics that are used in function application. Your view of function application is rather limited, you might want to study e.g. how functions are applied in Haskell (type classes) or Common Lisp Object System (CLOS, multimethods). Both of them use very different semantics to what you're used to.

In addition, good namespacing and module systems can solve a big part of the "problem" you mention. Most modern languages have more than one giant global namespace.


> Methods are just a limited special case of functions. Also, there are various different semantics that are used in function application. Your view of function application is rather limited, you might want to study e.g. how functions are applied in Haskell (type classes)

Well, sure. On the other hand, if you had receivers for record accessors, you wouldn't end up with the ugly prefixing necessary in Haskell.


GP specifically mentioned multimethods and your comment is completely wrong when they're taken in account.

They would allow `ToString(attr)` to have one implementation per type, and `CompareTo(item1, item2)` to have one implementation per `(typeof item1, typeof item2)` pair. The latter being unavailable in Go, and only statically available in languages with overloading.


With multimethods, that doesn't happen. You declare the compare multi (dispatch point), then you can declare as many compare methods as you want.


Not with higher order type inference (various MLs), but then you have function overloading and Go decided against function overloading.

/Edit: parallel comments mention multimethods. The difference is that with higher order type inference you can do static dispatch, but multimethods are dynamically dispatched AFAIK.


Type inference in the ML flavors doesn't work well with this at all. The multiple implementations of functions introduces ambiguity, which lead to multiple solutions to the type equations.

This leads to very hairy inference problems, which is why none of the ML family languages (that I know of) allow ad-hoc overloading of functions.


I always thought of multimethods as something that dispatches based on both static (where available) and dynamic type information. But you may be right that PL researchers don't see it that way.


Type inference is not necessary. C++ provides function overloading as well.


Go has methods only to satisfy interfaces, which are different than most other language's interfaces and a very crucial part of what gives the feel of Go. No methods, no interfaces, therefore if Go were to have no methods, it would have to offer the same features and language feel by some other mechanism. Perhaps there are many ways to do that, but the only way I can think of is allowing for code like this:

  type IPConn struct {
  	ip Ip
  	// contains filtered or unexported fields
  }
  
  func Write(i IPCconn, buf []byte) (int, error) { /* implementation */ }
  
  type File struct {
  	name string
  	// contains filtered or unexported fields
  }
  
  func Write(f File, buf []byte) (int, error) { /* implementation */ }
  
  func WriteString(w T, s string) (int, error) {
  	return Write(w, []byte(s))
  }
Please note that there are no explicit interfaces, but WriteString takes an implicit Writer. You can do that in C++ with overloaded functions and templates (not overloaded functions alone), but templates are an example of higher order type inference, which was the point I was trying to convey.


Multimethods don't have that problem.


AddressCompare seems good to me.


Until you have an IDE that has to infer what it applies to.

Your global namespace would be so incredibly polluted.


Interfaces with multiple dispatch would basically be Haskell typeclasses.

Typeclasses have a lot of tricky corner cases in defining their semantics, they're hard to type-inference (Haskell's type system is undecidable in some cases), and they lead to a lot of confusion for new programmers trying to learn the language. MPTCs were deliberately not standardized in Haskell 2010, because the design space around them hasn't been fully explored, and SPJ's feeling was that Associated Types might be a better solution for the problems involved.

Go's designers have decided to focus on pragmatism over power (wisely, IMHO - I quibble with a lot of the other aspects of Go's language design, but not this one), and the result is that they get about 85% of the power of multimethods/typeclasses with about 10% of the headaches.


> Interfaces with multiple dispatch would basically be Haskell typeclasses.

Typeclasses are completely different from Go interfaces in a way that's totally unrelated to single versus multiple dispatch: in Haskell they're a bound on generic functions that specifies permissible operations, while Go interfaces are existential wrapped values. To give a concrete example of the difference here, you can write a function that takes a List of Eq values and pass a list of integers to it in Haskell, but in Go you cannot pass []int to a function that takes []Equal without recreating the list. On the other hand, in Go you can have a heterogeneous array of Equal interfaces, while in Haskell this is not possible without existential types.

> they're hard to type-inference (Haskell's type system is undecidable in some cases)

This is only when you combine the feature with type inference for functions (which Go doesn't have) and H-M style inference (which Go also doesn't have). Typeclasses would pose no problems for Go's extremely limited inference.

Pitting typeclasses against interfaces makes little sense to me; typeclasses are an enhancement to generics, while interfaces are about runtime polymorphism. Typeclasses have no meaning in a language without generics like Go (although I personally think Go will need generics eventually).


I think the same as you, and I suspect that in this respect Go was designed to appear familiar to programmers coming from other languages, and they decided to stick with the receiver syntax.


Auto-completion aka -suggestion.

Te first argument is usually a variable already declared one more more lines above your cursor, so you can start with that. Then your IDE kicks in and gives you a list of possible methods. In this case it is easy, if you want to use "length" or "size" for your list.


I see no reason why the IDE couldn't do that with multimethods. The trigger might have to be a little different but otherwise I see no issues here.


> The trigger might have to be a little different

The "dot-autocompletion" has a pretty good usability in my opinion. You write down the name of the variable, enter a dot and you get a filtered list of methods that "make sense" to call on the receiver (key word: discoverability of the API).

How exactly would one implement the trigger with multimethods? I think this is an important consideration to make from a usability standpoint.


Could swap the function name and argument list, so DoSomething(x,y,z) would become (x,y,z)DoSomething.

I admit it's a little weird, but not so weird people couldn't get used to it.


It's not that weird - you've pretty much described what a function call would look like in a language that uses Reverse Polish e.g. PostScript

   x y x DoSomething
However, I don't think I've seen an IDE for PostScript or Forth


But then we've lost another level of discoverability - putting the method name first also gives you information about the arguments for the method.


Instead of starting with methods, start from interfaces. An interface is an object along with a collection of functions that have the same object as one of their parameters. It's a whole lot easier to implement if that special parameter is always in the same position.

Methods are useful for implementing interfaces and for avoiding namespace clutter; otherwise they're equivalent to functions.

I have no idea how you get from multimethods to interfaces.


I think that's how interfaces work. You define an interface by the methods a receiver has (a struct complying to the ReadCloser interface for example must implement Read() and Close()). While one has to get used to it, it works very nicely in practice.


I don't think so. Interfaces dispatch based on the type of the one and only special purpose receiver.


I've read this sentence 6 times and have no idea what it means...


It means that one argument to the function is special, syntactically, and it is that argument (and only that argument) that determines whether a type implements an interface.


> Go doesn't have an implicit "this" pointer.

You want to say Go doesn't have an explicit "this" pointer.


No, what I mean is that you have to refer to the receiver explicitly even from within methods invoked on that same receiver, contrary to Java or C++ where 'this' is implicitly assumed when you refer to other members of the receiver.


Here is the article that got me onto the way of go: http://areyoufuckingcoding.me/2012/07/25/object-desoriented-... The memes are a bit much but the content is solid.


For an understanding how to do OO design in Go, learning about component programming is a good way.

http://www.amazon.de/Component-Software-Beyond-Object-Orient...

The first edition used Component Pascal from the Oberon family, which Go gets some influence from. Later editions used Java and C# instead.

Additional learning about how COM and XPCOM work is also a way to get some ideas.


Yes Go is similar to COM in that it focuses on interfaces and leaves out implementation inheritance which causes so much trouble. COM was way ahead of its time on that.

"Component Oriented Programming = Polymorphism + (Really) Late Binding + (Real, Enforced) Encapsulation + Interface Inheritance + Binary Reuse"

Apart from "Binary Reuse", the definition above is what object oriented programming is really all about (I think).


> To apply the uniform access principle to the Part type, we could change the struct definition and provide setter/getter methods

If you have to apply it, it isn't uniform access. The point of the principle is that the call-site shouldn't distinguish between getters and fields so that the implementer can change that decision freely without breaking callers.

Nice article, though!


Thanks for the clarification.


I have a question about go. Let's say I have a struct Foo and a function/method for Less. What is the quickest way to sort an array of Foo's?

Do I have to implement my own []Foo type with all three methods as described in the sort package?



So the answer is yes, I have to implement the whole interface?


Yes, to use code that relies on an interface you must implement the interface for your types.

That is one of the reasons go has a guiding design principle that interfaces should be as small as possible.


Yes, generics are not (yet??) possible in go


Is there any advantage to using OO in a language that doesn't have classes or inheritance? Does declaring a method on a type give you something that declaring a function that takes the type as an argument doesn't?


OO does not have to use classes and inheritance. OO is about encapsulation of data and behaviors. Declaring a function on a type lets you specify the behaviors of the type. In particular, in languages like Go with interfaces, it allows you to have a contract of behaviors independent of implementations. You cannot do that with plain functions.


OOP is about polymorphism. Classes and inheritance are not necessary, just like encapsulation or private data. In long: http://beza1e1.tuxen.de/articles/oop.html

The question is not where the type syntactically is. The question is, if the dispatching between functions/methods happens dynamically or statically.


There are many ways to implement OO.

Self, JavaScript, CLOS, Go, BETA, Clojure Protocols, Type Classes, COM and probably quite a few others.

What most mainstream developers know, is not the only way.


+1 for mentioning BETA. Any actual user of that language?


BETA did come up in this HN post from last year: The impoliteness of overriding methods - https://news.ycombinator.com/item?id=4943538


I got to learn about it when I attended ECOOP'99.

Never saw it outside the conference.


BETA was the beginner's language at the University of Dortmund (Germany) back in 1996/7/8. Never saw it outside of these buildings.


Really?! Living in Düsseldorf, small world HN. :)


Yes, I still have my book "Object Oriented Programming in the BETA Programming Language" by Madsen/Møller-Pedersen/Nygaard with a price tag of 80 DM in 1997 (> 40 EUR now) (I now live in Berlin)


No, but the Racket OO system is based on BETA's implementation of OO, and Racket has some actual users. There's definitely some interesting ideas there.

For example, `inner` being the opposite of super (e.g. call the subclasses implementation of this method). Some others, but I'm not sure I remember them (Augmentation?).


The lack of inheritance and creation methods are a bit annoying at times (particularly the latter), but there's still benefits of adopting an OOP approach in Go.

For example, say you're writing a CMS and want to make strings like the article title to be used in the URL (for readable URLs and SEO), displayed in HTML (eg inside heading tags) and also potentially used for string comparisons, you'd need to have a function to URL / HTML encode the string and you'd need to remember to do it each time you outputted the string. This can make it very easy to introduce vulnerabilities where you forget to HTML encode the string. With methods, you can force the developer to state which format to output the string as:

    type DisplayText struct {
        Value string
    }
    
    func (dt DisplayText) HTMLEscaped() string {
        return html.EscapeString(dt.Value)
    }
    
    func (dt DisplayText) URLEscaped() string {
        return url.QueryEscape(dt.Value)
    }
    
    var article_title DisplayText
So now when ever you call article_title, you have to specify the string encoding. Which is not only more secure (eliminates the risk of forgetting to encode your string), but also more readable:

    fmt.Println(article_title.HTMLEscaped)
vs

    fmt.Println(html.EscapeString(article_title))


The last line was probably intended as

  fmt.Println(html.EscapeString(article_title))


You're right, it was. I'll correct that now.


> Is there any advantage to using OO in a language that doesn't have classes or inheritance?

Like Self or javascript?


JavaScript and Self don't have classes, but they do have inheritance.


Self has delegation. Conflating delegation and inheritance is no better than conflating embedding and inheritance.


Delegation seems pretty similar to inheritance to me. What am I missing?


It doesn't set up is-a relationships and can be used for significantly more than differential inheritance (IIRC Self uses it for scoping).


> Is there any advantage to using OO in a language that doesn't have classes or inheritance?

Yes. Really, interfaces are all you need for OO; class are just types-that-also-define-interfaces, and inheritance is just a shortcut for interface implementation.

While I think Go's implicit interface implementation is pretty much 180-degrees off the best way to do OO w/o classes and inheritance (I'd prefer explicit interface implementation, which eliminates interface collision), I do think clasess and inheritance get in the way more than they help.


Too many people were trained to think in the OO style, especially class-based OO, no matter if it fits the problem or not. Any language with prototype but not classes will end up with hand-crafted classes. Any language without neither will end up with hand-crafted prototypes.


Yes. It gives you Liskov substitution.


One of the most common problems people have when they get to know Go is to not embrace its approach to programming, but try to enforce what they already know in Go programs.

This is a clear example of that. Implementing “polymorphism” with Go interfaces is abusing interfaces! Seeing embedding as inheritance misses the point of both embedding and inheritance. Comparing packages to namespaces isn’t that bad, but just delays you getting to know Go for real.

I’m not saying that these old concepts are bad or Go’s new approach is superior. (I believe in that; but that’s not the point here.) I’m saying if you’ve gotten used to these concepts so much that you can’t think or program without them, then there’s a problem. Don’t design a solution around polymorphism or inheritance and then try hard to force that into Go. Design your program with that your language is giving you.


Did you RTFA? The conclusion has:

> Composition, embedding and interfaces provide powerful tools for Object-Oriented Design in Go. Thinking in terms of inheritance really doesn't work. Trust me, I tried.

The OP is clearly trying to introduce ideas in Go by relating them to other ideas you might be more familiar with. This is, IMO, very much distinct from trying to shoehorn design techniques from other languages.

In fact, the OP is rather explicitly agreeing with your point and trying to remedy it.

> Implementing “polymorphism” with Go interfaces is abusing interfaces!

Go's interfaces use structural subtyping, which is a form of polymorphism.


I see pretty much the same problem with JavaScript. It's everywhere -- framework developers are busily implementing polymorphism, inheritence etc. all over the place. Yet I can't recall ever choosing to use "inheritance" instead of composition in my JavaScript projects.


Off-topic, but it would be nice if the blog didn't use 24-pixel body type, so that I could see more than a handful of lines at a time on my laptop.

I've never seen a book with 24-point body type, except possibly for the visually impaired or 3-year-olds. Heck, most headings aren't even 24 point. What is it with this blog trend of gargantuan type, which seems increasingly common? It's totally out of proportion with the rest of anyone's OS and computer interface, and all the common sites. I'm really getting tired of having to zoom out to an insane 50% level just to make things legible again.


Off-topic: You'll be disappointed to learn that I also use an 18-pt font in my editor. I happen to like big readable text, maybe something to do with my age and my prescription being -5.5.

P.S. the font-size is actually 1.5rem


34yo, -9.5 here, and I prefer 10pt fonts for code.

Do you not wear your glasses and/or contacts when working? I don't follow.


This preference could also depend on the font and what's rendering it; on my Thunderbolt Display, while "18pt" Consolas renders at less than 7 points wide using OS X defaults, Windows 8, using defaults for the same display, renders the same font at around 8.5 points wide by default, and Myriad Pro's "M" at nearly 13 points wide. Since 13/7 is approximately 18/10, your respective preferences may not be as different as you think.


Good article - this covers the basic OO patterns in Go. I really like the simplicity of the design - objects are just structs and you can define a method for that object. Coming from C#, it's like every method is an extension method.

The only critique I have is that there's no way to verify that object X implements methods A, B, and C. When I'm using C# or Java, I often use an interface as a test to make sure I've done my work since the code won't compile if a class that extends an interface fails to implement all the methods.


The compiler will complain if you attempt to assign (or assert) a value to an interface that it cannot fulfill.

ex. http://play.golang.org/p/OLTHIXjgy8


"Compared to duck typing, interfaces are statically checked and documented through their declaration..."


To verify that Concrete implements Interface use this:

var _ Interface = Concrete{}




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: