A place where I capture raw, quick notes worth remembering.

July 9, 2024

lisp

Rich Comment Blocks in Common Lisp

In Clojure, “Rich Comment Blocks” serve as both a playground and a log during development. Named after Clojure’s creator, Rich Hickey, these blocks offer a unique approach to REPL-driven development.

Stuart Halloway, in his talk “Running with Scissors,” quipped:

These comments are rich because they provide rich detail about the development process and because they were written by a person named Rich.

Rich Comment Blocks look like this:

(comment
  (println "foo"))

When the program runs, it ignores the top-level form. However, during development, you can evaluate each line individually, making it an invaluable tool for experimentation and debugging.

As a Common Lisp user, I initially missed this feature. While Common Lisp does have the #+nil reader macro that can be used for similar purposes, it’s not quite as flexible as Clojure’s comment form. The #+nil macro looks like this:

#+nil
(print "This won't be evaluated when the file is loaded")

However, I wanted something closer to Clojure’s Rich Comment Blocks, so I created my own comment macro in Common Lisp:

(defmacro comment (&body body)
  (declare (ignore body)))

This simple macro brings the power of Rich Comment Blocks to Common Lisp, offering more flexibility than the #+nil reader macro. It allows you to wrap multiple expressions and even nested forms, which can be selectively evaluated during development.

Give it a try - you might find it as useful as I do.

Permalink

Guile Hoot on the Mac

Recently, I was exploring the Spritely Institute’s blog and discovered their project, Guile Hoot. This Scheme to WebAssembly compiler enables running Scheme code directly in web browsers, opening up new possibilities of using Scheme.

While the project’s documentation is tailored for Guix users (a natural choice for Guile enthusiasts), I’m stuck on MacOS and needed to adapt the setup process using Homebrew. To save you time and frustration, this is how I got it running:

First, install the latest version of Guile using Homebrew:

# This may take some time
brew install guile --HEAD

Next, set up the necessary environment variables. For Fish shell users like myself:

set -x GUILE_LOAD_PATH $brew_prefix/share/guile/site/3.0
set -x GUILE_LOAD_COMPILED_PATH $brew_prefix/lib/guile/3.0/site-ccache
set -x GUILE_SYSTEM_EXTENSIONS_PATH $brew_prefix/lib/guile/3.0/extensions

For Bash or ZSH users, use the following (replace brew_prefix with the actual path or set it as a variable):

export GUILE_LOAD_PATH=$brew_prefix/share/guile/site/3.0
export GUILE_LOAD_COMPILED_PATH=$brew_prefix/lib/guile/3.0/site-ccache
export GUILE_SYSTEM_EXTENSIONS_PATH=$brew_prefix/lib/guile/3.0/extensions

Now, let’s clone the Guile Hoot repository, configure, and compile it. Ensure that the prefix directory matches your brew_prefix:

git clone https://gitlab.com/spritely/guile-hoot.git
cd guile-hoot
./bootstrap.sh
./configure --prefix=/opt/homebrew
make
# No sudo needed when using Homebrew
make install

To verify your installation, launch the Guile REPL and test if the following command loads without errors:

scheme@(guile-user)> ,use (hoot compile)

If you don’t encounter any errors, congratulations! You’ve successfully set up Guile Hoot on your Mac. Now, let’s see if I can create a game in the browser with Scheme and WASM!

Permalink

July 1, 2024

emacs macos

Install Emacs on the Mac - Part Two

Previously, I used to install Emacs on the Mac through a build script, but I recently switched to the Emacs Plus version, installed through homebrew.

First, you need to “tap” it and install dependencies before you can use it:

brew tap d12frosted/emacs-plus

# Install dependencies
brew install jq

And then you can install it. I pin it to a version and use minimal flags:

brew install emacs-plus@30 --with-memeplex-slim-icon --with-native-comp

And there you go, an Emacs version specifically tuned for the Mac.

Permalink

June 30, 2024

emacs macos

Enhancing Font Rendering in Emacs on macOS

Recently, I stumbled upon a Reddit thread on font rendering in Emacs on macOS. I figured many users might be unaware of simple trick for crisper fonts:

defaults write org.gnu.Emacs AppleFontSmoothing -int 0

After running this command, restart Emacs to see the effect.

If you’re not satisfied with the result, you can easily revert to the default settings by running:

defaults delete org.gnu.Emacs AppleFontSmoothing

Remember to restart Emacs after making changes again.

Permalink

June 29, 2024

linux emacs

Sync Fastmail with isync

As an Emacs enthusiast, I’ve found Mu4e to be an excellent email client. While Emacs can send emails, it can’t fetch and sync them efficiently. Enter isync, the perfect companion to fill this gap.

Setting up this duo took some digging, so I’m sharing my configuration to save you time and headaches.

Here’s what my setup accomplishes:

  1. Syncs all mailboxes, excluding junk and notes.
  2. Renames “Sent Items” to “Sent” for simplicity.
  3. Encrypts your password on disk using GPG.

First, I recommend to install the latest version of isync, if you are on the Mac, use brew install isync --HEAD. Then, you’ll need to create an encrypted password file. In Emacs, run the epa-encrypt-file command.

Now, let’s dive into the heart of the setup. Here’s my .mbsyncrc configuration file:

# Petar account on Fastmail.
IMAPAccount petar
Host imap.fastmail.com
Port 993
AuthMechs LOGIN
User [email protected]
PassCmd "gpg -q --for-your-eyes-only --no-tty -d ~/.mbsync-password-petar.gpg"
TLSType IMAPS
TLSVersions +1.2

IMAPStore petar-remote
Account petar

# Petar Local.
MaildirStore petar-local
Path ~/Mail/Petar/
Inbox ~/Mail/Petar/Inbox
Trash ~/Mail/Petar/Trash/
SubFolders Verbatim

# Sync everything besides sent and junk.
Channel sync-petar-all
Far :petar-remote:
Near :petar-local:
Patterns * !"Sent Items" !"Junk Mail" !"Notes"
Expunge None
CopyArrivalDate yes
Sync All
Create Near
SyncState *

# Sync and rename the sent items
Channel sync-petar-sent
Far :petar-remote:"Sent Items"
Near :petar-local:Sent
Expunge None
CopyArrivalDate yes
Sync All
Create Near
SyncState *

Group petar
Channel sync-petar-all
Channel sync-petar-sent

With this setup, you’ll have all your email on disk, ready to use it in Emacs and Mu4e.

Permalink

June 28, 2024

emacs lisp

From Python to Emacs Lisp

Ever feel like your brilliant blog ideas evaporate faster than spilled coffee on a hot keyboard?

If you’re like me, the lower the barrier to creating a note for your blog, the better. I started with a Python script to streamline my blog writing process, but it wasn’t quite hitting the mark. It required me to open the terminal, run the command, and then open the file in Emacs. The experience felt clunky.

Then it hit me: I’m using Emacs, why the heck am I doing this in Python.

I decided to port my Python script to Emacs Lisp, and not only was it easier to write, but the overall experience also improved significantly. It prompts you with a few questions and opens a buffer for you to start writing.

For reference, here’s the code that I ended up writing.

(defun pet/write-blog ()
  "Create a new blog note or post."
  (interactive)
  (let* ((is-note (y-or-n-p "Do you want to write a note? "))
         (title (read-string "What is the title? "))
         (tags (read-string "Do you want to tag it? "))
         (filename (concat (pet--slugify title) ".md"))
         (dir-type (if is-note "note" "post"))
         (path (expand-file-name
                filename
                (expand-file-name
                 (concat "content/" dir-type "s") pet/blog-directory)))
         (date (format-time-string "%Y-%m-%d"))
         (replacements
          `(("${title}" . ,title)
            ("${date}" . ,date)
            ("${type}" . ,dir-type)
            ("${taxonomies}" . ,(format (if is-note "tags: [%s]" "categories: [%s]") tags))
            ("${tags}" . ,tags))))
    (find-file path)
    (insert pet/blog-frontmatter) ;; insert template for frontmatter
    (goto-char (point-min)) ;; beginning of buffer, so we can replace the title
    (pet--replace-strings replacements)
    (goto-char (point-max))))

(defun pet--slugify (s)
  "Create a slug from string S."
  (replace-regexp-in-string "^-\\|-$" ""
    (replace-regexp-in-string
     "[^a-z0-9]+" "-" (downcase s))))

(defun pet--replace-strings (replacement-list)
  "Replace multiple strings in the current buffer.

REPLACEMENT-LIST is an alist where each element is a cons cell (SEARCH
. REPLACE).  For each pair, all occurrences of SEARCH are replaced with
REPLACE."
  (save-excursion
    (dolist (rep replacement-list)
      (goto-char (point-min))
      (while (search-forward (car rep) nil t)
        (replace-match (cdr rep) t t)))))

Now it’s just one command pet/write-blog to start writing a new note, just like the one you are reading right now.

Permalink

June 27, 2024

life

Embrace the Challenge of Learning: Insights from Andrej Karpathy

Recently, I came across a quote from Andrej Karpathy, whose videos and teaching style on YouTube I greatly admire. His words resonated deeply with me, serving as a powerful reminder that genuine learning and progress are supposed to be challenging.

In it he outlines three steps to mastering a subject:

  • Depth Over Breadth: Focus on completing concrete projects and learning what you need as you go, rather than trying to cover everything superficially.
  • Teach What You Learn: Summarize and teach the material in your own words to reinforce your understanding.
  • Personal Progress: Measure your progress against your past self, not others.

Here’s the full quote:

There are a lot of videos on YouTube/TikTok etc. that give the appearance of education, but if you look closely they are really just entertainment. This is very convenient for everyone involved : the people watching enjoy thinking they are learning (but actually they are just having fun).

Learning is not supposed to be fun. The primary feeling should be that of effort. It should look a lot less like that “10 minute full body” workout from your local digital media creator and a lot more like a serious session at the gym. You want the mental equivalent of sweating.

I find it helpful to explicitly declare your intent up front as a sharp, binary variable in your mind. If you are consuming content: are you trying to be entertained or are you trying to learn? And if you are creating content: are you trying to entertain or are you trying to teach? You’ll go down a different path in each case. Attempts to seek the stuff in between actually clamp to zero.

So for those who actually want to learn. Unless you are trying to learn something narrow and specific, close those tabs with quick blog posts. Close those tabs of “Learn XYZ in 10 minutes”. Consider the opportunity cost of snacking and seek the meal - the textbooks, docs, papers, manuals, longform. Allocate a 4 hour window. Don’t just read, take notes, re-read, re-phrase, process, manipulate, learn.

How to become an expert at a thing:

  1. Iteratively take on concrete projects and accomplish them depth wise, learning “on demand” (ie don’t learn bottom up breadth wise).
  2. Teach/summarize everything you learn in your own words.
  3. Only compare yourself to younger you, never to others.

Permalink

April 20, 2024

elixir exercism

Piping arithmetic in Elixir

For an exercism exercise I wanted to do multiplication with the pipe operator, and simply using * does not work, neither does (*).

Now, it turns out the arithmetic operations are functions part of the Kernel module, and you can also call them as an actual function with Kernel.*(2, 2), which is equal to writing 2 * 2.

This enables you do do arithmetic as part of the pipe operation, see:

def monthly_rate(hourly_rate, discount) do
  hourly_rate
  |> daily_rate
  |> apply_discount(discount)
  |> Kernel.*(22)
  |> ceil()
end

Permalink

April 20, 2024

testing elixir

Shorter feedback loops with Elixir tests

In Ruby, you have the --fail-fast flag, which stops running the test suite at the first failed test. Convenient to get shorter feedback loops, with a long running test suite.

In Elixir, you can achieve the same with:

mix test --max-failures 1

And then when a test fails, you fix it, and make sure that it works with:

mix test --failed

That makes for shorter feedback loops!

Permalink

Hello World, strings in Zig

I am doing the Exercism exercises for Zig and decided to document my progress since, with every exercise, I learn new and interesting information.

The first exercise is doing a “Hello World” and the end solution is simple:

pub fn hello() []const u8 {
    return "Hello, World!";
}

The interesting part is that Zig represents string literals as []const u8, which is an array of bytes.

const foo = "Hello" is (almost) the same as const foo = [_]u8{ 'H', 'e', 'l', 'l', 'o' }; Let’s break it down:

  • const means it’s a constant value or immutable.
  • [_]u8 means it’s an array of bytes.
  • The compiler will infer the [_] to [5].

Where the string uses double quotes (“H”), and the individual characters (‘H’) use single quotes.

But there is more because a string also contains a sentinel value. A sentinel value is something that indicates the end of a sequence. A popular choice of a sentinel value is the value 0, also called the null character.

Zig supports sentinel-terminated arrays, slices, and pointers:

//.    This is taken from Ziglings, exercise 76.
//     const a: [4:0]u32       =  [4:0]u32{1, 2, 3, 4};
//     const b: [:0]const u32  = &[4:0]u32{1, 2, 3, 4};

Array a stores five values, the last of which is a 0. Array b is only allowed to point to zero-terminated arrays.

Now we can determine the actual type of a string in Zig:

@TypeOf("foo") == *const [3:0]u8

Translated to English, a string is a “constant pointer to a null-terminated fixed-size array of u8”.

Now, why would you still have a 0 at the end when you know the size of the array? That’s because Zig strings are compatible with C strings, which are also null-terminated.

If you want to learn more about strings, I can recommend the Zig / Strings in 5 minutes article.

Permalink