Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: Casey Flynn
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: Casey Flynn

An Introduction To Programming In Go By Caleb Doxsey

By
Published in , Comments (4)

At InVision, we have a top-down directive to move all of our programming over to Google's Golang programming language. As someone who loves to program in ColdFusion and JavaScript, I've put this off as long as possible (somewhat hoping it would be a passing fancy). But, the time has come for me to actually start digging into Golang (Go). So, this week, I sat down and read An Introduction to Programming in Go by Caleb Doxsey. This book is a gentle and accessible introduction to Golang which has left me with a basic ability to have [somewhat] meaningful converstations with my Go-obsessed teammates.

Introduction to Programming in Go by Caleb Doxsey, review by Ben Nadel.

An Introduction to Programming in Go covers everything from how to install Golang, up through basic types and variables, all the way past Pointers and Closures, through Structs and Methods, and even covers Concurrency via Channels and Locking and basic Testing practices. In 165 pages, it doesn't do any of this in significant depth; but, it successfully paints the landscape of the Go language, leaving the reader with a solid foundation on which to continue learning.

Before I read this book, looking at Golang code felt much akin to reading Sanskrit. Now, having read the book, Go still seems funky; but, at least I feel like we're all speaking the same language (no pun intended).

To be honest, Golang doesn't seem like a very enjoyable language. While reading the book, I didn't find myself with any "ah ha!" moments - no points of clarity in which it was suddenly clear as to why all the swelling popularity. Some features, like "named return types," even seem downright misguided.

And of course, you all know how little I love linting and how much I love my own formatting. I can already tell that the inability to wrap my for-loop conditions in parenthesis is going to wear my soul down to a tiny numb. And why do my Struct literals have to end with a trailing-comma - this is pure madness!

But, perhaps much of this point of view stems from my lack of experience. Hopefully, the more I learn, the more it becomes obvious why people love Golang. And, perhaps with time, I too will learn to love Golang. I mean, after all, Captain Picard was almost a full-on Borg - assimilation is possible.

So, here's my "Hello world" for Golang. I created a simple, yet - for me - non-trivial script that consumes two different "Greeter" packages. They both implement the same interface, but one can wrap the other one, altering the "greeting message" as it is generated.

Immediately, the part of this "Hello world" scenario that I found most challenging was the general file and package structure. In another programming language, I would keep variations of a "behavior" in the same folder. However, in Golang, a single folder - which represents a single package - can't export two public Functions with the same name; so, you can't have swappable behaviors live in the same package (at least, not that I can figure out on day one). As such, I found myself creating several small packages, which Mike Frey already tells me is an established anti-pattern in the Golang community.

  • one.go
  • greeter / greeter / greeter.go
  • greeter / interfaces / interfaces.go
  • greeter / shouter / greeter.go

As you can see, there are two "greeter.go" packages, each of which exposes a Struct that implements a common interface (provided by interfaces.go):

NOTE: Forgive me if the terminology is incorrect about "implementing" and "packages" - I'm quite out of my element and I'm sure I'm making mistakes.

package interfaces;

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

type IGreeter interface {
	Greet( name string ) string;
}

As you can see, this interface requires a Greet() function which accepts a string and returns a string. Interestingly enough, it doesn't appear that interfaces can define the parameterization of Functions, which strikes me as a bit odd. But, again, I'm still learning.

Now, the basic Greeter doesn't need to know about this interface since it only deals with its own message generation:

package greeter;

// Import the core packages.
import "fmt";

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

type Greeter struct {
	template string;
}

// ---
// CONSTRUCTOR METHODS.
// ---

func New( template string ) *Greeter {

	var greeter = &Greeter {
		template: template,
	};

	return( greeter );

}

// ---
// PUBLIC METHODS.
// ---

func ( this *Greeter ) Greet( name string ) string {

	return( fmt.Sprintf( this.template, name ) );

}

... but, the Shouter has to know about the Interface since it proxies any Greeter instance, transforming the greeting message as it is generated:

package shouter;

// Import the core packages.
import "fmt";
import "strings";

// Import the application packages.
import "golang-book/greeter/interfaces";

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

type Greeter struct {
	target interfaces.IGreeter;
	template string;
}

// ---
// CONSTRUCTOR METHODS.
// ---

func New( target interfaces.IGreeter, template string ) *Greeter {

	var greeter = &Greeter {
		target: target,
		template: template,
	};

	return( greeter );

}

// ---
// PUBLIC METHODS.
// ---

func ( this *Greeter ) Greet( name string ) string {

	var baseGreeting = this.target.Greet( name );
	var shoutGreeting = strings.ToUpper( fmt.Sprintf( this.template, baseGreeting ) );

	return( shoutGreeting );

}

As you can see, the Shouter actually uses the injected Greeter to generate the base message. Then, the Shouter consumes the base message in order to generate its own message.

The main script then instantiates (or maybe "allocates", if that's the right terminology) both of the Greeter types, wrapping one inside the other:

package main;

// Import the core packages.
import "fmt";

// Import the application packages.
import "golang-book/greeter/greeter";
import "golang-book/greeter/interfaces";
import "golang-book/greeter/shouter";

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

type example struct {
	label string;
	greeter interfaces.IGreeter;
	name string;
}

func main() {

	var g = greeter.New( "Good morning, %v." );

	// When creating the shouting greeters, we need to pass in an instance that
	// implements the IGreeter interface. Both the Greeter and the Shouting Greeter
	// implement IGreeter, so a Shouting Greeter can wrap both a Greeter and a
	// Shouting Greeter.
	var s = shouter.New( g, "Yoooo! %v" );
	var s2 = shouter.New( s, "%v Kick some ass!" );

	// For no other reason than trying out Arrays / Slices, let's aggregate all the
	// greeter instances into a set of examples, and then loop over them.
	var examples = []example {
		example {
			label: "Greet:",
			greeter: g,
			name: "Sarah",
		},
		example {
			label: "Shout:",
			greeter: s,
			name: "Kim",
		},
		example {
			label: "Shout 2:",
			greeter: s2,
			name: "Joanna",
		},
	};

	// Iterate over each example and try out the greeter instance.
	for _, value := range examples {

		fmt.Println( red( value.label ), value.greeter.Greet( value.name ) );

	}

}

// I return the given value wrapped in RED ASCII delimiters for the terminal.
func red( value string ) string {

	return( "\x1b[31;1m" + value + "\x1b[0m" );

}

As you can see, the Shouter type can wrap both Greeter types and Shouter types because both the Greeter and the Shouter adhere / implement the same IGreeter interface. And, when we run the above code in the terminal, we get the following output:

Introduction to Programming in Go - test output.

I'm sure that experienced Golang developers are looking at this code and wincing with all the mistakes that I'm making. My use of semi-colons alone is probably enough to drive one or two of you crazy. What can I say, I like hugging my code with parenthesis and semi-colons.

The use of key-casing as a means to indicate exported identifiers is just funky. Does that just feel normal after a while?

Writing this code felt janky and frustrating. And, it may not be anywhere near "idiomatic" Golang; but, at least I was able to write it after reading an Introduction To Programming In Go by Caleb Doxsey (and a little bit of Googling when I was stuck). So, to that end, this was a highly effective book!

Want to use code from this post? Check out the license.

Reader Comments

15,902 Comments

@Andy,

Ha ha, to be clear, I still do ColdFusion ... but, it has, unfortunately, been end-of-lifed at work. That said, there will likely be legacy maintained for a good period of time (things never get done as quickly as anyone expects). But, it does make me sad.

I need to find ways to stay current on CF in a more casual capacity.

8 Comments

Can you share the reasons why Go was chosen? Unless it's a strategic move, wouldn't something else that's more established make more sense, like Java flavors (Scala, Clojure) or Node.js, since you are already running under the JVM and JavaScript engines?

15,902 Comments

@Evagoras,

Unfortunately, I don't have a great reason. I was just told "it would help us scale". Personally, I can't stand the Golang syntax. I would much rather use something like Node.js (probably because I am very familiar with JavaScript). I've tried a few times to play with Go; and, each time, it just feels like I'm fighting so hard.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel