Tuesday, November 17, 2009

A Mental Model of Types and Methods in Go

I've been spending the past few days since Go's release wrapping my head around it. The language site has a lot of good information, but I wanted to write up my understanding of one part of the type system.

First off, while they're types in terms of syntax, I see Go's interfaces as semantically distinct; for most of this post, when I say "type" I mean a concrete or non-interface type.

Aside from those, Go has a lot of built-in types. To build a user-defined type, one simply aliases that type to a built-in type or to some other user-defined type. The built-in type defines the format of an instance in memory, but defines no methods. Methods are attached to user-defined types; which methods a type has may depend on various things I won't get into right now, but this set is fixed for any particular concrete type.

Interfaces act as a layer on top of user-defined types. A variable with an interface type can contain any instance of any concrete type whose method set includes all of the methods in the interface. Go is rather unusual in that no explicit declaration is necessary for a concrete type to implement an interface -- if the methods are there, the interface is considered to be implemented. Among other things, this means that an interface can be created after the fact to describe functionality common to several types, and all of those types will automatically be compatible with variables declared as instances of that interface.

This means that any particular variable carries three pieces of information. First, there's the actual value; this is not directly accessible via interface variables but present in all cases. Second, there is the runtime type -- that is, the concrete type of the variable; this is what reflect.Typeof will tell you about, and defines the method set that will be used. Finally, there is the compile-time type, which is either the runtime type or some interface it implements; this determines which methods can actually be called and whether access to the value is possible.

Each of these bits of information can be changed relatively independently. A type assertion can change the compile-time type without affecting either the runtime type or the value. A (non-numeric) conversion can change the runtime type as well as the compile-time type, as long as the in-memory representations are identical. And of course changes to the value will in general not affect either type associated with an instance.

One interesting result of this is that you can use conversions to swap out the method set of an instance in runtime. For example, sort.StringArray defines methods on a []string that define a particular sort ordering. One could easily write a ReversedStringArray type that induces a backwards sort. Applying conversions, it would even be possible to take the same array and sort it in different ways at different points in the program, all using the sort package, just by telling Go to treat the array as a different runtime type each time.

Friday, November 13, 2009

Thoughts on Go

Along with many other people, I've recently run into Go, a new programming language from Google. I think it's got a lot of potential, and I really want to play with the type system and see how it works out in practice.

I got it running on my server, and quickly got down to writing some thought-experiment code. Once I split the code out into packages, I had some initial difficulty getting it to compile. The Go source tree has some Makefile magic for that, but it presumes that you will want to install your packages into the $GOROOT/pkg directory for use after they're built, so I didn't want to use that directly. Luckily, another intrepid blogger saved me the trouble of figuring out how to build Go code manually.

Go is designed to be good for writing highly-concurrent network services. I think I'll try and bootstrap an XMPP server project in the language, if it feels fun to code the first bits.