Jun 262012
 

Update: I’ve posted a followup Followup to A Rubyist has Some Difficulties with Go: Limits to Polymorphism and Method Dispatch in Go that addresses my current thoughts about the last point in this article.

Update: There’s a thread discussing this post on the Golang-nuts mailing list and I hope that pointer works for you.

Update: Steven Degutis has posted an article Ruby Accessors Considered Pernicious in response to part of this post and the thread on the golang group. He makes some reasonable statements, but as usual, I’ll take the position that while the usual situation shouldn’t play accessor tricks, it’s incredibly valuable for not-so-usual situations.

I’ve been using Golang for perhaps 18 months now, happily and successfully. I’ve implemented quite a number of smallish projects ranging from a couple of hundred lines to maybe a couple of thousand, as well a three or four larger projects of maybe around 5k lines each. Go was a very good fit for these, in some cases a spectacularly good fit.

Over the same period of time I’ve used Clojure and, especially, Ruby to implement several much larger projects. I’m now looking at a new project that I’d like to write in Go, one that I’d have, unhesitatingly, until now, writen in Ruby. I imagine I’m one of the unexpected migrants that Rob Pike talks about in his article from yesterday Less is exponentially more. Part of my decision process has been to prototype fragments of the solution in Go. This is where these difficulties arose. I’ve got ugly workarounds to all but the last issue, so it’s not like these are show stoppers (well maybe the last one).

Lets be clear here, I have a very high regard for Go. I intend to use it many times in the future. I recommend people consider it seriously. It just isn’t quite the breeze it seems it’ll be when coming from something like Ruby. I can’t help but think that they are consequences of the lower level nature of Go. And maybe that’s the interesting part of this article.

If I’m lucky, I’m totally wrong about them all, and someone can set me straight.

Nonconformity with Uniform Access Principle

The Uniform Access Principle (UAP) was articulated by Bertrand Meyer in defining the Eiffel programming language. This, from the Wikipedia Article pretty much sums it up: “All services offered by a module should be available through a uniform notation, which does not betray whether they are implemented through storage or through computation.”

Or, alternatively, from this article by Meyer: “It doesn’t matter whether that query is an attribute (representing an object field) or a function (representing a computation); this is an internal representation decision, irrelevant to clients accessing objects through calls such as [ATTRIB_ACCESS]. This “Principle of Uniform Access” — it doesn’t matter to clients whether a query is implemented as an attribute or a function”

Languages like Eiffel, Ruby, Common Lisp all satisfy the UAP, as does Python in it’s own way. Languages like C, C++, and Go do not.

In Go, the usual way of accessing an attribute of a struct is something like:

Person.Name = "Jack"
fmt.Println(Person.Name)

There is no opportunity to get in there and change how the value of the attribute is obtained. Of course, it’s possible to take a getter/setter approach:

Person.SetName("Jack")
fmt.Println(Person.GetName())
fmt.Println(Person.Name)

but this does not prevent the third line.

The problem with this is that when developing a framework you want to minimize the error-prone boiler plate that the user of the framework has to write. It is also desirable to minimise the non-idiomatic code (the Person.GetName() is non-idiomatic in Go).

Lack of Struct Immutability

By “immutability” I mean that there’s no way for a framework to prevent changes to structures that are supposed to be managed by the framework. This is related to Go’s non-conformity to the UAP (above). I don’t believe there’s a way to detect changes either.

This can probably be done by implementing immutable/persistent data structures along the line of what Clojure did, but these will not have literal forms like Arrays and Maps do now in Go. Once again, this is, in part, an issue of non-idiomatic use of Go.

Lack of Optional/Default/Named Function Arguments, and No Overloading

A function has a single signature, and that signature does not support either optional or named arguments. The consequence is that all arguments must always be provided in the function call. This is a potential source of errors and requires a lot of knowledge on the user’s part to know what the suitable “don’t care” values might be. When there usual usage involves a small number of parameters with the remaining being ‘the usual’ then we’ve also got more complex function calls that would be necessary. There’s a maintenance issue here as well since, effectively, the function calls are over specified. When the API has to change you have to consider each call much more carefully.

Library writers sometimes try to use the “zero” value for the arguments, recognize that when passed in, and substitute in the actual default value. This separates the code as it appears in usage from the meaning of the code, at the very least this is a documentation problem, and could represent a real source of error.

It’s possible to use a literal map to pass optional parameters. To do this the user will be expected to write something like:

type nparams map[string]interface{} // Probably defined once in the framework
...
SomeFunction(1, nparams{"p1": 2, "p2": "hello"})

Not pretty, but not completely disgusting either. The nparams is just a type alias that shortens the code and removes some gratuitous ugliness. Without the map of string to interface{} you’d be stuck with a single type of named argument. This technique makes the implementation of SomeFunction pretty ugly (but that’s where ugliness belongs if it has to be somewhere).

The usual safest way to handle this is to offer several different functions with different parameters and different names. This, of course, makes the APIs more complex and all the bad things that flow from that have to be contended with.

Unyielding Enforcement of ‘Unused’ Errors

The implementors of Go have made a couple of rules that I’m very happy that they’re enforcing. Two in particular: “there are no compiler warnings, only errors”, and “unused things (variables, imports, etc.) are errors”.

The annoying part is that a Go programmer can’t get around this even temporarily. So if there’s something they are trying to experiment with (i.e. figure out how it works) it isn’t always possible to just comment out surrounding code. Sometimes the commenting makes variables or import statements unused… and it won’t compile. So the programmer has to comment out still more code. This can happen a few times before it successfully compiles. Then the programmer has to uncomment the stuff just commented out. Error prone, messy, etc.

There are hacky ways to get around this, but these allow unused things to end up in the final code if programmers are careless/forgetful/busy/rushed.

This is really just an annoyance but it stands out, to me at least. Here’s a compiler that comes with tools for sophisticated functions like formatting, benchmarking, memory and CPU profiling, and even “go fix” yet they leave us temporarily commenting out code just to get it to compile? Sigh.

Lack of Go Routine Locals

There is no way to store Go routine specific data that’s accessible to any function executing in the go routine. This would be analogous to thread locals, but thread locals don’t make a lot of sense, maybe no sense at all, in Go. Yes, this is just an inconvenience but, again, the only ways that I’ve come across that get around it require error-prone, non-idiomatic, boiler-plate code.

Lack of Weak References

There’s no direct support for weak references in Go. There are tricks that can be done with finalizers that allow you do something to address the problem, or a variation of it at least (kinda like a limited form of strong pointers), but it isn’t the same. This is, again, an inconvenience, but it’d be nice. I’ve worked around it so far by having a go routine manage the resource, but once again error-prone, non-idiomatic, and boiler-plate code is involved.

Limits to Polymorphism and Method Dispatch

This is a big issue for me, I think the biggest so far. I want to avoid the type system jargon as much as possible.

I hope this makes sense.

Here’s the code:

The same code is on play.golang.org where you can actually execute it.

The output is:

Base...
Step1AsMethod (*main.Base)
Step2AsMethod (*main.Base/*main.Base) 0xf8400240a0 &main.Base{Thing:main.Thing(nil)}
Step1AsFunction (*main.Base) &main.Base{Thing:main.Thing(nil)}
Step2AsMethod (*main.Base/*main.Base) 0xf8400240a0 &main.Base{Thing:main.Thing(nil)}

Derived...
Step1AsMethod (*main.Base)
Step2AsMethod (*main.Base/*main.Base) 0xf840024230 &main.Base{Thing:main.Thing(nil)}
Step1AsFunction (*main.Derived) &main.Derived{Base:main.Base{Thing:main.Thing(nil)}}
Derived Step2AsMethod (*main.Derived)

The trouble is illustrated in the output of the Derived struct. When Step2AsMethod is run from Step1AsMethod it is the method defined for the Base struct that is run, not the one defined for Derived. When the Function Step1AsFunction is run the correct Step2AsMethod is called.

What is happening here is that the parameters to functions are polymorphic in interfaces and the runtime type/struct information is used to select the method. In the case of methods, the dispatching type/struct is the type used not the runtime struct/type, and so Step2AsMethod for the base struct/type is called.

What cannot be achieved is something equivalent to this simple Ruby program:

Of these problems, this one is my biggest concern. I’ve already run into it several times and have been able to work around it, but barely… had me sweating for a while.

I’m afraid that if I get trapped with this somehow the only way out will be a massive unmaintainable, ugly, error-prone type switch.

 Posted by at 5:05 pm