A Rubyist has Some Difficulties with Go

4,640 views
Skip to first unread message

Bob Hutchison

unread,
Jun 26, 2012, 5:11:06 PM6/26/12
to golang-nuts

Hi,

I've posted an article here: http://xampl.com/so/2012/06/26/a-rubyist-has-some-difficulties-with-go/ that I'd appreciate some feedback on.

I'm coming from Ruby/Clojure and am considering using Go for a project that I'd normally use one of those two languages for. I've prototyped bits and pieces of the project and have come across some difficulties. This post describes them briefly.

This isn't meant to be a critique of Go, more of a heads up to Ruby programmers as they consider Go for larger projects.

Cheers,
Bob

Peter Bourgon

unread,
Jun 26, 2012, 5:44:37 PM6/26/12
to Bob Hutchison, golang-nuts
Some thoughts...

> Nonconformity with Uniform Access Principle

I've not heard of this principle by name before now, but as I
understand it, my sense in Go is that it often _does_ matter "whether
[services offered by a module] are implemented through storage or
through computation." Go makes a point to eliminate ambiguity; ie. "In
Go, the code does exactly what it says on the page"[0].

[0] http://go-lang.cat-v.org/quotes

> Lack of Struct Immutability

You're right: you can only achieve this by convention. I agree in
abstract, but I'm not sure I've ever had a problem with this in
practice.

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

This has been a huge and noticeable gain for me far more often than
it's been annoying. I believe it is Go's position that overloading and
optional arguments increase ambiguity too much, and I support that
conclusion.

> Unyielding Enforcement of ‘Unused’ Errors

I agree it is annoying in the rare instance that I need to do it to
debug things. Happily, this happens to me very rarely.

> Lack of Go Routine Locals

Thread-local or goroute-local storage is, I think as was explained
earlier in the list, an antipattern.

> Lack of Weak References
> Limits to Polymorphism and Method Dispatch

And thank goodness for that!

I believe your example reveals some problems with your underlying
assumptions. First, there is no real concept of base/derived
inheritance in Go; embedding has similar properties but shouldn't be
thought of as the same. If a Sprinter interface embeds the Walker
interface, that means that type UsainBolt is guaranteed to be able to
Walk() as well as Run(), but it makes no statement on the
implementation of either.

Similarly, the fact that you define interface-methods whose names
imply their implementation details reveals some misconception toward
the idiomatic use of interfaces. In Go, you compose interfaces, ie.
behavior-specifiers, to form contracts that can be satisfied by
concrete implementations. You then reason via the contracts, rather
than the implementations.

Without getting into too much detail, I think the reason you're
sweating is that you're trying to solve a problem by transliterating
OOP paradigms into a language that deliberately doesn't "support"
them. The most productive path to breaking through this wall is
usually by asking you what specific problem you're trying to solve,
and then stepping through a more idiomatic approach.

€0.02,
Peter.

Rémy Oudompheng

unread,
Jun 26, 2012, 5:51:07 PM6/26/12
to Bob Hutchison, golang-nuts
Here is my opinion on these matters:

* about UAP: Go favours explicitness over tricky syntaxes. A
consequence of that is that it should be clear, what can cause side
effects, and what does not (causing side effects is *not* a low-level
feature). What if assign to a property launches a goroutine? Efficient
debugging relies on being able to quickly identify fishy lines of
code, and variable.Field = value is not a fishy line of code.
Uniformness is meant to be achieved through interfaces only.

* about immutability: it feels true that Go offers few ways of
protecting an API from bad uses. Exposing only interfaces and not
structs are a way to achieve that. But probably the best way to
protect a framework's internals is to not expose them at all. It is
certainly hard, but it is probably an important part of API design.

* about optional arguments: it has been discussed many times in the
ML. I didn't feel a lack of argument optionality. If I had to take the
defense of it, I would say it is much easier to document the behaviour
of a small number of suitably chosen similar functions, than to
document an ultra-polymorphic function that suports 2^n combinations
of arguments. The interactions are too unpredictable.

* about unused variables: it's not just about cleaning code, it's
about avoiding actual programming errors (wrong scoping, typos, etc.).
The "unused" compiler error saved me from many bugs and I really don't
mind cleaning up my code if I can save some bugs. Uunsed imports are a
different matter. If Go had warnings, it could be a warning, but I
think unused variables should remain an error.

* goroutine locals: this is already discussed elsewhere.

* weak references: i probably lack the background to understand what
it is and how it can be useful

* polymorphism and method dispatch: I think I actually do not
understand the purpose of the snippet you post. It assumes
object-oriented programming is familiar to programmers (along with its
usual tricks and idioms). It is true for many people, but not
necessarily for any programmers. Your example needs the notion that
types (or rather, OOP classes) are not individual entities but instead
collections of types (of variable sizes). In Go a type is itself and
nothing else: there is no possible ambiguity on its behaviour. Again,
interfaces are the way to express dispatch and their behaviour is
quite simple to express.

You may find latest post by Rob Pike
(http://commandcenter.blogspot.fr/2012/06/less-is-exponentially-more.html)
quite interesting.

Rémy.

Robert Johnstone

unread,
Jun 26, 2012, 6:25:23 PM6/26/12
to golan...@googlegroups.com
I will also attempt to comment on your points.

- UAP:  The language author's prefer explicit behaviour to implicit behaviour.  A property access that may in fact be an expensive computation is a leaky abstraction, so it may be better to make the distinction explicit.  Additionally, Go is not really OOP language.  This is just a personal view, but Go appears to support keeping data as POD, which fits with some of its other more functional aspects.

- immutability:  I agree with you that immutability would be useful.  I don't recall the authors ever commenting on it, so it may just be a question of language maturity, and it may arrive at some point in the future (not shallow const, but a deep const).  However, this is a "just" a protection mechanism.  While it might be useful in programs with multiple developers, it does not affect the range of programs that you can write.

- arguments:  Overloading will never arrive in Go.  While I miss it from time to time, the trade-off is the excellent reflect package.  I believe this also eliminates optional parameters.  In principle, Go could support default values for trailing arguments (using a similar rule as C++).  That would not affect method signatures or the reflect package, but I still doubt it will arrive.  I believe that standard practice is to ensure defaults are identical to zero or nil, which helps partially with filling in default arguments.  Again, this is a question of style, and it does not affect the range of programs that you can write.

- unused errors:  Learn to enjoy wearing the hair shirt!  Seriously, while this causes short-term pain, it is all to easy to move on to something else once the code is running.  The benefit of this strictness is that your code stays clean.

- Go routine locals:  This is just a Go-version of thread locals, which are generally considered bad practice (just another form of global variables).  However, like global variables, they can be useful in some limited situations.  Still, this is a code smell, and most of the time indicates that something is wrong with your architecture.

- Weak references:  Perhaps I'm confusing this with weak pointers in C++, but why would you want such a thing in a garbage collected language?

- Method dispatch:  This is the largest section in your post, so I suspect this is your major concern.  Fortunately, the answer is simple.  If you want polymorphism, use an interface.  This means that you cannot delegated to a "derived class" (which does not exist in Go), so you'll need to reorganize your code into freestanding functions that operates on one or more interfaces.  Again, Go is not really an OOP language.

Good luck

Jesse McNelis

unread,
Jun 26, 2012, 7:40:23 PM6/26/12
to Bob Hutchison, golang-nuts
On Wed, Jun 27, 2012 at 7:11 AM, Bob Hutchison
<hutch-...@recursive.ca> wrote:

* Method dispatch

Polymorphism in Go comes from interfaces.
Code reuse in Go comes from embedding and functions.

If you want common functionality shared between a number of types then
you make them implement a common interface and write a function that
takes types that satisfy that interface.
eg. the standard library has a lot of shared functionality for
io.Readers. To use that functionality your type just has to satisfy
the io.Reader interface.




--
=====================
http://jessta.id.au

Ian Lance Taylor

unread,
Jun 26, 2012, 9:03:29 PM6/26/12
to Bob Hutchison, golang-nuts
Bob Hutchison <hutch-...@recursive.ca> writes:

> I've posted an article here: http://xampl.com/so/2012/06/26/a-rubyist-has-some-difficulties-with-go/ that I'd appreciate some feedback on.

Others have made good responses, I'll just add some notes from perhaps a
slightly different perspective.

Language design is always a series of tradeoffs. Every change has both
benefits and drawbacks. As much as possible, the choices made for Go
have been biased toward actually writing programs rather than following
any particular theory of language design.

1) The Uniform Access Principle is a theory about how languages should
work. Is it a good idea in general? I don't know. Is it a good idea
for Go? No. The benefit is that the user of a type doesn't have to
care how some data is implemented. The drawback is that in real
programs, it matters. Go of course permits you to apply the UAP to your
own types, by always using methods. But it does not require you to do
so. If your framework will benefit from providing consistent access to
all fields, then implement them using a method.

2) Go in general permits immutable structs: don't export the struct,
don't export its fields, provide methods to retrieve values but not
change them. For example, the reflect.Value type works this way. The
trick with immutability is keeping it out of the type system.

3) Optional and default arguments would interact poorly with interfaces.
So would overloading. It's very simple and efficient that interface
methods are recognized only by name. Changing that property would make
the language significantly more complex and hard to understand. Named
function arguments could be implemented by requiring that methods match
not only on type, but also on the argument name. I don't know how
useful that would be. Many people implement a similar idea by using a
struct to hold the arguments.

4) Unused errors. One of the issues with writing large programs is
eliminating cruft. Go keeps cruft down by design. It's a tradeoff.

5) Goroutine locals don't really make sense when goroutines are cheap.

6) I'm not sure how I feel about weak references. Perhaps there would
be a reasonable way to add them. I don't think they should go into the
type system, though. And to be useful they should really go into slices
and maps, but then the complexity increases.

7) I think your polymorphism code should simply be designed differently
in Go. Use interfaces for common functionality. Use embedding for
shared implementation. Don't combine them--they aren't the same thing
in Go, although they are the same in C++.

Ian

Bob Hutchison

unread,
Jun 27, 2012, 7:53:56 AM6/27/12
to Peter Bourgon, golang-nuts

On 2012-06-26, at 5:44 PM, Peter Bourgon wrote:

> Some thoughts...
>
>> Nonconformity with Uniform Access Principle
>
> I've not heard of this principle by name before now, but as I
> understand it, my sense in Go is that it often _does_ matter "whether
> [services offered by a module] are implemented through storage or
> through computation." Go makes a point to eliminate ambiguity; ie. "In
> Go, the code does exactly what it says on the page"[0].

I think maybe this comes down to your views on encapsulation. If you are an encapsulationista then you will insist that it is the service's responsibility to provide a result in whatever way that it thinks best, and the client does not want to be concerned with how it's done just that it's done. If you are an excapsulationista (I don't know what the opposite of encapsulate so I'm saying it's excapsulate :-) and it sounds as though you might be, then you will want the client to be responsible for manipulating the service and extracting the information that it wants in its own context. The encapsulationistas will say this is a maintenance nightmare :-)

I don't think there's an issue of ambiguity here either way... just a question of which page has the unambiguous expression of what's to happen: the service or the client? Do you see what I mean?

>
> [0] http://go-lang.cat-v.org/quotes
>
>> Lack of Struct Immutability
>
> You're right: you can only achieve this by convention. I agree in
> abstract, but I'm not sure I've ever had a problem with this in
> practice.

I've seen it a problem as code evolves and the originators start leaving the team. Conventions get lost. This is a problem that's compounded by issues vaguely related to encapsulation, for example, UAP. Go strictly enforces certain rules (e.g. unused variables and imports) but leaves other important things to convention -- obviously this is a necessity so I'm just pointing this out for -- especially for -- the benefit of the migrant Ruby programmer, but hopefully for anyone with an OO background.

>
>> Lack of Optional/Default/Named Function Arguments, and No Overloading
>
> This has been a huge and noticeable gain for me far more often than
> it's been annoying. I believe it is Go's position that overloading and
> optional arguments increase ambiguity too much, and I support that
> conclusion.

Again, I don't think it's ambiguity that's the problem. It's where explicitness must/should reside, client or service?

>
>> Unyielding Enforcement of ‘Unused’ Errors
>
> I agree it is annoying in the rare instance that I need to do it to
> debug things. Happily, this happens to me very rarely.
>
>> Lack of Go Routine Locals
>
> Thread-local or goroute-local storage is, I think as was explained
> earlier in the list, an antipattern.
>
>> Lack of Weak References
>> Limits to Polymorphism and Method Dispatch
>
> And thank goodness for that!
>
> I believe your example reveals some problems with your underlying
> assumptions. First, there is no real concept of base/derived
> inheritance in Go; embedding has similar properties but shouldn't be
> thought of as the same. If a Sprinter interface embeds the Walker
> interface, that means that type UsainBolt is guaranteed to be able to
> Walk() as well as Run(), but it makes no statement on the
> implementation of either.

Not if UsainBolt does something that a walker would do before trying to run. If a method is dispatched on a Walker, in Go you'll lose the fact that the Walker is also a Sprinter. You'll not lose this in any OO language. This is the point I was trying to make.

>
> Similarly, the fact that you define interface-methods whose names
> imply their implementation details reveals some misconception toward
> the idiomatic use of interfaces.

Naw, I was just trying to make it clear what was going on by naming things :-) This is definitely not the code where I encountered the problem.

> In Go, you compose interfaces, ie.
> behavior-specifiers, to form contracts that can be satisfied by
> concrete implementations. You then reason via the contracts, rather
> than the implementations.

So what are methods for? Aside from confusing OO programmers :-)

>
> Without getting into too much detail, I think the reason you're
> sweating is that you're trying to solve a problem by transliterating
> OOP paradigms into a language that deliberately doesn't "support"
> them. The most productive path to breaking through this wall is
> usually by asking you what specific problem you're trying to solve,
> and then stepping through a more idiomatic approach.

Sure, no doubt. But I'm also writing with the migrant Ruby programmer in mind.

Thanks for your comments, very interesting.

Cheers,
Bob

Bob Hutchison

unread,
Jun 27, 2012, 8:08:30 AM6/27/12
to Rémy Oudompheng, golang-nuts

On 2012-06-26, at 5:51 PM, Rémy Oudompheng wrote:

> On 2012/6/26 Bob Hutchison <hutch-...@recursive.ca> wrote:
>>
>> Hi,
>>
>> I've posted an article here: http://xampl.com/so/2012/06/26/a-rubyist-has-some-difficulties-with-go/ that I'd appreciate some feedback on.
>>
>> I'm coming from Ruby/Clojure and am considering using Go for a project that I'd normally use one of those two languages for. I've prototyped bits and pieces of the project and have come across some difficulties. This post describes them briefly.
>>
>> This isn't meant to be a critique of Go, more of a heads up to Ruby programmers as they consider Go for larger projects.
>>
>
> Here is my opinion on these matters:
>
> * about UAP: Go favours explicitness over tricky syntaxes. A
> consequence of that is that it should be clear, what can cause side
> effects, and what does not (causing side effects is *not* a low-level
> feature). What if assign to a property launches a goroutine? Efficient
> debugging relies on being able to quickly identify fishy lines of
> code, and variable.Field = value is not a fishy line of code.
> Uniformness is meant to be achieved through interfaces only.

It's not "explicitness" I think, it's encapsulation vs. excapsulation (or whatever). The only conclusion that I can reach as an OO programmer is that variable.Field = value is a Very Bad Idea and that I should be providing accessors, so I'll force variable.setField(value) is the syntax required. What UAP admits is that that's ugly and unnecessary that you should be able to write variable.Field = value as a synonym for the setter method.

>
> * about immutability: it feels true that Go offers few ways of
> protecting an API from bad uses. Exposing only interfaces and not
> structs are a way to achieve that. But probably the best way to
> protect a framework's internals is to not expose them at all. It is
> certainly hard, but it is probably an important part of API design.

I agree.

>
> * about optional arguments: it has been discussed many times in the
> ML. I didn't feel a lack of argument optionality. If I had to take the
> defense of it, I would say it is much easier to document the behaviour
> of a small number of suitably chosen similar functions, than to
> document an ultra-polymorphic function that suports 2^n combinations
> of arguments. The interactions are too unpredictable.

The documentation task is identical if the functionality is the same. Optional arguments are normally provided as arguments with a default value, this is not hard to document. Named arguments just allow the order of arguments to vary according to the desires of the client. I'm not sure what you mean by ultra-polymorphic? Maybe the multi-dispatch of CLOS?

>
> * about unused variables: it's not just about cleaning code, it's
> about avoiding actual programming errors (wrong scoping, typos, etc.).
> The "unused" compiler error saved me from many bugs and I really don't
> mind cleaning up my code if I can save some bugs. Uunsed imports are a
> different matter. If Go had warnings, it could be a warning, but I
> think unused variables should remain an error.

To be clear, I am talking about during development not during production.

>
> * goroutine locals: this is already discussed elsewhere.
>
> * weak references: i probably lack the background to understand what
> it is and how it can be useful
>
> * polymorphism and method dispatch: I think I actually do not
> understand the purpose of the snippet you post. It assumes
> object-oriented programming is familiar to programmers (along with its
> usual tricks and idioms).

Definitely, the post is written with a migrant Ruby programmer in mind, but hopefully for other OO programmers as well.

> It is true for many people, but not
> necessarily for any programmers. Your example needs the notion that
> types (or rather, OOP classes) are not individual entities but instead
> collections of types (of variable sizes). In Go a type is itself and
> nothing else: there is no possible ambiguity on its behaviour. Again,
> interfaces are the way to express dispatch and their behaviour is
> quite simple to express.

I don't imagine there's any ambiguity in Go, just as there isn't in Ruby. The issue is only that what looks like a method dispatch in Go isn't anything like a method dispatch in Ruby or any other OO language.

>
> You may find latest post by Rob Pike
> (http://commandcenter.blogspot.fr/2012/06/less-is-exponentially-more.html)
> quite interesting.
>

I did indeed, and even referenced it in my post :-)

Thanks for you comments.

> Rémy.

Bob Hutchison

unread,
Jun 27, 2012, 8:37:53 AM6/27/12
to Robert Johnstone, golan...@googlegroups.com
On 2012-06-26, at 6:25 PM, Robert Johnstone wrote:

I will also attempt to comment on your points.

- UAP:  The language author's prefer explicit behaviour to implicit behaviour.  A property access that may in fact be an expensive computation is a leaky abstraction, so it may be better to make the distinction explicit.  Additionally, Go is not really OOP language.  This is just a personal view, but Go appears to support keeping data as POD, which fits with some of its other more functional aspects.

I don't think its necessarily a leaky abstraction, plenty of OO languages implement this with out leaking the abstraction. Nobody is saying that the client can be oblivious to the cost of an operation, but if that's the cost and the client wants it done, the client is going to have to pay.

But that's not your point I think. I think you're saying that UAP has the possibility of hiding from the user that what looks like accessing the attribute (e.g. how_many_dead) will actually launch the missiles before counting the dead. I think that almost everyone would agree that that's a crappy API, and once you're writing crappy APIs there's no reasoning with you :-) Obviously because I brought the issue up, I don't think that UAP is worth sacrificing in an ultimately losing battle against crappy APIs.

Explicit vs implicit is an interesting thought. But I'll just point out that just because it's not written in the client's code doesn't mean it's not completely explicit on the service's side.


- immutability:  I agree with you that immutability would be useful.  I don't recall the authors ever commenting on it, so it may just be a question of language maturity, and it may arrive at some point in the future (not shallow const, but a deep const).  However, this is a "just" a protection mechanism.  While it might be useful in programs with multiple developers, it does not affect the range of programs that you can write.

True, witness Clojure and Haskell. And that's not my goal. I just like to be sure that nobody inadvertently uses some idiomatic Go that breaks the application.


- arguments:  Overloading will never arrive in Go.  While I miss it from time to time, the trade-off is the excellent reflect package.  I believe this also eliminates optional parameters.  In principle, Go could support default values for trailing arguments (using a similar rule as C++).  That would not affect method signatures or the reflect package, but I still doubt it will arrive.  I believe that standard practice is to ensure defaults are identical to zero or nil, which helps partially with filling in default arguments.  Again, this is a question of style, and it does not affect the range of programs that you can write.

I agree, but a Ruby programmer might get tripped up.


- unused errors:  Learn to enjoy wearing the hair shirt!  Seriously, while this causes short-term pain, it is all to easy to move on to something else once the code is running.  The benefit of this strictness is that your code stays clean.

I in no way want this feature to go away, just go away for a couple of minutes. Unfortunately, my code doesn't stay clean, it's loaded up with comments all through the file that I have to remove.


- Go routine locals:  This is just a Go-version of thread locals, which are generally considered bad practice (just another form of global variables).  However, like global variables, they can be useful in some limited situations.  Still, this is a code smell, and most of the time indicates that something is wrong with your architecture.

I agree. But when you do have one of those limited situations you really wish you had them.


- Weak references:  Perhaps I'm confusing this with weak pointers in C++, but why would you want such a thing in a garbage collected language?

The weak pointer/references I'm talking about make sense only in a GC language. A strong pointer/reference will prevent the GC from collecting the object. A weak pointer/reference will not. Very useful in cache-like situations.


- Method dispatch:  This is the largest section in your post, so I suspect this is your major concern. 

Yep, you're right :-)

Fortunately, the answer is simple.  If you want polymorphism, use an interface.  This means that you cannot delegated to a "derived class" (which does not exist in Go), so you'll need to reorganize your code into freestanding functions that operates on one or more interfaces.  Again, Go is not really an OOP language.

I know it's not OOP :-) What's the point of methods in Go? They will only dispatch from a type, not an interface, and promptly lose the runtime/dynamic type of the dispatching argument.



Good luck

Thanks! And thanks for your comments

Cheers,
Bob

Bob Hutchison

unread,
Jun 27, 2012, 8:38:54 AM6/27/12
to Jesse McNelis, golang-nuts
This makes sense. But what's the point of methods in Go then?

Cheers,
Bob

>
>
> --
> =====================
> http://jessta.id.au

Peter Bourgon

unread,
Jun 27, 2012, 8:40:27 AM6/27/12
to Bob Hutchison, golang-nuts
> If you are an encapsulationista then you will insist that it is the service's responsibility to provide a result in whatever way that it thinks best, and the client does not want to be concerned with how it's done just that it's done. If you are an excapsulationista (I don't know what the opposite of encapsulate so I'm saying it's excapsulate :-) and it sounds as though you might be, then you will want the client to be responsible for manipulating the service and extracting the information that it wants in its own context. The encapsulationistas will say this is a maintenance nightmare :-)

Interesting way of framing the discussion.

<opinion>
As a client of a service (in the broadest sense), I definitely want to
know—or rather, specify—exactly what actions my request will effect. I
want transparency in my call-stack. I absolutely don't want the server
to be "clever" and deduce that because my request came in during the
year of the dragon, it should be treated differently than if Saturn
were in the seventh house.

I'm very confused that someone could interpret this position as a
"maintenance nightmare". From where I sit it is exactly the opposite.
"Cleverness" in the server cannot be deterministically analyzed,
reasoned about, or predicted.

> I don't think there's an issue of ambiguity here either way... just a question of which page has the unambiguous expression of what's to happen: the service or the client? Do you see what I mean?

I believe so, but I can't grok that this is a debatable point ;) The
answer is in the client, obviously. It is the authoritative source of
what it wants to do. When you strip that authority from it, and make
decisions for it in a way it can neither know about or affect, your
system becomes a black box, and your developers cease to be able to
confidently manipulate it.
</opinion>

> Not if UsainBolt does something that a walker would do before trying to run. If a method is dispatched on a Walker, in Go you'll lose the fact that the Walker is also a Sprinter. You'll not lose this in any OO language. This is the point I was trying to make.

That "the Walker is also a Sprinter" is an implementation detail. A
function which takes a Sprinter interface as a parameter necessarily
doesn't care about the fact that the concrete type satisfying that
particular parameter may also be a Walker. The code using the Sprinter
interface cares only about the contracts that the Sprinter interface
defines.

This is the type of reasoning that interfaces enable:
interface/behavior-domain, rather than implementation-domain. It's
different than is-a inheritance, as Ian mentioned earlier.

Bob Hutchison

unread,
Jun 27, 2012, 8:59:13 AM6/27/12
to Ian Lance Taylor, golang-nuts

On 2012-06-26, at 9:03 PM, Ian Lance Taylor wrote:

> Bob Hutchison <hutch-...@recursive.ca> writes:
>
>> I've posted an article here: http://xampl.com/so/2012/06/26/a-rubyist-has-some-difficulties-with-go/ that I'd appreciate some feedback on.
>
> Others have made good responses, I'll just add some notes from perhaps a
> slightly different perspective.
>
> Language design is always a series of tradeoffs. Every change has both
> benefits and drawbacks. As much as possible, the choices made for Go
> have been biased toward actually writing programs rather than following
> any particular theory of language design.
>
> 1) The Uniform Access Principle is a theory about how languages should
> work. Is it a good idea in general? I don't know. Is it a good idea
> for Go? No. The benefit is that the user of a type doesn't have to
> care how some data is implemented. The drawback is that in real
> programs, it matters. Go of course permits you to apply the UAP to your
> own types, by always using methods. But it does not require you to do
> so. If your framework will benefit from providing consistent access to
> all fields, then implement them using a method.

I'm not sure I buy the "real program" argument :-) What's different between a real program and a not real program?

>
> 2) Go in general permits immutable structs: don't export the struct,
> don't export its fields, provide methods to retrieve values but not
> change them. For example, the reflect.Value type works this way. The
> trick with immutability is keeping it out of the type system.

If you don't export the struct what do you export? Surely not interface{}?

>
> 3) Optional and default arguments would interact poorly with interfaces.

I think it depends on how you treat them. The compiler has a lot of freedom, including, for example (with no real thought behind the suggestion) simply inserting the default values at the call point.

> So would overloading.

Personally, I don't know if I like overloading, so personally, I'm not missing them.

> It's very simple and efficient that interface
> methods are recognized only by name.

Which, I think in Go, is equivalent to being recognised by signature. No?

> Changing that property would make
> the language significantly more complex and hard to understand. Named
> function arguments could be implemented by requiring that methods match
> not only on type, but also on the argument name. I don't know how
> useful that would be.

The compiler sees a canonical form of the method, the user sees what's convenient? Since you can't overload the name and signature are equivalent, and this gives the compiler some room to play.

> Many people implement a similar idea by using a
> struct to hold the arguments.

I showed an example using a map, but a struct would be better in that it controls the possible values and their types.

>
> 4) Unused errors. One of the issues with writing large programs is
> eliminating cruft. Go keeps cruft down by design. It's a tradeoff.

And in production I'll take what Go does anytime. The trouble is that five minutes where you're trying to work through some issue while developing.

>
> 5) Goroutine locals don't really make sense when goroutines are cheap.

Hmm? Not sure what you mean here. Yes, cheap goroutines change a lot of things, and yes they can deal with some otherwise very complex situations very neatly (this is a *huge* part of why I started using Go in the first place). But I don't see how cheap goroutines change the sensibility of locals?

>
> 6) I'm not sure how I feel about weak references. Perhaps there would
> be a reasonable way to add them. I don't think they should go into the
> type system, though. And to be useful they should really go into slices
> and maps, but then the complexity increases.

I have no doubt that in Go the compiler's complexity increases. I think it's worth spending some time trying to express them in Go in a way reasonable to developers and feasible for implementation.

>
> 7) I think your polymorphism code should simply be designed differently
> in Go. Use interfaces for common functionality. Use embedding for
> shared implementation. Don't combine them--they aren't the same thing
> in Go, although they are the same in C++.

I think you are right about this. I just wanted to point this out to migrant Rubyists. I also, personally, don't have a model of how these interact in a design (I know how they work, but how do you work with them together)

Thanks for you comments.

Cheers,
Bob

>
> Ian

Dmitry Maluka

unread,
Jun 27, 2012, 9:32:05 AM6/27/12
to golan...@googlegroups.com
On 06/27/2012 03:59 PM, Bob Hutchison wrote:
> If you don't export the struct what do you export? Surely not
> interface{}?

You can export a struct but hide some or all of its fields. (Just in
case - those whose names aren't capitalized.)

Ian Lance Taylor

unread,
Jun 27, 2012, 10:47:22 AM6/27/12
to Bob Hutchison, golang-nuts
Bob Hutchison <hutch-...@recursive.ca> writes:

> On 2012-06-26, at 9:03 PM, Ian Lance Taylor wrote:
>
>> 1) The Uniform Access Principle is a theory about how languages should
>> work. Is it a good idea in general? I don't know. Is it a good idea
>> for Go? No. The benefit is that the user of a type doesn't have to
>> care how some data is implemented. The drawback is that in real
>> programs, it matters. Go of course permits you to apply the UAP to your
>> own types, by always using methods. But it does not require you to do
>> so. If your framework will benefit from providing consistent access to
>> all fields, then implement them using a method.
>
> I'm not sure I buy the "real program" argument :-) What's different
> between a real program and a not real program?

The Uniform Access Principle is a theoretical idea. There are many such
theoretical ideas in language design. There are many programs written
in support of these theoretical ideas.

When I say a "real program" I simply mean a program written to solve a
real problem, rather than a program written to investigate a language.


>> 2) Go in general permits immutable structs: don't export the struct,
>> don't export its fields, provide methods to retrieve values but not
>> change them. For example, the reflect.Value type works this way. The
>> trick with immutability is keeping it out of the type system.
>
> If you don't export the struct what do you export? Surely not interface{}?

Well, I pointed you at reflect.Value as an example. Here is a somewhat
different trivial example inline:

type unexportedStructType {
val int
}

var ImmutableStruct = unexportedStructType{1}

func (p *ImmutableStruct) GetVal() int {
return p.val
}

Now another package can use ImmutableStruct.GetVal() to retrieve the
value of val, but that other package has no way to change val.

Go gives you control over visibility at several levels, so there are
other approaches you could take. Returning an interface would be one of
them. I don't see why it would be an interface{}--I think it would be
an interface with methods that let you pick up the values you want.


>> 3) Optional and default arguments would interact poorly with interfaces.
>
> I think it depends on how you treat them. The compiler has a lot of freedom, including, for example (with no real thought behind the suggestion) simply inserting the default values at the call point.

type I interface {
Lookup(name string, id int) *P
}

func (p *T1) Lookup(name string, id int) *P {
...
}

func (p *T2) Lookup(name string, id int = 2) *P {
...
}

func (p *T3) Lookup(name, string, id int, id2 int = 4) *P {
...
}

Clearly the type T1 satisfies the interface I. What about the types T2
and T3?

If we can have default arguments for type methods, can we have them for
interface methods as well?

It is possible to answer questions like this, but it means additional
complexity, it makes the language harder to understand, it makes
programs harder to understand. Is it worth it?

>> It's very simple and efficient that interface
>> methods are recognized only by name.
>
> Which, I think in Go, is equivalent to being recognised by signature. No?

Recognizing by name is much simpler than recognizing by signature. You
don't have to puzzle out the exact rules about type identity. You can
just look at the names.


>> 4) Unused errors. One of the issues with writing large programs is
>> eliminating cruft. Go keeps cruft down by design. It's a tradeoff.
>
> And in production I'll take what Go does anytime. The trouble is that
> five minutes where you're trying to work through some issue while
> developing.

It's true: Go is inflexible and eschews compiler options. We've seen
too many cases where an option is set "just during development" and is
never removed "because we don't have time now."


>> 5) Goroutine locals don't really make sense when goroutines are cheap.
>
> Hmm? Not sure what you mean here. Yes, cheap goroutines change a lot
> of things, and yes they can deal with some otherwise very complex
> situations very neatly (this is a *huge* part of why I started using
> Go in the first place). But I don't see how cheap goroutines change
> the sensibility of locals?

When I start a new goroutine, it no longer has the same set of goroutine
local variables. So adding a go statement is no longer purely
introducing concurrency into the program; it is also changing the
meaning of variables. The same problem exists for thread local
variables, but because creating a new thread is a relatively expensive
operation, people don't notice it.


>> 6) I'm not sure how I feel about weak references. Perhaps there would
>> be a reasonable way to add them. I don't think they should go into the
>> type system, though. And to be useful they should really go into slices
>> and maps, but then the complexity increases.
>
> I have no doubt that in Go the compiler's complexity increases. I
> think it's worth spending some time trying to express them in Go in a
> way reasonable to developers and feasible for implementation.

I'm not talking about the complexity of the compiler. I'm talking about
the complexity of understanding the language, and programs written in
the language.

Ian

Ian Lance Taylor

unread,
Jun 27, 2012, 10:50:04 AM6/27/12
to Bob Hutchison, Jesse McNelis, golang-nuts
Bob Hutchison <hutch-...@recursive.ca> writes:

> This makes sense. But what's the point of methods in Go then?

Methods in Go permit a type to satisfy an interface.

An interface is a collection of methods that implement a specific
functionality.

E.g., there are many types in the standard library that implement the
io.Writer interface: e.g., files, sockets, buffers. And there are many
functions in the standard library that take an io.Writer parameter: e.g,
fmt.Print and friends.

This allows for very convenient coding: you can use fmt.Print with a
file, and you can also use it with a type that automatically encrypts
all data sent on it.

Ian

Dmitry Maluka

unread,
Jun 27, 2012, 11:29:20 AM6/27/12
to golan...@googlegroups.com
On 06/27/2012 05:47 PM, Ian Lance Taylor wrote:
> Well, I pointed you at reflect.Value as an example. Here is a somewhat
> different trivial example inline:
>
> type unexportedStructType {
> val int
> }
>
> var ImmutableStruct = unexportedStructType{1}
>
> func (p *ImmutableStruct) GetVal() int {
> return p.val
> }
>
> Now another package can use ImmutableStruct.GetVal() to retrieve the
> value of val, but that other package has no way to change val.
>

Ian, I'm a bit confused with your code. First I expected it wouldn't
compile, with "ImmutableStruct is not a type" error. But when I tried it
at http://play.golang.org/p/ifKHtuleSz, the error message was slightly
different:

prog.go:11: invalid indirect of ImmutableStruct (type unexportedStructType)


Now a question to everyone: what does that "invalid indirect" mean?

Dmitry Maluka

unread,
Jun 27, 2012, 11:40:07 AM6/27/12
to golan...@googlegroups.com
On 06/27/2012 05:47 PM, Ian Lance Taylor wrote:
>>> 4) Unused errors. One of the issues with writing large programs is
>>> eliminating cruft. Go keeps cruft down by design. It's a tradeoff.
>> And in production I'll take what Go does anytime. The trouble is that
>> five minutes where you're trying to work through some issue while
>> developing.
> It's true: Go is inflexible and eschews compiler options. We've seen
> too many cases where an option is set "just during development" and is
> never removed "because we don't have time now."

+1. Coding in a rush is a bad idea anyway. If one is too hurried to
remove unused declarations during development or prototyping, he is
likely to be too hurried to remove compilation options before a
production release. :)

Patrick Mylund Nielsen

unread,
Jun 27, 2012, 11:44:52 AM6/27/12
to Dmitry Maluka, golan...@googlegroups.com
Indeed, this is my biggest problem with (significant) warnings and being able to toggle strictness. The majority of people, or at least a significant percentage of them, are just going to ignore the warnings and keep the options turned on, and that means an ecosystem of lower-quality code with "hacks to get it to compile."

Source: Virtually all C Makefiles :)

Norbert Roos

unread,
Jun 27, 2012, 11:51:53 AM6/27/12
to golan...@googlegroups.com
On 06/27/2012 05:40 PM, Dmitry Maluka wrote:

> +1. Coding in a rush is a bad idea anyway. If one is too hurried to
> remove unused declarations during development or prototyping, he is
> likely to be too hurried to remove compilation options before a
> production release. :)

And he is also likely to be too hurried to remove all the "_ =
unusedVar" from the source code, which go forced him to insert.

Norbert

Dmitry Maluka

unread,
Jun 27, 2012, 11:59:56 AM6/27/12
to golan...@googlegroups.com
Did it? I don't see why inserting "_ = unusedVar" would be faster than
removing unusedVar declaration.

Or you don't want to remove it because you'll need it later? Why not
comment it, then?

Andy Balholm

unread,
Jun 27, 2012, 12:01:02 PM6/27/12
to golan...@googlegroups.com, Bob Hutchison, Jesse McNelis
On Wednesday, June 27, 2012 7:50:04 AM UTC-7, Ian Lance Taylor wrote:
Bob Hutchison writes:

> This makes sense. But what's the point of methods in Go then?

Methods in Go permit a type to satisfy an interface.

And satisfying an interface has nothing to do with an inheritance hierarchy.

When you define a method on a type, its implementation should be closely bound to the implementation of the type. It shouldn't expect to polymorphically handle other related types.

Polymorphism is best handled by functions that take parameters of an interface type.

I rewrote the example from your blog post in more idiomatic go at http://play.golang.org/p/GuTniuNMnG
I also added a new type, IntThing, which has an implementation of the Thing interface which is completely unrelated to Base and Derived, to illustrate that satisfying an interface is completely unrelated to inheriting implementation.

Andy

Norbert Roos

unread,
Jun 27, 2012, 12:12:03 PM6/27/12
to golan...@googlegroups.com
On 06/27/2012 05:59 PM, Dmitry Maluka wrote:

> Or you don't want to remove it because you'll need it later? Why not
> comment it, then?

1. Go allows such things as

usedVar, unusedVar := f()

2. When i'm trying to locate an error, it may happen that i deactivate
and activate parts of the code again (and again), making variables
unused and used again. I would have to track the declaration at a
possibly complete different location.

Norbert

Dmitry Maluka

unread,
Jun 27, 2012, 12:26:24 PM6/27/12
to golan...@googlegroups.com
usedVar, /*unusedVar*/ _ := f()

Is it ok for you?

Ian Lance Taylor

unread,
Jun 27, 2012, 1:58:22 PM6/27/12
to Dmitry Maluka, golan...@googlegroups.com
Dmitry Maluka <dmitry...@gmail.com> writes:

> On 06/27/2012 05:47 PM, Ian Lance Taylor wrote:
>> Well, I pointed you at reflect.Value as an example. Here is a somewhat
>> different trivial example inline:
>>
>> type unexportedStructType {
>> val int
>> }
>>
>> var ImmutableStruct = unexportedStructType{1}
>>
>> func (p *ImmutableStruct) GetVal() int {
>> return p.val
>> }
>>
>> Now another package can use ImmutableStruct.GetVal() to retrieve the
>> value of val, but that other package has no way to change val.
>>
>
> Ian, I'm a bit confused with your code. First I expected it wouldn't
> compile, with "ImmutableStruct is not a type" error. But when I tried it
> at http://play.golang.org/p/ifKHtuleSz, the error message was slightly
> different:
>

Sorry, I messed up the example. I should have written

type unexportedStructType struct {
val int
}

var ImmutableStruct = unexportedStructType{1}

func (p *unexportedStructType) GetVal() int {
return p.val
}


> prog.go:11: invalid indirect of ImmutableStruct (type unexportedStructType)
>
> Now a question to everyone: what does that "invalid indirect" mean?

When the compiler saw *ImmutableStruct it thought I was trying to
indirect through the pointer named ImmutableStruct. But ImmutableStruct
is a struct, not a pointer. So it is an invalid indirect: an indirect
on a value that does not have pointer type.

Ian

Bob Hutchison

unread,
Jun 27, 2012, 3:31:13 PM6/27/12
to Bob Hutchison, golang-nuts

On 2012-06-26, at 5:11 PM, Bob Hutchison wrote:

>
> Hi,
>
> I've posted an article here: http://xampl.com/so/2012/06/26/a-rubyist-has-some-difficulties-with-go/ that I'd appreciate some feedback on.

I've posted a followup to this article here: http://xampl.com/so/2012/06/27/followup-to-a-rubyist-has-some-difficulties-with-go-limits-to-polymorphism-and-method-dispatch-in-go/

Thanks for all the comments, I think they helped. You can check for yourself if I've improved my understanding of the limits to polymorphism etc. that was concerning me in the first post.

Cheers,
Bob

Bob Hutchison

unread,
Jun 27, 2012, 3:33:43 PM6/27/12
to Ian Lance Taylor, golang-nuts

On 2012-06-27, at 10:47 AM, Ian Lance Taylor wrote:

>>> 2) Go in general permits immutable structs: don't export the struct,
>>> don't export its fields, provide methods to retrieve values but not
>>> change them. For example, the reflect.Value type works this way. The
>>> trick with immutability is keeping it out of the type system.
>>
>> If you don't export the struct what do you export? Surely not interface{}?
>
> Well, I pointed you at reflect.Value as an example. Here is a somewhat
> different trivial example inline:

What did you mean by "don't export the struct"? This is what I was asking about. It seems to me that reflect.Value has exported the struct or am I missing something?

Cheers,
Bob

Bob Hutchison

unread,
Jun 27, 2012, 3:39:29 PM6/27/12
to Andy Balholm, golan...@googlegroups.com, Jesse McNelis
On 2012-06-27, at 12:01 PM, Andy Balholm wrote:

On Wednesday, June 27, 2012 7:50:04 AM UTC-7, Ian Lance Taylor wrote:
Bob Hutchison writes:

> This makes sense. But what's the point of methods in Go then?

Methods in Go permit a type to satisfy an interface.

And satisfying an interface has nothing to do with an inheritance hierarchy.

Yes, this was where I was going wrong. I have attempted to explain myself in the followup post.


When you define a method on a type, its implementation should be closely bound to the implementation of the type. It shouldn't expect to polymorphically handle other related types.

Polymorphism is best handled by functions that take parameters of an interface type.

I rewrote the example from your blog post in more idiomatic go at http://play.golang.org/p/GuTniuNMnG
I also added a new type, IntThing, which has an implementation of the Thing interface which is completely unrelated to Base and Derived, to illustrate that satisfying an interface is completely unrelated to inheriting implementation.

Thanks, that's a nice example.

Cheers,
Bob


Andy

Bob Hutchison

unread,
Jun 27, 2012, 3:49:27 PM6/27/12
to sdeg...@8thlight.com, golan...@googlegroups.com, Peter Bourgon

On 2012-06-27, at 12:05 PM, sdeg...@8thlight.com wrote:

> hutch:
>
> In response to the idea that clients should not know how services retrieve data, I have written a blog post at http://blog.8thlight.com/steven-degutis/2012/04/26/ruby-accessors-considered-pernicious.html on the matter.
>
> In this article I assert that it is an anti-pattern to require simple simple data and computation (fields and methods) to be accessible in the same way. The underlying assumption behind UAP is that the client should not know the implementation details of what it is using. But in practice, this leads to all sorts of bad quality designs and other problems.
>
> Consider accessing the text of a tweet via "tweet.text". The person who wrote or maintains this code might assume it is an instant operation, just implemented as a struct-field access. Thus this person will code with the assumption that it will return immediately and with valid data. Yet in reality, if this makes an HTTP request, it might not return immediately, but only after 60 seconds! It also might return an error code which there is no way to access using this accessor syntax. This may be circumvented with exceptions, but (at least in Ruby, where exceptions are possible) this results in strange conventions, such as having accessors that might throw TimeOut exceptions, with no enforcement for this strange behavior's documentation.
>
> This is just one example of the strange things that may happen when you consider that methods and fields should be accessed using the same syntax to prevent any knowledge of the implementation. Often times, we really do want to know at least a little about what mechanisms are used in the implementation, even if they may change in the future, so we can plug this portion of our code into the bigger picture program successfully and without odd errors. In other words, the program should do exactly what the code on the page says it does.

I understand your argument, and to a limited extent I agree, certainly that doing stupid things in what looks like an accessor is stupid. However, just because something is often unnecessary or can be used in a stupid way doesn't mean it should be banned.

Cheers,
Bob

Andy Balholm

unread,
Jun 27, 2012, 3:53:20 PM6/27/12
to golan...@googlegroups.com, Bob Hutchison
On Wednesday, June 27, 2012 12:31:13 PM UTC-7, hutch wrote:
I've posted a followup to this article here: http://xampl.com/so/2012/06/27/followup-to-a-rubyist-has-some-difficulties-with-go-limits-to-polymorphism-and-method-dispatch-in-go/

Thanks for all the comments, I think they helped. You can check for yourself if I've improved my understanding of the limits to polymorphism etc. that was concerning me in the first post.

Just one quibble. You said:

It is not necessary to explicitly state that a type satisfies an interface, though you may, the compiler is able to work that out. 

But there is no way to explicitly state that a type satisfies an interface (except in a comment, of course). 

chris dollin

unread,
Jun 27, 2012, 4:04:03 PM6/27/12
to Andy Balholm, golan...@googlegroups.com, Bob Hutchison
On 27 June 2012 20:53, Andy Balholm <andyb...@gmail.com> wrote:

> But there is no way to explicitly state that a type satisfies an interface
> (except in a comment, of course).

var _ TheInterfaceType = AnExpressionOfTheSatisfyingType

Chris

--
Chris "allusive" Dollin

Bob Hutchison

unread,
Jun 27, 2012, 4:07:07 PM6/27/12
to Andy Balholm, golan...@googlegroups.com
Yes. Thank you. Fixed.

Dmitry Maluka

unread,
Jun 27, 2012, 4:23:43 PM6/27/12
to Ian Lance Taylor, golan...@googlegroups.com
On 06/27/2012 08:58 PM, Ian Lance Taylor wrote:
>> prog.go:11: invalid indirect of ImmutableStruct (type unexportedStructType)
>>
>> Now a question to everyone: what does that "invalid indirect" mean?
> When the compiler saw *ImmutableStruct it thought I was trying to
> indirect through the pointer named ImmutableStruct. But ImmutableStruct
> is a struct, not a pointer. So it is an invalid indirect: an indirect
> on a value that does not have pointer type.
>
> Ian

Ok, as far as I understand now, the compiler was dealing with the AST
node *ImmutableStruct treating it as a pointer indirection. But why it
did that in the first place? According to
http://golang.org/ref/spec#Method_declarations there is no place for a
pointer indirection, syntactically. This behavior looks strange to me,
and the resulting error message is a bit confusing. (And if we replace
ImmutableStruct by a pointer variable, we will get "is not a type"
error, as expected.)

Sorry for driving this thread a little off-topic...

Ian Lance Taylor

unread,
Jun 27, 2012, 5:01:11 PM6/27/12
to Dmitry Maluka, golan...@googlegroups.com
I don't know. Consider opening an issue for a confusing compiler error
message.

For reference, gccgo prints this:

foo.go:9:10: error: expected type
func (p *ImmutableStruct) GetVal() int {
^
Ian

Ian Lance Taylor

unread,
Jun 27, 2012, 5:02:45 PM6/27/12
to Bob Hutchison, golang-nuts
You're right, my terminology was confusing. Sorry about that. The type
reflect.Value is indeed exported. But none of the fields are exported.
So values of type reflect.Value are immutable outside of the reflect
package.

As in my example, other approaches are possible too.

Ian

Robert Johnstone

unread,
Jun 27, 2012, 6:10:09 PM6/27/12
to golan...@googlegroups.com, Robert Johnstone


On Wednesday, 27 June 2012 08:37:53 UTC-4, hutch wrote:
I don't think its necessarily a leaky abstraction, plenty of OO languages implement this with out leaking the abstraction. Nobody is saying that the client can be oblivious to the cost of an operation, but if that's the cost and the client wants it done, the client is going to have to pay.
 
If you need to be aware of how much computation is behind every field access, then you are facing a leaky abstraction.  In most cases the abstraction is good enough that users don't notice, but that doesn't change the underlying concern.  I'm not discounting UAP, just pointing out the trade-off involve.

But that's not your point I think. I think you're saying that UAP has the possibility of hiding from the user that what looks like accessing the attribute (e.g. how_many_dead) will actually launch the missiles before counting the dead. I think that almost everyone would agree that that's a crappy API, and once you're writing crappy APIs there's no reasoning with you :-) Obviously because I brought the issue up, I don't think that UAP is worth sacrificing in an ultimately losing battle against crappy APIs.

It is funny that you should write the above, as I've made the exact same argument in other places.  Trying to protect yourself from crappy APIs is a lost cause, and shouldn't prevent you from including genuinely useful features because of the possibility of abuse.  
 
I know it's not OOP :-) What's the point of methods in Go? They will only dispatch from a type, not an interface, and promptly lose the runtime/dynamic type of the dispatching argument.
Thanks! And thanks for your comments
 
This is one of the most difficult things to grasp about Go.  In most language, dispatch tables are packaged inside the types, in Go, they are packaged alongside the types.  The purpose of methods is to create method sets.  When you package a concrete type inside an interface, the method set defines the functions that can be used to implement that interface.  I suppose that methods could be replaced with free functions, but the use of methods simplifies the runtime's job of locating the functions that are applicable for a given type.  It may not be obvious, but the dispatch tables for interfaces are often constructed at runtime.

Jesse McNelis

unread,
Jun 27, 2012, 7:17:13 PM6/27/12
to Bob Hutchison, sdeg...@8thlight.com, golan...@googlegroups.com, Peter Bourgon
On Thu, Jun 28, 2012 at 5:49 AM, Bob Hutchison
<hutch-...@recursive.ca> wrote:
> I understand your argument, and to a limited extent I agree, certainly that doing stupid things in what looks like an accessor is stupid.
>However, just because something is often unnecessary or can be used in a stupid way doesn't mean it should be banned.

The outcome of having it is that it makes code harder to read for
everyone for the benefit of the few that actually use the feature.
Most accessors are just an assignment, but if accessors can be
function calls then I have to careful when I read code that does
assignment in all code I read.
Assigment in Go isn't an interesting line of code, it's so simple that
I can largely recognise the pattern and move on to the next line.
If assignment can be a function call then I need to be aware of the
types involved and whether they have accessor functions that are being
called.

In other people's code this is very annoying.


--
=====================
http://jessta.id.au

Norbert Roos

unread,
Jun 28, 2012, 5:48:07 AM6/28/12
to golan...@googlegroups.com
On 06/27/2012 06:26 PM, Dmitry Maluka wrote:

>> 2. When i'm trying to locate an error, it may happen that i deactivate
>> and activate parts of the code again (and again), making variables
>> unused and used again. I would have to track the declaration at a
>> possibly complete different location.
>
> usedVar, /*unusedVar*/ _ := f()
>
> Is it ok for you?

It is ok for me to have a compiler switch to temporarily ignore unused
variables, because i do not enable this switch in the final compiler run.

You cannot prevent people from using the '_ = v' trick, which may result
in ugly code, if the people don't remove this later. If you cannot
prevent people from creating ugly code, why then forbid the "ignore
unused variables" switch, which only purpose is to avoid ugly code? Even
worse, by using this trick the compiler can't tell any more which
variables are used and which are not - if i temporarily allow unused
variables, i just disable the switch to make the compiler show all the
unused variables.

And your suggestion doesn't help in the second case. The easiest
solution for the second case is '_ = v'.

I want to be able to use a screw driver to quickly put a small nail into
a light wall; i don't want it to tell me that it's not the correct tool
and that i have to get a hammer for this..

(BTW: Are these lines long or are they wrapped? How can word wrapping
after 72 characters be enabled in Thunderbird? Didn't find anything so
far..)

Norbert

Rémy Oudompheng

unread,
Jun 28, 2012, 7:00:09 AM6/28/12
to Bob Hutchison, golang-nuts
On 2012/6/27 Bob Hutchison <hutch-...@recursive.ca> wrote:
> On 2012-06-26, at 5:51 PM, Rémy Oudompheng wrote:
>> * about unused variables: it's not just about cleaning code, it's
>> about avoiding actual programming errors (wrong scoping, typos, etc.).
>> The "unused" compiler error saved me from many bugs and I really don't
>> mind cleaning up my code if I can save some bugs. Uunsed imports are a
>> different matter. If Go had warnings, it could be a warning, but I
>> think unused variables should remain an error.
>
> To be clear, I am talking about during development not during production.

I am aware of that. Most people think that enabling all errors at
final compilation will be enough to catch all bugs. I think it is
false, and many bugs I have written during development would have
ended up in production if I had done so. For the same reason I think
this error is not just about tidying code up. I am very thankful to
the Go compiler authors for making this a mandatory error.

Most variables that "should be used but are unused due to a typo" get
finally used anyway because of logging or code structure. You *have
to* catch them during development by compiling very often, otherwise
they go unnoticed.

I even think it must be an error in any language that supports
shadowing variables in inner scopes. Even unused imports can actually
be bugs: you could have shadowed the package by a variable and
accidentally called a method instead of the package function. The
error must be caught ASAP and will disappear in the final code,
because the source file will contain both a correct and an incorrect
usage and the import that was unused during development (thus exposing
the bug) becomes used in the final file.

Regards,
Rémy.

Bob Hutchison

unread,
Jun 28, 2012, 8:35:53 AM6/28/12
to Rémy Oudompheng, golang-nuts

On 2012-06-28, at 7:00 AM, Rémy Oudompheng wrote:

> On 2012/6/27 Bob Hutchison <hutch-...@recursive.ca> wrote:
>> On 2012-06-26, at 5:51 PM, Rémy Oudompheng wrote:
>>> * about unused variables: it's not just about cleaning code, it's
>>> about avoiding actual programming errors (wrong scoping, typos, etc.).
>>> The "unused" compiler error saved me from many bugs and I really don't
>>> mind cleaning up my code if I can save some bugs. Uunsed imports are a
>>> different matter. If Go had warnings, it could be a warning, but I
>>> think unused variables should remain an error.
>>
>> To be clear, I am talking about during development not during production.
>
> I am aware of that. Most people think that enabling all errors at
> final compilation will be enough to catch all bugs.

I am *not* saying that, nor do I believe it in the slightest. I completely agree with you on that (and your examples are good ones).

What I'm thinking could be something like providing "go build-a-hacky-hack-version" as an alternative to the "go build"... and even let the compiler spew "YOU SAD EXCUSE FOR A PROGRAMMER! YOU DISGUST ME!" as many times as it wants to the console (maybe once for each unused variable)... or even only building an executable called 'a-sorry-hack'... or building an executable that will only run in the next 3 seconds... or even not building an executable at all, just running it, maybe even only in the debugger... just don't force me to comment out code, and then, even worse, uncomment it properly (and there's NO WAY I'm going to do the _ = xxx thing, even once... too much like crack)

Cheers,
Bob

Patrick Mylund Nielsen

unread,
Jun 28, 2012, 8:40:19 AM6/28/12
to Bob Hutchison, Rémy Oudompheng, golang-nuts
I think this is the gist. You shouldn't be able to build a hacky-hack-version -- or, at least it should be as "hard" as possible.

DisposaBoy

unread,
Jun 28, 2012, 9:04:50 AM6/28/12
to golan...@googlegroups.com
its actually quite easy to write a tool that automatically comment out or even delete unused local variables

Bob Hutchison

unread,
Jun 28, 2012, 9:26:04 AM6/28/12
to DisposaBoy, golan...@googlegroups.com

On 2012-06-28, at 9:04 AM, DisposaBoy wrote:

> its actually quite easy to write a tool that automatically comment out or even delete unused local variables


Even with lines like?

used, unused := new(B), new(B)

Dmitry Maluka

unread,
Jun 28, 2012, 10:02:24 AM6/28/12
to golan...@googlegroups.com
On 06/28/2012 12:48 PM, Norbert Roos wrote:
> You cannot prevent people from using the '_ = v' trick, which may
> result in ugly code, if the people don't remove this later.

True, you cannot prevent people from writing ugly code, in any language.

> If you cannot prevent people from creating ugly code, why then forbid
> the "ignore unused variables" switch, which only purpose is to avoid
> ugly code?

Because instead of hiding the problem (by making bad code not look ugly
at first glance), you'd better fix it. As long as bad code stays, it's
better for it to look as ugly as possible.

> Even worse, by using this trick the compiler can't tell any more which
> variables are used and which are not - if i temporarily allow unused
> variables, i just disable the switch to make the compiler show all the
> unused variables.
>
> And your suggestion doesn't help in the second case. The easiest
> solution for the second case is '_ = v'.

My suggestion was: always comment exactly that code (including
declarations) which is currently unused. This may take more time but
prevents you from forgetting what to revert later.

Or use a VCS. :)

> I want to be able to use a screw driver to quickly put a small nail
> into a light wall; i don't want it to tell me that it's not the
> correct tool and that i have to get a hammer for this..

Well, I still believe in "one tool per one job" philosophy. Software is
much more flexible than hardware in this regard. DisposaBoy has already
suggested to write a separate tool for dealing with unused declarations.
go/parser and go/ast are there.

> (BTW: Are these lines long or are they wrapped? How can word wrapping
> after 72 characters be enabled in Thunderbird? Didn't find anything so
> far..)

They are wrapped, don't worry. :) Check the message source via Ctrl+U.

> Norbert

DisposaBoy

unread,
Jun 28, 2012, 11:21:29 AM6/28/12
to golan...@googlegroups.com
you might have to take a little bit of care to make sure you do the right thing but the answer is yes.

Dmitry Maluka

unread,
Jun 28, 2012, 11:27:00 AM6/28/12
to Ian Lance Taylor, golan...@googlegroups.com
On 06/28/2012 12:01 AM, Ian Lance Taylor wrote:
> I don't know. Consider opening an issue for a confusing compiler error
> message.

Done: http://code.google.com/p/go/issues/detail?id=3783

> For reference, gccgo prints this:
>
> foo.go:9:10: error: expected type
> func (p *ImmutableStruct) GetVal() int {
> ^

Thanks. I was going to check this with gccgo myself but I was lazy to
install it. :)
Reply all
Reply to author
Forward
0 new messages