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