kota's memex

teaching

https://matklad.github.io/2022/10/19/why-linux-troubleshooting-advice-sucks.html

programming language theory

https://craftinginterpreters.com/

A great book on writing interpreters.

style

Parse, don't validate

https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/

A static type system can be used to prove if a given function is possible to implement. For example, a function which takes a list of items and returns the first item from the list:

head :: [a] -> a

This is actually problematic because we cannot (with this function signature) handle the case where the list is empty.

There are two different approaches to handling this situation. The most immediately obvious is to change the return type to some sort of Maybe type:

head :: [a] -> Maybe a

But this solution is not ideal. Often, in the code calling your function you will have already checked that the list is empty, but now you will need to check it twice. This introduces more room for errors and means a small, but non-zero performance toll.

When possible it is better to strengthen the argument type rather than weakening the return type:

head :: NonEmpty [a] -> a

In haskell and other strongly typed languages we can use a trait which proves that a list is non-empty. The code now has no redundant checks. We've parsed and then passed along our proof rather than validating at every usage.

Making illegal states unrepresentable

https://corrode.dev/blog/illegal-state/

Related to the "Parse, don't validate" advice is the concept of using your type system to make illegal states impossible to represent. One of the most common examples of this is having a bucket full of booleans:

let is_admin: bool = true;
let is_guest: bool = true;

Assuming your system isn't actually meant to have users who are both guests and admins you would likely be better served with a enum:

enum UserRole {
    User,
    Admin,
    Guest,
}

Composibility

Lets compare a few implementations of an INI parser. The best one has two functions; parse(string) INI and INI.stringify() string to convert ini data to and from a structured format. This is quite an elegant system which can be reused in many different instances.

Another way of creating an INI parser would be to have a parse(filename) INI function and a INI.write(filename) to save it back to a file. There's a few issues with this. First of all it makes the library useless for reading INI files which come from other sources, such as over a network, or even using a file reader that buffers the input for example. Second, this system often mixes filesystem reading error and parsing errors... if it even handles errors at all. Ultimately, it's adding complexity to something that would better be addressed by composing the library with some file reading function.

The third way would be where you must create an INI object, then load the file/string into your object, and finally use specialized methods to get at the results. This type of thing is common in the object-oriented tradition, and it's terrible. Instead of making a single function call and moving on, you now have to perform the ritual of moving your object through various states. And because data is now wrapped in a specialized object type, all code that interacts with it has to know it's type, creating unnecessary interdependencies. When a standard data type suffices prefer that over creating a custom data structure.

project documentation

Readmes, Changelogs, etc