Frontier Software

Pointers

Ampersands and Asterisks

Go uses the same notation as C for pointers where the memory address of a variable x is &x and if we store this memory address in p then whatever is stored in that memory address is retrieved as *p.

The unary operator & gives the address of an object

/*
Simple use of the & unary operator
*/
package main

import "fmt"

func main() {
	s := "I know your address"
	fmt.Printf("%v\n", &s)
}

Running that gives me 0xc000014080. This memory address is a value that can be stored in a variable to get used (or abused) in various ways. A variable containing a memory address is called a pointer.

The unary operator * is the indirection or dereferencing operator; when applied to a pointer, it accesses the object the pointer points to.

/*
Simple use of the * unary operator
*/
package main

import "fmt"

func main() {
	s := "I know your address"
	p := &s
	fmt.Printf("%v\n", *p)
}

Running the contents of *p, ie the text “I know your address”.

A common use (or abuse) of pointers is to allow variables to get mutated from anywhere:

/*
How to change the value of a variable from anywhere.
*/
package main

import "fmt"

var s = "I know your address"
var p = &s

func main() {
	fmt.Printf("%v\n", *p)
	mutate()
	fmt.Printf("%v\n", *p)
}

func mutate() {
	*p = "I've replaced whatever was in your address"
}

“We can solve any problem by introducing an extra level of indirection.” — Fundamental theorem of software engineering

“It is always possible to add another level of indirection.” — RFC 1925

As with most programming languages, abstract data types in Go involve structs linked by pointers.

Different programming languages may have strikingly different data models. For example, unlike C, the language Lisp supports trees directly, and the language Prolog has logic built into its data model. — Foundations of Computer Science by Al Aho and Jeff Ullman

Abstract data types (or data models in Aho’s and Ullman’s parlance) can broadly be divided into:

Four abstract operators, abreviated to CRUD, can be applied to any of the above:

  1. Create
  2. Read (or access)
  3. Update (or modify)
  4. Delete (or destroy)

Trees

Chapter 4 of gopl has an example of using a binary tree to sort a list of integers.

package treesort

type tree struct {
	value       int
	left, right *tree
}

// Sort sorts values in place.
func Sort(values []int) {
	var root *tree
	for _, v := range values {
		root = add(root, v)
	}
	appendValues(values[:0], root)
}

// appendValues appends the elements of t to values in order
// and returns the resulting slice.
func appendValues(values []int, t *tree) []int {
	if t != nil {
		values = appendValues(values, t.left)
		values = append(values, t.value)
		values = appendValues(values, t.right)
	}
	return values
}

func add(t *tree, value int) *tree {
	if t == nil {
		return &tree{value: value}
	}
	if value < t.value {
		t.left = add(t.left, value)
	} else {
		t.right = add(t.right, value)
	}
	return t
}

The accompanying sort_test.go:

package treesort_test

import (
	"math/rand"
	"sort"
	"testing"

	"gopl.io/ch4/treesort"
)

func TestSort(t *testing.T) {
	data := make([]int, 50)
	for i := range data {
		data[i] = rand.Int() % 50
	}
	treesort.Sort(data)
	if !sort.IntsAreSorted(data) {
		t.Errorf("not sorted: %v", data)
	}
}

Trees fall under what How To Design Programs calls self-referential data, needed whenever we are dealing with arbitrary large data.

Its example of a binary tree in Racket looks like:

(define-struct node [value left right])
; A BT (short for BinaryTree) is one of:
; – NONE
; – (make-node Number BT BT)

Lets start with the line in the add function that creates a new node:

return &tree{value: value}

which is shorthand for

t = new(tree)
t.value = value
return t

or

t = new(tree)
*t = tree{value: value}
return t

A reason I find programming in Lisp or Prolog easier than C and in turn Go is I find pointers confusing and a recipe for disaster, so try to work with literal data, specifically JSON, whenever possible.

These notes I wrote while creating my first simple Go program.

Why?

The Bash scripts I wrote to scrape events for data for my listing sites joeblog.co.za and seatavern.co.za hit the snag that the way Hugo’s urlize function which converts strings listed in taxonomies to lowercased, hyphenated directory names had weird rules for accented foreign alphabet characters and punctuation.

My initial attempt was to first run

go get github.com/gohugoio/hugo/helpers

which installed a go.sum file listing 218 dependencies to just get one little function from the Hugo package.

My next attempt was to look at the code and track down underlying standard packages.

https://github.com/gohugoio/hugo/blob/v0.125.7/helpers/url_test.go

p := newTestPathSpec()
output := p.URLize(test.input)

ps, err := helpers.NewPathSpec(hugofs.NewFrom(afero.NewMemMapFs(), conf.BaseConfig()), config.New(), loggers.NewDefault())

The code:

// URLize is similar to MakePath, but with Unicode handling
// Example:
//
//	uri: Vim (text editor)
//	urlize: vim-text-editor
func (p *PathSpec) URLize(uri string) string {
	return p.URLEscape(p.MakePathSanitized(uri))
}

PathSpec

// PathSpec holds methods that decides how paths in URLs and files in Hugo should look like.
type PathSpec struct {
	*paths.Paths
	*filesystems.BaseFs

	ProcessingStats *ProcessingStats

	// The file systems to use
	Fs *hugofs.Fs

	// The config provider to use
	Cfg config.AllProvider
}