Dysfunctional options pattern in Go

· 7 min

Ever since Rob Pike published the text on the functional options pattern, there’s been no shortage of blogs, talks, or comments on how it improves or obfuscates configuration ergonomics.

While the necessity of such a pattern is quite evident in a language that lacks default arguments in functions, more often than not, it needlessly complicates things. The situation gets worse if you have to maintain a public API where multiple configurations are controlled in this manner.

Einstellung effect

· 4 min

In 9th grade, when I first learned about Lenz’s law in Physics class, I was fascinated by its implications. It states:

The direction of an induced current will always oppose the motion causing it.

In simpler terms, imagine you have a hoop and a magnet. If you move the magnet close to the hoop, the hoop generates a magnetic field that pushes the magnet away. Conversely, if you pull the magnet away, the hoop creates a magnetic field that pulls it back. This occurs because the hoop aims to prevent any change in the surrounding magnetic field. That’s Lenz’s Law: the hoop consistently acts to maintain the magnetic field’s status quo, reacting against the motion that’s the cause of the existence of the magnetic flux in the first place. Generators leverage this principle to convert mechanical motion into electrical energy.

Strategy pattern in Go

· 6 min

These days, I don’t build hierarchical types through inheritance even when writing languages that support it. Type composition has replaced almost all of my use cases where I would’ve reached for inheritance before.

I’ve written about how to escape the template pattern hellscape and replace that with strategy pattern in Python before. While by default, Go saves you from shooting yourself in the foot by disallowing inheritance, it wasn’t obvious to me how I could apply the strategy pattern to make things more composable and testable.

Anemic stack traces in Go

· 7 min

While I like Go’s approach of treating errors as values as much as the next person, it inevitably leads to a situation where there isn’t a one-size-fits-all strategy for error handling like in Python or JavaScript.

The usual way of dealing with errors entails returning error values from the bottom of the call chain and then handling them at the top. But it’s not universal since there are cases where you might want to handle errors as early as possible and fail catastrophically. Yet, it’s common enough that we can use it as the base of our conversation.

Retry function in Go

· 5 min

I used to reach for reflection whenever I needed a Retry function in Go. It’s fun to write, but gets messy quite quickly.

Here’s a rudimentary Retry function that does the following:

  • It takes in another function that accepts arbitrary arguments.
  • Then tries to execute the wrapped function.
  • If the wrapped function returns an error after execution, Retry attempts to run the underlying function n times with some backoff.

The following implementation leverages the reflect module to achieve the above goals. We’re intentionally avoiding complex retry logic for brevity:

Type assertion vs type switches in Go

· 6 min

Despite moonlighting as a gopher for a while, the syntax for type assertion and type switches still trips me up every time I need to go for one of them.

So, to avoid digging through the docs or crafting stodgy LLM prompts multiple times, I decided to jot this down in a Go by Example style for the next run.

Type assertion

Type assertion in Go allows you to access an interface variable’s underlying concrete type. After a successful assertion, the variable of interface type is converted to the concrete type to which it’s asserted.

Patching pydantic settings in pytest

· 5 min

I’ve been a happy user of pydantic settings to manage all my app configurations since the 1.0 era. When pydantic 2.0 was released, the settings portion became a separate package called pydantic_settings.

It does two things that I love: it automatically reads the environment variables from the .env file and allows you to declaratively convert the string values to their desired types like integers, booleans, etc.

Plus, it lets you override the variables defined in .env by exporting them in your shell.

Omitting dev dependencies in Go binaries

· 3 min

As of now, unlike Python or NodeJS, Go doesn’t allow you to specify your development dependencies separately from those of the application. However, I like to specify the dev dependencies explicitly for better reproducibility.

While working on link-patrol, a CLI for checking dead URLs in Markdown files, I came across this neat convention: you can specify dev dependencies in a tools.go file and then exclude them while building the binary using a build tag.

Eschewing black box API calls

· 6 min

I love dynamically typed languages as much as the next person. They let us make ergonomic API calls like this:

import httpx

# Sync call for simplicity
r = httpx.get("https://dummyjson.com/products/1").json()
print(r["id"], r["title"], r["description"])

or this:

fetch("https://dummyjson.com/products/1")
  .then((res) => res.json())
  .then((json) => console.log(json.id, json.type, json.description));

In both cases, running the snippets will return:

1 'iPhone 9' 'An apple mobile which is nothing like apple'

Unless you’ve worked with a statically typed language that enforces more constraints, it’s hard to appreciate how incredibly convenient it is to be able to call and use an API endpoint without having to deal with types or knowing anything about its payload structure. You can treat the API response as a black box and deal with everything in runtime.

Annotating args and kwargs in Python

· 3 min

While I tend to avoid *args and **kwargs in my function signatures, it’s not always possible to do so without hurting API ergonomics. Especially when you need to write functions that call other helper functions with the same signature.

Typing *args and **kwargs has always been a pain since you couldn’t annotate them precisely before. For example, if all the positional and keyword arguments of a function had the same type, you could do this: