Following along with https://quii.gitbook.io/learn-go-with-tests/
- https://fasterthanli.me/articles/i-want-off-mr-golangs-wild-ride
- Interesting how this differs from Rich Hickey’s Simple made Easy
- Interesting how Go appears to expose all the under-the-hood stuff and leaves it up to the dev to sort it out
- Had to turn on yasnippet minor mode to have golang completions work
- I have my editor configured to display tabs as two spaces to make golang more accessible for me
- I have opted to edit files directly rather than tangling so that I can get the benefit of LSP integration in golang
- [ ] There may be a good way to make it work inside Org-mode but I haven’t investigated
- Pretty cool to be able to see compiler errors in the editor without needing to run the tests 😎
- I have had to run
M-x projectile-invalidate-cache
occasionally to get Projectile to pick up the.dir-locals
config for jumping between tests and impls.
- [ ] Can I get
go test
to report using dots?
To see a module’s godocs:
godoc -http :8000
- Not through
go test
directly :( - I can do some shenanigans with
fswatch
(an OS X tool, similar tools exist for other operating systems) - Install with
brew install fswatch
fswatch -o ./*.go | xargs -n1 -I{} go test
See https://emcrisostomo.github.io/fswatch/usage.html
This is interesting:
t.Helper()
is needed to tell the test suite that this method is a helper. By doing this when it fails the line number reported will be in our function call rather than inside our test helper. This will help other developers track down problems easier.
This is a feature of Playwright that I prefer as well.
Doesn’t look like Go has syntactic sugar for default values for function parameters :( https://stackoverflow.com/questions/19612449/default-value-in-gos-method#comment77755758_23650312
Finally breaking code out of this toy function that’s already too large and concerned with too much. The book illustrates that there are implicit returns in golang
func greetingPrefix(language string) (prefix string) {
switch language {
case french:
prefix = frenchHelloPrefix
case spanish:
prefix = spanishHelloPrefix
default:
prefix = englishHelloPrefix
}
return
}
which will return the prefix
because it’s specified as a named return value to which I say, “Ewww!” This is a language feature that I would discourage using because it makes it harder to reason about the code inplace. You need to be aware of the call signature in order to interpret the return
statement rather than simply interpreting the return
statement by itself.
See https://github.com/golang/go/wiki/CodeReviewComments#named-result-parameters for more details.
When you have more than one argument of the same type (in our case two integers) rather than having
(x int, y int)
you can shorten it to(x, y int)
.
I found this unclear. I thought it was saying x
is any type rather than int
.
I’d rather use reduce to construct a string like this. The standard strings
library https://cs.opensource.google/go/go/+/refs/tags/go1.19.5:src/strings/strings.go;l=528 uses a chunked approach to writing into a Builder
. Doesn’t look like Go has functional constructs like reduce
. Maybe there’s a more idiomatically functional way to do this rather than imperatively iterating through a loop and concatenating strings.
Ah make
seems sort of like what I want. make
and range
feel a little bit like initializing a collection and then mapping over a collection to transform it into another collection.
- [ ] If a function consumes a slice, why can’t an array of defined size be passed to it?
- It seems like something that a seq-like abstraction could handle
go test -cover
In order to avoid the kind of error-prone boundary checking, I almost always find it more advantageous to express this sort of computation as a reduce operation over the inputs.
Go doesn’t support function dispatch based on the argument type, something like multimethods in Clojure.
According to the book:
We have two choices:
- You can have functions with the same name declared in different packages. So we could create our Area(Circle) in a new package, but that feels overkill here.
- We can define methods on our newly defined types instead.
The first choice seems like wasteful overhead. Let’s see what’s up with methods. Oh you just bind them on the struct. That’s not that complicated, but it forces tying abstract implementations to concrete types :/. It seems like this would not allow for the flexibility that you can achieve with Clojure protocols. Interfaces get some of the way there, just like they do in Clojure, but cannot be extended by consumers without opening up the initial implementation.
The book’s author “would like to reiterate how great the compiler is here”. Go’s compiler messages have been largely helpful. It’s not as fancy as Elm, but it seems pretty good. It’s as good or better than Typescript’s compiler, but not as good as Haskell and other fancy compilers.
The syntax for declaring methods binds the method to a receiver
.
The public/private distinction applies to packages. So a capitalized field is publicly available in an external package, a lower-case field is not. I had previously thought that the public/private distinct applied to each entity itself.
Following along at https://golangbyexample.com/go-mod-sum-module/
go.mod
specifies the root of a Go module.- It defines the module import path
- The version of go with which the module was created
- Dependency requirements for the module (including specific versions)
go.sum
lists the checksums of direct and indirect dependencies
Also please note that both go.mod as well as go.sum file should be checked into the Version Control System (VCS) such as git
Note: when following the example, I needed to add ./learn
to the root go.work
to make the imported dependency resolve properly. Before I did this, I repeatedly got an error saying that no required module provides the package, even after following the go get
instructions to install the package.
- [ ] It’s not clear to me why I would need to install a package’s indirect dependencies. If they’re not necessary to run the code that I’m imported, why would I need them. If they are necessary, then why aren’t they specified among the package’s direct dependencies?
Following along at https://quii.gitbook.io/learn-go-with-tests/go-fundamentals/pointers-and-errors#write-the-test-first
Variables copy on calling in golang (maybe this isn’t the right way to describe it, thinking of the copy on write sense). In order to update state in a struct, you need to pass a pointer to the struct in memory so that updates mutate the original struct rather than a copy. To achieve this, we use the *Foo
receiver type func (f *Foo)
. Struct pointers are automatically dereferenced which leads to some convenience at the cost of some confusion (explicit vs. implicit dereference).
Stringer
is how golang refers to the string representation of an struct.
https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully
Following along at https://quii.gitbook.io/learn-go-with-tests/go-fundamentals/maps
The map
keyword defines a mapping of map[keyType]valueType
which is definitely a syntax that a parser may be able to parse.
Maps in golang return a second value so you can tell whether a value was present in the dictionary. The book says “This property allows us to differentiate between a word that doesn’t exist and a word that just doesn’t have a definition.” It’s not clear to me when a word doesn’t exist and how this helps differentiate. Maybe they’re referring to a “word that doesn’t have a definition” as an entry in the map that has a value of empty string.
Remember what we just learned about pointers and all that. Forget it. Maps are different. The whole map doesn’t copy when passed as an argument, just the pointers. Under the hood their values are just pointers to underlying structures.
Confusing? Sure. More detail than I asked for when I went to look up my value in an aggregate? Yep. Simple enough to learn and develop heuristics around. I guess.
A gotcha with maps is that they can be a nil value. A nil map behaves like an empty map when reading, but attempts to write to a nil map will cause a runtime panic. You can read more about maps here.
Therefore, you should never initialize an empty map variable:
// don't do
var m map[string]string
// instead do
var m = map[string]string{}
// or
var m = make(map[string]string)
Both approaches create an empty
hash map
and pointm
at it. Which ensures that you will never get a runtime panic.
Leaving off at https://quii.gitbook.io/learn-go-with-tests/go-fundamentals/maps#refactor-2
https://calvinfeng.gitbook.io/gonotebook/idioms/custom-json-marshaling from https://sagan.slack.com/archives/C02SUTFE3/p1684425228308799
Following along at https://freshman.tech/linting-golang/
golangci-lint run
In order to make this work, I needed to define a module in the root of my project. I think this is related to golangci/golangci-lint#2654 .
go mod init
go mod tidy
echo "package main" > main.go
go work use .
However it doesn’t appear to recursively check the submodules. I needed to pass a specific directory each time I ran the linter.
I needed to install the https://github.com/weijiangan/flycheck-golangci-lint package locally as I couldn’t find it on the normal package repos I use. I had to use https://www.flycheck.org/en/latest/user/syntax-checkers.html#configuring-checker-chains to set up the golangci-lint
as the next checker following lsp
.
Cuddling refers to not leaving extra space between statements in code. See https://github.com/bombsimon/wsl/blob/master/doc/rules.md .
https://arslan.io/2019/06/13/using-go-analysis-to-write-a-custom-linter/