Devlog: An Interpreter in Go

I know nothing about interpreters, I don't know any Go. A log of me trying to remedy that.


I want to learn about interpreters/compilers. The book Crafting Interpreters has a free online version and is widely lauded. I know if I just read the book and follow his implementation in Java, I won't fully internalize and understand. Why not take this chance to learn another language.

Pros:

Another emphasis: Tests I'm not a fan of tests. Don't enjoy writing them, don't enjoy reading them. Currently unconvinced that more tests: good. Of course, I understand the rationale behind tests, but thus far, I haven't seen a codebase that has good tests, that fully achieves what's been promised.

Therefore, I want to force myself to go about this in a TDD-approach, and remain open-minded to the benefit of tests.

My current game plan:

  1. Go through this doc: https://quii.gitbook.io/learn-go-with-tests
    • Test focused, looks better than the official docs imo
  2. Go chapter by chapter through Crafting Interpreters
    1. Read each chapter fully
    2. Internalize
    3. Come out with a suite of tests that make sense
    4. Get coding

[17/9/24] Day 1: Learn Go with Tests

My objective for the day:

  1. Get Go running locally
  2. Learn the very basics of the syntax
  3. Get some ✅ green tests

My resource for learning Go: https://quii.gitbook.io/learn-go-with-tests

package main
 
import "fmt"
 
func Hello() string {
	return "Hello, world"
}
 
func main() {
	fmt.Println(Hello())
}
package main
 
import "testing"
 
 
func TestHello(t *testing.T) {
	got := Hello()
	want := "Hello, world"
 
	if got != want {
		t.Errorf("got %q want %q", got, want)
	}
}

Ok this was easy enough, I installed via Homebrew, and things just kind of worked first try. Things that stood out to me:

  1. := vs =
    • something new to me. short variable declaration, infers type, can only be used inside functions, cannot be reassigned.
  2. string formatting. t.Errorf("got %q want %q", got, want) , a lot of options here: https://gobyexample.com/string-formatting
  3. the test was easy. no need to install any packages or choose a testing framework or learn test specific syntax

TIL: Go doesn't support optional function parameters. I wanted to make it so that Hello() could take in a name, and return a default "world" if name wasn't provided. Turns out optional/default function arguments are not supported 🤯

Initial thoughts: this is weird. It's definitely a feature that I use extensively + assumed was generally supported. The reason for it makes sense though: https://arc.net/l/quote/rnkufgdf, will have to use Go a lot more to make up my mind.

One feature missing from Go is that it does not support default function arguments. This was a deliberate simplification. Experience tells us that defaulted arguments make it too easy to patch over API design flaws by adding more arguments, resulting in too many arguments with interactions that are difficult to disentangle or even understand. The lack of default arguments requires more functions or methods to be defined, as one function cannot hold the entire interface, but that leads to a clearer API that is easier to understand. Those functions all need separate names, too, which makes it clear which combinations exist, as well as encouraging more thought about naming, a critical aspect of clarity and readability.

  • Rob Pike

Named return values

func greetingPrefix(language string) (prefix string) {
	switch language {
	case french:
		prefix = frenchHelloPrefix
	case spanish:
		prefix = spanishHelloPrefix
	default:
		prefix = englishHelloPrefix
	}
	return
}

^ this return actually returns the appropriate prefix. interesting.

I also made it a point to disable AI/Copilot. I think AI is great productivity-wise but definitely bad for learning a new language as reading ≠ writing. Very refreshing indeed, I really enjoyed today.


[22/9/24]

Just a short day learning the syntax in Go. I also started using Zed as my editor for this, and am forcing myself to use the Vim keybindings even more. Pretty fun.

Iteration and Arrays

Define an array like this: numbers := [5]int{1, 2, 3, 4, 5} If you don't wanna explicitly specify a length: numbers := [...]int{1, 2, 3, 4, 5} If you want the collection to be any size, use a slice: numbers := []int{1, 2, 3, 4, 5}

To loop through an array:

numbers := [...]int{1,2,3}
 
for index, number := range numbers {
	// do stuff here
}

To compare arrays/slices, use reflect.DeepEqual. (this is, however, not type-safe)

if !reflect.DeepEqual(got, want) {
		t.Errorf("got %v want %v", got, want)
	}

Structs, Methods and Interfaces A struct is pretty straightforward, just a named collection of fields.

type Person struct {
	Name string
	Age int
}

Can just define a new person using this syntax:

gavin := Person{"gavin", 23}

Methods

type Rectangle struct {
	Width  float64
	Height float64
}
 
func (r Rectangle) Area() float64 {
	return 0
}
 
type Circle struct {
	Radius float64
}
 
func (c Circle) Area() float64 {
	return 0
}

Interfaces

type Speaker interface {
    Speak() string
}

Basically describes behaviour, not data, unlike structs. Defines a set of methods that a type must implement. Used for abstraction, allows polymorphism.