Hacker News new | past | comments | ask | show | jobs | submit login

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.




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

Search: