Header logo.
small hallucinations
homeyearstagsaboutrss

Recently

I went through my finances using beancount. Double-entry bookkeeping seemed boring at first. But with the help of VSCode extensions for beancount that enable syntax highlighting and formatting, it became somewhat enjoyable.

I ran into problems over different currencies. I have a few transactions in USD and EUR. I wrote a Python script to convert bank statement formats to that of beancount.


Did you know you could use calc in Tailwind CSS? I was first mind-blown then felt it wrong. One should not choose to use the dark magic that is calc() unless it is her last option. Besides, by using Tailwind, we are telling ourselves now we are one step removed from the nuts and bolts of CSS. Using arbitrary numbers in brackets already feels “unidiomatic” in Tailwind. Last week when I found myself resorting to something like h-[calc(100%-20rem)] I grimaced very hard.


I created huge mess while trying to resolve a merge conflict. I was using GitHub Desktop. It was ill-equipped to compare and switch between different branches on GitHub Desktop. (And I was ill-equipped to use the commandline.) Thankfully, the web interface is quite advanced by now. Usually, small conflicts that do not involve files being deleted or moved around could be resolved by a few clicks in the browser. This time, though, it did involve files being moved around. That was a huge mess.

I have tried Sourcetree on GitHub before and run into access problems. I couldn't pull or push the repo. An error message would tell me the repo did not exist. But when I pulled or pushed the repo using GitHub Desktop, I could check out the local repo and diff branches and commits.

The cause of the issue might be logging in using OAuth over HTTPS. This problem was solved when I 1) got a personal access token and 2) logged in using my username and this PAT as “password”, while choosing Basic for auth type and SSH for protocol.

Gothenburg

I moved to Gothenburg from Stockholm. And I like Gothenburg better.

Stockkholm feels like an archipelago of settlements connected by highways, subways and pendeltåg. In the middle of the city, we have touristy areas in Gamla Stan, commercial areas in Norrmalm and Södermalm. And really fancy areas in Östermalm. Beyond these quarters, the city feels more like non-contiguous dots of inhabited areas separated by woods. Some of these dots are bustling urban areas in their own right, such as Solna and other centers of communes in Greater Stockholm Area. But at the same time, a lot of them feel like villages.

Gothenburg as a city is held together more closely. The downtown area of Gothenburg is more walkable than that of Stockholm. I live in Hisingen and I love the lively messiness around here.

Yet it's not just the lively messiness. A few bus stops away from the down-to-earth supermarkets, shopping malls, and a Taiwanese-owned hotpot restaurant, lies a campus of Gothenburg University and Chalmers, a science park and a bunch of tech companies.

I don't know what to make of this contradiction yet. What I can say is this city is dope.

Trying out Fyne

The idea of building a piece of software once and run it everywhere is certainly charming. And Go supports cross-compiling out of the box.

Fyne is a very promising GUI package for Go that helps you build a GUI app and cross-compile it for (almost) all devices and OSes.

Heres is a quick example

For a quick example, this code gives you a small window with a label and an input field. Grab this piece of code, init a mod and tidy it, and you are good to go.

 1package main
 2
 3import (
 4	"fyne.io/fyne/v2/app"
 5	"fyne.io/fyne/v2/widget"
 6	"fyne.io/fyne/v2/container"
 7)
 8
 9func main() {
10	a := app.New() // {1}
11	w := a.NewWindow("Hello, you!") // {2}
12
13	l := widget.NewLabel("Hello! What is your name?") // {3}
14	e := widget.NewEntry()
15	e.SetPlaceHolder("Input your name...")
16
17	w.SetContent(container.NewVBox(l, e)) // {4}
18	w.ShowAndRun()
19}

First, on line {1} we define an app in a. Then we define a window w with title “Hello, you!” on line {2}.

Then we create a new label (line {3}) and a new input field (entry). These are all “widgets”. So their constructors are inside the widget package. We set a placeholder for the input field for good measure.

Next we create a vertical box (which is a container) and set it as the content of the window w on line {4}.

When we build and run this program, this is how it looks like:

You might have noticed the quirks. When you resize the window, you can see the content shivers. This is because Fyne tries to automatically adjust the window size and layout.

And when you try to display something that's not rendered by the default typeface, it becomes a block. CJK characters, Arabic letters and emojis are all mangled.

Using a custom font

In order to correctly display Unicode characters, you need to bundle a custom font and include it in your custom theme. Then this font is applied globally to your app as part of the theme. From what I read the issues section, as of now only TTF is supported.

I downloaded Source Han Serif in TTF format. And ran this command to bundle it as a resource.

1fyne bundle SourceHanSerifSC.ttf > bundled.go

Next thing we do is get a theme from one of Fyne's demo apps – “Notes”.

The only thing we need to do is change the name of the font in Font() method in theme.go:

1func (m *myTheme) Font(s fyne.TextStyle) fyne.Resource {
2	return resourceSourceHanSerifSCTtf
3}

See? Not bad.

Data-binding

I borrowed the theme from Fyne Notes and didn't change the color. That explains the yellow background color. New theme is applied by calling the SetTheme() method.

In this example, whenever the user types in or clears the text field, the greeting changes. This is done by setting up data-binding. (If the action to link two things is “binding”, then each of the two things are “bound” together. I guess I'll call them “bound variables” and “bound widgets”.)

First we need to declare bound string variables. Then we need to use these variables to create bound widgets. Note you use different methods to create bound widgets.

At this point, we need to link the two bound variables. There are methods that allow bi-directional conversion between a number and a string. Some conversion methods even support string formats. There's also a method to convert strings to and from a URI.

But it seems the only way to change greeting whenever userinput changes is adding a listener. To add a listener, you need to create a data listener from an anonymous function.

With this, whenever userinput changes, the callback function is executed. Notice how we get and set the value of a bound variable.

 1package main
 2
 3import (
 4	"fmt"
 5	"fyne.io/fyne/v2/app"
 6	"fyne.io/fyne/v2/widget"
 7	"fyne.io/fyne/v2/container"
 8	"fyne.io/fyne/v2/data/binding"
 9)
10
11func main() {
12	a := app.New()
13    // Setting the theme. `myTheme` is defined in `theme.go`
14	a.Settings().SetTheme(&myTheme{})
15	w := a.NewWindow("你好 Hello")
16	
17    // Declare two binding string variables
18	greeting := binding.NewString()
19    userinput := binding.NewString()
20
21    // Adding a listener
22	userinput.AddListener(
23		binding.NewDataListener(func() {
24			if val, ok := userinput.Get(); val == "" || ok != nil {
25				greeting.Set(
26                    "你叫什么名字?\nWhat is your name?"
27                    )
28			} else {
29				greeting.Set(
30                    fmt.Sprintf("你好,%s!\nHello, %s!", val, val)
31                    )
32			}
33		}))
34	
35    // Creating binding widgets
36	l := widget.NewLabelWithData(greeting)
37	e := widget.NewEntryWithData(userinput)
38
39	w.SetContent(container.NewVBox(l, e))
40	w.ShowAndRun()
41}

A few words

I tried two other pure-Go GUI packages. They are Gio and Nuxui. Both are ingenious projects. (There are quite a few other packages that help bind Go apps to more established GUI engines.)

Gio seems much more flexible than Fyne. And the way it uses contexts and channels seems more Go-ish. But typing Chinese in the input field simply doesn't work. The way an IME for CJK works is you type a sequence of letters, a popup menu shows up, from which you to choose a character or a word. Ideally, when an IME is active, keydown events should not be registered. The input field should only get data when a word has been composed in IME and is “committed” (selected by the user). But when you type in Gio's default input field, each letter in the sequence, as well the committed word, are all registered in the input field. This probably has to do with how Gio handles keyboard events.

Nuxui uses a backtick-wrapped string to declaratively define the UI. This is way less cumbersome than calling a bunch of nested constructors. I didn't spend too much time on it as breaking changes are introduced to some basic functions from v0.6 to 0.8. This resulted in some sample projects failing to compile.

I spent the most time on Fyne because Fyne is the best documented among the three. There are a few demo videos and even a book!

Fyne is designed with unit testing in mind. It provides helper functions to mock events. It supports comparing to snapshots to ensure correct rendering of UI.

Fyne is also well structured. Although so much so that it occasionally feels there's too much boilerplate.

But all in all, Fyne is great and very promising. I had a great time playing with it. I'm sure you will too.

Recently

Table-driven tests

Go has superb built-in support for good programming practices, such as test-driven development. In fact, I'm learning a lot about TDD while learning Go from this book: “Learn Go with Tests”.

If you want to test a bunch of similar inputs and outputs, it's handy to run table-driven tests. What you do is list input values and expected output in an array, then loop through all the test cases.

I found this blog post by Lorenzo Peppoloni quite helpful. It gives examples in both Golang and Python for table-driven tests.


Create your own keyboard layout optimized to reduce finger fatigue

We all push buttons for a living. Most of us spend enormous amount of time with the Qwerty keyboard layout.

I came across this fascinating video about making your own perfect keyboard layout.

By perfect, “adumb” refered to the shortest distance your fingers travel while typing on the keyboard. And the corpus he used to calculate the total distance is arXiv abstracts.

He first randomly generated keyboard layouts then crossed over the best ones. Until after 1000 generations of crossover, the total finger travel distance stopped decreasing.

If I were to make a keyboard layout, I would use a larger corpus that includes English, Swedish, Chinese as typed using Pinyin, and a bunch of programming languages.

On top of the larger number of symbols to rearrange, when you type CJK (and other complex, non-alphabetic) languages, you need to deal with the complicated relationship between the input method and the keyboard layout, especially on Linux.


zsh

For quite a while, I found I couldn't use commands I clearly had installed properly (macOS 12.2).

After some digging, I used an export command in .zshrc file and forgot to include $PATH: when setting the PATH value.

This answer on StackOverflow was helpful.

Using prepared statements & pointers in Golang

I changed the name of this blog to “a study of bugs”. This makes it easier for me to think of what to write about -- bugs, of course.

In MySQL, you can use a question mark (?) in a prepared statement to stand in for a value.

 1func GetUserId(name string) (int, error) {
 2	// (1) - BAD
 3	stmt, err := dbconn.Db.Prepare("SELECT id FROM user WHERE name=\"?\";")
 4	// (2) – GOOD
 5	stmt, err := dbconn.Db.Prepare("SELECT id FROM user WHERE name=?;") 
 6	if err != nil {
 7		log.Fatal(err)
 8	}
 9    defer stmt.Close()
10
11	var userId int
12	err = stmt.QueryRow(name).Scan(&userId) // (3)
13	if err != nil {
14		if err != sql.ErrNoRows {
15			log.Fatal(err)
16		}
17		return 0, err
18	}
19
20	return userId, nil
21}

In the Go code above, dbconn is a connection to a MySQL server. Line 1 defines a prepared statement. And Line 3 queries the table for a row where the name column matches the value of variable name. I assumed the ? in this query would be interpolated with an actual string. I added quotation marks since they are needed around strings in MySQL CLI.

This fails to return anything. Removing the quotation marks solved the problem. The correct code is on Line 2.

I also find Line 3 quite interesting. Here, first, stmt.QueryRow() returns a pointer to a sql.Row object. Its Scan() method then does two fascinating things: a) it sets a “dest” to the output value and b) returns an error if there's an error or nil if there's no error.

Thingy a is quite interesting. In this case, Scan() method will find the memory address of variable userId and write the output value (the user's ID) there.

It feels as if getting the value from a DB is a side effect.

The same goes when you decode a JSON string. In this example, on Line 4, Unmarshal() method unpacks the JSON string and writes it to &animals, the address of animals variable. And this is a side effect. The return value is again, either an error or nil — if there is an error, you simply can't ignore it.

 1var jsonBlob = []byte(`[
 2	{"Name": "Platypus", "Order": "Monotremata"},
 3	{"Name": "Quoll",    "Order": "Dasyuromorphia"}
 4]`)
 5type Animal struct {
 6	Name  string
 7	Order string
 8}
 9var animals []Animal
10err := json.Unmarshal(jsonBlob, &animals) // (4)

“Zulu timezone”

I've been using a pomodoro app that provides a REST API. Through this API, you can query past pomodoros using parameters such as ended_before and ended_after. The response data also contains started_at and ended_at fields.

The values for these fields all look like this: 2022-01-01T07:18:59.000Z. And they are UTC time strings suffixed with a Z. Correctly so, because it indicates this timestring is UTC.

There are fields named local_started_at and local_ended_at. Although those values are clearly not UTC, they are all suffixed with a Z. This confused me a bit at first. (And it seems quite common for people to suffix Z at the end of a time string, regardless of which timezone that time string actually is.)

The default timezone in a Docker container is UTC. The default timezone of AWS is also UTC. (I wrote a script to check just to be sure. But it's actually written somewhere in the documentation.)

Interestingly, Python does not support the letter Z when you try to parse a timestring using datetime.fromisoformat. But if you use +00:00 to mark a zero offset from UTC, it works and a datetime object with timezone info is returned.

 1>>> datetime.fromisoformat('1989-06-04T08:11:25.000Z') # ❌
 2Traceback (most recent call last):
 3  File "<stdin>", line 1, in <module>
 4ValueError: Invalid isoformat string: '1989-06-04T08:11:25.000Z'
 5
 6>>> datetime.fromisoformat('1989-06-04T08:11:25.000+00:00') # ✅
 7datetime.datetime(1989, 6, 4, 8, 11, 25, tzinfo=datetime.timezone.utc)
 8
 9>>> datetime.fromisoformat('1989-06-04T08:11:25.000+02:00') # ✅
10datetime.datetime(1989, 6, 4, 8, 11, 25,
11    tzinfo=datetime.timezone(datetime.timedelta(seconds=7200)))

Now I digress.

Z apparently stands for Zulu in “military timezones”. 25 letters are used to indicate the time difference from UTC. Although the Z timezone means UTC, the timezone where the actual Zulu people live has a offset of +02:00.

There are also India, Lima and Quebec in these military timezone names. Only Quebec indicates the actual timezone used in Quebec as daylight saving time.

A simple demo of how useCallback works

This is how useCallback works.

In the callback defined on line 🙄, the function body increases the count by 1. We set the dependency list to be shallChange. This way, the count will not increase unless shallChange gets a different value.

If you click Count+ multiple times, the count will only increase once. It will increase again after you click Shall Change? butotn.

 1import { useCallback, useState } from 'react';
 2
 3function App() {
 4  const [count, setCount] = useState(0);
 5  const [shallChange, setShallChange] = useState(0);
 6
 7  const setCountCallback = useCallback( // 🙄
 8    ()=> {
 9      setCount(count+1)
10    }, [shallChange]
11  )
12  
13  const onPlusBtnClick = () => {
14    setCountCallback();
15  }
16
17  const onShallChangeClick = () => {
18    setShallChange(shallChange+1)
19  }
20  
21  return (
22    <div>
23        Count: {count}<br />
24        shallChange: {shallChange}<br />
25        <button onClick={onPlusBtnClick}>Count+</button><br />
26        <button onClick={onShallChangeClick}>Shall change?</button>
27    </div>
28  );
29}
30
31export default App;