Loose coupling in Go lang

22 Jan 2015

Go lang offers a wide range of features that help us achieve loosely coupled program designs. In this blog post I will talk about interfaces and higher-order functions.

1. Interfaces

In Go we can define a type interface by supplying a set of method definitions:

type /* name of interface */ interface {
     // method definitions, that is without implementation
     // e.g. sum(a, b int) int
}

Let's say we're working on a chess program, and we want to define an interface to present the game in multiple I/O devices. We need a method to get the input from the user, and another one to present the output.

type IO interface {
    Read() string
    Write(message string)
}

When we call Read we expect a string back, and when we call Write we pass in a message. In Go, any value that implements those two functions satisfies the interface. For example:

type CommandLine struct{}

func (cli CommandLine) Read() string {
    var input string

    fmt.Scanf("%s\n", &input)

    return input
}

func (cli CommandLine) Write(output string) {
    fmt.Printf("%v", output)
}

This is especially useful for testing. We can create a FakeIO type and have complete control over its input and output to verify their contents.

type FakeIO struct{
    Input string
    Output string
}

func (io FakeIO) Read() string {
    return io.Input
}

func (io FakeIO) Write(output string) {
    io.Output = output
}

Now we can stub and spy calls to I/O in our tests. Suppose we want the player to enter his name when we start a new game:

func (player Player) GetName(io IO) {
     io.Write("What's your name?")
     player.Name = io.Read()
}

func TestGetPlayerName(t *testing.T) {
    var io FakeIO

    io.Input = "Javier"
    player.GetName(io)

    if name := player.Name; name != "Javier" {
        t.Errorf("Expected name to be %#v, but was %#v",
            "Javier", name)
    }
}

And it's just as easy to verify the game output, we'd just check the value of io.Output when testing functions that use io.Write(string).

2. Higher-order functions

In mathematics and computer science, a higher-order function (also functional form, functional or functor) is a function that does at least one of the following:

  • takes one or more functions as an input
  • outputs a function

Go is strongly typed, which can complicate the use of functions as a means of injecting dependencies. Continuing with our chess game, consider the problem of playing against the computer.

When it's the human player's turn, we have to pass the chess board and the IO interface to get the move input, which could be a mouse click in a GUI, or text entered through the command line, etc.:

player.Move(io, chessBoard)

The computer on the other hand, has no need for an IO interface to supply its move. It calculates what to do based on the current state of the chess board.

computer.Move(chessBoard)

An implementation of the game loop could look like this:

for !chessBoard.HasCheckMate() {
    player.Move(io, chessBoard)
    computer.Move(chessBoard)
}

As you may notice, the problem with the loop above is that we're not checking for the end game condition before the computer moves, so if the human wins, the computer will still attempt to move. This can be easily solved if we create an endless loop instead, and guard each player move with HasCheckMate()

for {
    if !chessBoard.HasCheckMate() {
        player.Move(io, chessBoard)
    }

    if !chessBoard.HasCheckMate() {
        computer.Move(chessBoard)
    }
}

But I'd rather avoid duplication (consider what happens if we want to add a third player, and a fourth, and so on).

It'd be nice to store the players in a data structure, and cycle through it to get the player for each turn. Then we'd be able to write something like this:

for !chessBoard.HasCheckMate() {
    getCurrentPlayer().Move(...)
}

Storing the references to the players wouldn't be enough, though, because of the different arity in their Move functions. If we really wanted the function above to work there are a few approaches we could try, but I won't be doing that in this blog post. Instead, I'm going to use Go's anonymous functions to store the Move functions, not the players.

var playerMove = func(chessBoard chess.Board) {
    player.Move(io, chessBoard)
}

var computerMove = func(chessBoard chess.Board {
    computer.Move(chessBoard)
}

var moves = []func(chessBoard chess.Board) {
    playerMove,
    computerMove.
}

The anonymous functions receive only the chess board, so arity is no longer an issue and we can store them in an array. Finally our game loop can look like this:

for !chessBoard.HasCheckMate() {
    moveCurrentPlayer(chessBoard)
    changeTurn()
}

And calling move on the current player is trivial:

func moveCurrentPlayer(chessBoard chess.Board) {
    moves[0](chessBoard)
}

To change the player turn, simply cycle through the contents of the moves array.

These are only some of the language features that we can use to achieve loosely coupled designs. In this example we used interfaces and higher-order functions to comply with the language constraints while keeping our code concise and easy to test.

On an ending note, I find Go features very refreshing. Out of the box it's simple and powerful. It provides just enough utility functions (through its src packages), so you don't have to reinvent the wheel. Go treats functions as first-class citizens, and that's my favorite feature of the language so far.