The Emacs 29 release branch was just cut—and it’s chock full of new
features! In this post, I want to talk about the new
`package-vc-install`

function, which allows one to install packages
directly from their respective upstream source; for example, GitHub. It
can be seen as a built-in alternative to things like quelpa or
straight.el.

I’ve been using quelpa and quelpa-use-package to install packages that are not on any popular archive straight from source. Especially the latter package resulted in an almost seemless integration with the rest of my configuration; for example:

```
use-package math-delimiters
("oantolin/math-delimiters")) :quelpa (math-delimiters :fetcher github :repo
```

Recently, Emacs added built-in capabilities for
installing a package directly from its remote repository. Eager to
shave yet another external package from my otherwise ever growing list,
I took `package-vc.el`

out for a spin: turns out, it almost perfectly
covers the use-case for which I—and perhaps a few other people—used
quelpa up until now!

The most user-facing of these new functions is `package-vc-install`

,
with signature

` (package-vc-install PACKAGE &optional NAME REV BACKEND)`

In the simplest case, it takes a URL pointing to some online source as its argument and installs the respective package from there, guessing the name from the URL. In case that doesn’t work—or one wants more control, like requiring a specific revision—there are some other optional arguments available, see the function’s documentation.

`package-vc-install`

When a package is already installed, `package-vc-install`

will ask the
user to interactively confirm whether they really want to overwrite the
existing directory. Naturally, this is not a good experience when
trying to use this in a non-interactive fashion.

There are a few ways one could go about fixing this. One of these is
even documented in the manual: customise `package-vc-selected-packages`

and then call `package-vc-install-selected-packages`

, which works much
like `package-install-selected-packages`

. However, this feels
unergonomic to me—at least considering that I want to use
`package-vc-install`

as a (hopefully) drop-in replacement for
use-package’s `quelpa`

keyword. Plus, I’d rather have the information
that package X is not installed from *ELPA local to the use-package
declaration of X itself.

So, let’s take the easy way out and write a small wrapper:

```
"github") repo name rev backend)
(cl-defun slot/vc-install (&key (fetcher "Install a package from a remote if it's not already installed.
This is a thin wrapper around `package-vc-install' in order to
make non-interactive usage more ergonomic. Takes the following
named arguments:
- FETCHER the remote where to get the package (e.g., \"gitlab\").
If omitted, this defaults to \"github\".
- REPO should be the name of the repository (e.g.,
\"slotThe/arXiv-citation\".
- NAME, REV, and BACKEND are as in `package-vc-install' (which
see)."
let* ((url (format "https://www.%s.com/%s" fetcher repo))
(when name (intern name)))
(iname (or iname (intern (file-name-base repo)))))
(pac-name (unless (package-installed-p pac-name)
( (package-vc-install url iname rev backend))))
```

This function can now be used under the `init`

keyword of the
use-package macro, almost without changing the shape of the declaration
from above:

```
;; Before
use-package math-delimiters
("oantolin/math-delimiters"))
:quelpa (math-delimiters :fetcher github :repo
;; After
use-package math-delimiters
("github" :repo "oantolin/math-delimiters")
:init (slot/vc-install :fetcher ;; OR (slot/vc-install :repo "oantolin/math-delimiters")
)
```

In case you think I cherry picked the example, here
is the full commit that exchanges quelpa for `slot/vc-install`

.

Admittedly, my use of quelpa was rather primitive. I can imagine users
more heavily invested in, for example, the `straight.el`

ecosystem
probably want a bit more out of their package manager than `package.el`

can give them right now, even with the added convenience of
`package-vc.el`

. However, for me—and probably at least a few people out
there—this is quite enough. After all, for anything more there’s always
nix :)

Here’s a fun one: when previewing LaTeX fragments via AUCTeX’s
`preview.el`

library (whether it be in a .tex buffer, or—via
org-auctex—in Org) things get *really* messed up when one or more
monitors are set up in portrait mode.

When you have two monitors oriented vertically, previews might end up looking something like this:

With the perhaps more common setup of one vertical and one horizontal monitor, you could instead get the charming

Imagine a whole page of this—things get pretty funky. Being a boring person, I would rather prefer the much more ordinary looking

Thankfully, this isn’t so complicated. Looking into `preview.el`

, we
get the geometry of the frame from `preview-get-geometry`

. At least,
this is what `preview-generate-preview`

calls before delegating the
heavy lifting to some internal functions. After staring at the former
function for a while, one can single out `preview-get-dpi`

as the main
culprit. It seems to calculate the “resolution” of the preview:

```
defun preview-get-dpi ()
(let* ((monitor-attrs (frame-monitor-attributes))
(cdr (assoc 'mm-size monitor-attrs)))
(mm-dims (nth 0 mm-dims))
(mm-width (nth 1 mm-dims))
(mm-height (assoc 'geometry monitor-attrs)))
(pixel-dims (cl-cdddr (nth 0 pixel-dims))
(pixel-width (nth 1 pixel-dims)))
(pixel-height (cons (/ (* 25.4 pixel-width) mm-width)
(/ (* 25.4 pixel-height) mm-height)))) (
```

Monitor details are returned by the `frame-monitor-attributes`

function;
its output for a horizontal monitor is

```
"DP1")
'((name . 0 0 1920 1080)
(geometry 0 0 1920 1080)
(workarea 530 300)
(mm-size
(frames <<omitted>>)"XRandR 1.5")) (source .
```

While the same monitor in “vertical-mode” returns

```
"DP1")
'((name . 0 0 1080 1920)
(geometry 0 0 1080 1920)
(workarea 530 300)
(mm-size
(frames <<omitted>>)"XRandR 1.5")) (source .
```

Crucially, the physical width and height of the monitor don’t change,
but the *geometry*—the pixel width and height—does; you can `C-h f display-monitor-attributes-list RET`

for more information. This means
that in portrait mode, we actually compare the pixel *width* of the
monitor with its physical *height*, as well as its pixel height with its
width. Naturally, and depending on the specific setup, this produces
too narrow or too wide previews.

The solution is to only compare the comparable values. Indeed,
overriding the built-in `preview-get-dpi`

function with

```
defun preview-get-dpi ()
(
(-let (((&alist 'mm-size (mw mh)
'geometry (_ _ pw ph))
(frame-monitor-attributes)))cons (/ (* 25.4 (max pw ph)) (max mw mh))
(/ (* 25.4 (min pw ph)) (min mw mh))))) (
```

produces the correct behaviour! This implicit assumption—that monitors
are generally wider than they are tall—of `preview-get-dpi`

should
probably be fixed; I will report it as an Emacs bug.

As an aside, this is an excellent opportunity to see the ergonomic
benefits of the dash.el library. Compare the readability of the
“fixed” implementation using `-let`

to the original one above. I
certainly know which of the two I’d rather write!

Emacs is the “extensible text editor”, and it wouldn’t be fun if one didn’t at least try to take advantage of that, right? Having just written a README for my Emacs configuration, I thought it might be nice to somewhat expand on certain ideas and give a little context to some snippets that have accumulated over time.

While there is a post about my version of the
`query-replace`

function, most other tidbits have only seen the light of
day in places like the “Weekly Tips, Tricks, &c.” thread on Reddit. In
the spirit of hosting my content somewhere that I actually control,^{1}
I chose to showcase these again here, hoping that other people may also
find some of this stuff useful.

`frame-inhibit-implied-resize`

This variable is interesting for all the people who, for one reason or another, care about the startup time of their Emacs session. Even more if one uses a tiling window manager, as then Emacs doesn’t get a say in how big its frame will be anyway. An excerpt from the documentation:

Whether frames should be resized implicitly.

If this option is nil, setting font, menu bar, tool bar, tab bar, internal borders, fringes or scroll bars of a specific frame may resize the frame in order to preserve the number of columns or lines it displays. If this option is t, no such resizing is done.

I always wondered why startup time skyrocketed whenever I changed the
default font to something else—surely opening a font and using it can’t
be that expensive! What I didn’t realise was that what I set was
slightly larger than Emacs’s default font, which I reckon was some kind
of monospace fallback on my system. Setting
`frame-inhibit-implied-resize`

to `t`

will thusly prevent Emacs from
trying to—futilely, since I use XMonad—resize its frame in order to
“preserve the number of columns or lines it displays”. The upshot is
that this cuts my startup time from just over 1 second to about 0.8
seconds. This may not seem like much, but it’s literally setting a
single variable in my `early-init.el`

—pretty good
value for money.

`pixel-scroll-precision-mode`

This is pretty old news by now, but I wanted to take the opportunity to
again praise `pixel-scroll-precision-mode`

. My day job is being a PhD
student in maths, which means that I write a lot of
LaTeX and also use Org extensively for taking notes. While ordinary
LaTeX entry in Org works quite well, commutative diagrams are a pain
more often than not. In fact, It’s much easier to draw them with a tool
like quiver, make a screenshot, and then include the resulting picture
in the file. However, now we have the problem that Emacs treats
pictures as very large single characters—the result is a scrolling
experience that’s very far from optimal. This is *exactly* where
`pixel-scroll-precision-mode`

comes in and saves the day, but see the
difference for yourself:

`org-roam`

Speaking of inserting images into Org; how does one do that, exactly? Doing everything by hand seems like a slog: select an arbitrary rectangle on the screen, take a screenshot of it, move the resulting picture into the correct directory, give it an appropriate name, and insert a link to it into the current buffer. This sounds like a lot of busywork for something that I ideally don’t want to think about at all; thankfully, most of this can be nicely automated.

```
defun slot/org-roam-insert-image ()
("Select and insert an image at point."
(interactive)let* ((file-name (format "%s-%s.png"
(
(file-name-sans-extension (buffer-name))expt 2 31))))
(cl-random (format "%s/%s/%s" org-roam-directory "images" file-name)))
(path (;; The mouse movement via xdotool is needed because otherwise, if
;; unclutter is active, the pointer will remain hidden. Uff.
"xdotool" nil 0 nil "mousemove_relative" "--" "-1" "0")
(call-process let ((scrot-exit (call-process "scrot" nil nil nil
("-z" "-f" "-s" "--file" path)))
when (= scrot-exit 0)
(format "[[../images/%s]]" file-name)))))) (insert (
```

All it needs is `xdotool`

for moving the mouse (though, if you don’t use
`unclutter`

then this may well be deleted) and `scrot`

for actually
taking the screenshot. Pretty convenient. If `scrot`

is too low-tech
for you, then the above snippet probably also works with `flameshot`

or
a similar tool.

I am pretty impatient when it comes to LaTeX entry. So impatient that I have created a few “now you’re really taking it too far”-type of functions. To be honest, they kind of delight me.

First, the following is an override for the `self-insert-command`

, which
enables faster entry of one-character math symbols:

```
defun slot/LaTeX-self-insert (&optional arg char)
("`self-insert-command' for LaTeX mode.
If the previous word is just a single character, surround it with
dollar signs. If already in math mode, do nothing. If the
character is a single `a', do nothing.
If called with a single \\[universal-argument], just call
`self-insert-command'."
"P")
(interactive
(pcase arg4) (self-insert-command 1))
('(let ((ppoint (save-excursion (backward-word) (point)))
(_ (
(ipoint (save-excursion (back-to-indentation) (point)))
(word (word-at-point)))unless (or (length> word 1) ; longer than a single character
(not word)
(= ipoint ppoint) ; the first thing on a new line
(equal "a" word)
(
(number-at-point)
(texmathp))open . close) math-delimiters-inline))
(-let (((
(backward-char)open)
(insert 1)
(forward-char close)))
(insert 1 char))))) (self-insert-command
```

Bound to `<SPC>`

(and also things like `.`

and `-`

), it enables one to
write `"foo T<SPC>"`

and have Emacs insert `"foo $T$ "`

instead—very
convenient, and much faster even than having a snippet to insert dollars
based on some condition.

The laziness continues with me not wanting to write `\blank`

so
often.^{2} I could also create an auto-expanding snippet for this, but
wouldn’t it be *much better* to insert it on a double tap of the space
bar instead? I think so!

```
defun slot/LaTeX-space (&optional arg)
("Insert a space; or not.
In case the previous character was already a space, insert
\\blank instead."
"P")
(interactive if (and (= ?\s (char-before)) (texmathp))
("\\blank ")
(insert (slot/LaTeX-self-insert arg ?\s)))
```

Now, writing something like `"C(<SPC><SPC>,<SPC><SPC>)"`

in math-mode
nicely inserts `"C( \blank , \blank )"`

. Because the space bar is so
easy to press, this is again marginally faster than having to write
something like `"C(bln,bln)"`

.

Together with auto-expanding snippets, this enables reasonably fast LaTeX entry:

More examples can be found in the post about my research workflow.

I recently switched from WeeChat to ERC for IRC. It’s
really great so far, but some things felt a bit lackluster out of the
box. As such, my ERC config has already grown quite a
bit.^{3} The following are a few tweaks that improve my experience
greatly.

One feature I was dearly missing was the ability to set urgency hints in the case I get highlighted/pinged. This is essentially the window telling your window manager or desktop environment that it wants your attention. You can then execute an action based on this urgency hint. Thankfully, Emacs is extensible, so hacking this behaviour into ERC wasn’t actually all that complicated.

```
defun slot/mark-emacs-urgent ()
("Mark the current frame as urgent."
let* ((WM-HINTS "WM_HINTS")
(
(hints (seq--into-list;; By default this returns a string/vector.
nil WM-HINTS nil nil t))))
(x-window-property WM-HINTS ;; Urgency flag: (1L << 8) == 256
;; Source (as always): https://tronche.com/gui/x/xlib/ICC/client-to-window-manager/wm-hints.html
logior (car hints) (lsh 1 8)))
(setcar hints (nil WM-HINTS 32)))
(x-change-window-property WM-HINTS hints
defun slot/beep-on-match (match-type _nickuserhost message)
("Beep and mark the frame as urgent on highlight."
let ((visible-bell nil))
(
(slot/mark-emacs-urgent)
(erc-beep-on-match match-type _nickuserhost message)))
(add-hook 'erc-text-matched-hook #'slot/beep-on-match)
```

And that’s really it! Now ERC correctly sends an urgency hint whenever
I get highlighted. Note that, as we found out, if
you use a reparenting window manager (you probably do if you use a
desktop environment) you might have to give `x-change-window-property`

above an extra `t`

argument.

Having configured quite a few regular expressions for when I get
highlighted, things can get quite overwhelming at times. For example,
when ERC starts up it prints `/users`

in every channel buffer. Of
course, I’m a user in a channel that I’m in, so Emacs happily starts
beeping and throwing around urgency hints—not a good experience. This
also clutters the `ERC Keywords`

buffer (which is built-in and akin to
WeeChat’s `highmon.pl`

).

Thankfully, however, there is a straightforward hack around this: just check the message for certain regular expression first and do nothing when they are present.

```
defun slot/erc-ignore-highlight (msg)
("Don't highlight me when these things happen."
let ((message (s-trim-left msg))
(or (erc-default-target) "")))
(channel (
(--any? (s-prefix? it message)"*** Users on"
`("*** Your new nickname is"
"*** Welcome to the"
"*** " channel ": topic set by"))))) ,(concat
```

All that’s left to do is to thread this function through to
`erc-log-matches`

and the above-defined `slot/beep-on-match`

:

```
defun slot/erc-log-matches (match-type nickuserhost message)
("Log matches to extra buffer, unless they are annoying."
unless (slot/erc-ignore-highlight message)
(
(erc-log-matches match-type nickuserhost message)))
defun slot/beep-on-match (match-type _nickuserhost message)
("Beep and mark the frame as urgent on highlight."
let ((visible-bell nil))
(unless (slot/erc-ignore-highlight message)
(
(slot/mark-emacs-urgent)
(erc-beep-on-match match-type _nickuserhost message))))
;; As before, now add the appropriate hooks to `erc-text-matched-hook'.
```

If it works, it works, right?

Next to being a user, I also spend way too much time working on XMonad. As such, I often help people coming into our IRC or posting on the subreddit with their problems. More often than not one needs to link to the same resources over and over again—why not write something so that I don’t have to dig up these links again and again?

I currently have a set-up where I can link to every XMonad module, all of my blog posts, as well as selected extra sites, like our tutorial and installation instructions. Depending on the given universal argument, a different link style is used, to accomodate for different platforms.

```
defun slot/get-xmonad-modules ()
("Get all XMonad modules in the form (NAME . DOC-URL)."
let* ((xmonad-cabal "~/repos/xmonad/xmonad-contrib/xmonad-contrib.cabal")
("https://hackage.haskell.org/package/xmonad-contrib/docs/")
(hackage
(modules (shell-command-to-stringformat "tail -n +50 %s | grep -E \" XMonad\\.*\""
(
xmonad-cabal))))
(->> (s-lines modules)1) ; empty line
(-drop-last "exposed-modules:" "" it)))
(--map (s-trim (s-replace cons it
(--map (format (concat hackage (s-replace "." "-" it) ".html")))))))
(
defun slot/get-posts ()
("Get all of my blog posts in the form (NAME . URL)."
let* ((website "https://tony-zorman.com/")
("~/repos/slotThe.github.io/")
(base-path "posts/") ".md$")))
(posts (directory-files-recursively (concat base-path
(--map (with-temp-buffer
(insert-file-contents-literally it)"title: ")
(search-forward cons ; Name . URL
("\"" "" (buffer-substring (point) (point-at-eol)))
(string-replace string-trim it base-path ".md") ".html")))
(concat website (
posts)))
defun slot/often-used-links (&optional arg)
("Choose a link and insert it into the buffer in .md format.
This is quite useful, since many people happen to have very
similar problems when, for example, first starting out with
xmonad."
"P")
(interactive
(-let* ((links"tutorial" . "https://xmonad.org/TUTORIAL.html")
(-concat '(("install" . "https://xmonad.org/INSTALL.html")
("xmonad.hs". "https://gitlab.com/slotThe/dotfiles/-/blob/master/xmonad/.config/xmonad/src/xmonad.hs"))
(
(slot/get-xmonad-modules)
(slot/get-posts)))"Link: " (mapcar #'car links)))
(choice (completing-read assoc choice links)))
((name . link) ("[" name "]")
(insert if arg
("(" link ")")
(insert "\n\n[" name "]: " link))))) (save-excursion (insert
```

I bind this to `C-c l`

in `markdown-mode`

; it looks like this:

`repeat-mode`

I’ve written a macro for Emacs’s `repeat-mode`

, which allows you to
execute repeated commands without having to press the same prefix over
and over again. For example, one can set this up for Org navigation
commands such that `C-c C-n n n`

executes `org-next-visible-heading`

three times. A great introduction to `repeat-mode`

can be found
here.

There are lots of packages
around that define different macros which probably work much better than
the one below. Even use-package now sports a `:repeat-map`

keyword
now. However, obviously the one I wrote feels the most natural to *me*,
so it’s being kept around regardless.

```
defmacro defrepeatmap (sym pairs &optional docstring)
("A macro for defining `repeat-map's.
Defines a new repeat-map called SYM with the given DOCSTRING.
The keys are derived via the list PAIRS, whose elements are cons
cells of the form (KEY . DEF), where KEY and DEF must fulfill the
same requirements as if given to `define-key'.
If the key only consists of a single character; i.e., is already
bound and a repeat map is created afterwards, simply add it to
the repeat-map SYM. If not, globally bind KEY to DEF and only
insert the last character of DEF into the repeat map SYM."
declare (indent 1) (debug t))
(progn
`(defvar ,sym
(let ((kmap (make-sparse-keymap)))
(
(--each ,pairs
(-let (((key . fun) it))if (length= key 1)
(
(define-key kmap (kbd key) fun)
(bind-key (kbd key) fun)1 key)) fun))))
(define-key kmap (kbd (s-right
kmap)
,docstring);; Tell the keys they are in a repeat map.
mapcar 'cdr (cdr ,sym))
(--each ( (put it 'repeat-map ',sym))))
```

The following would, for example, bind `mc/mark-next-like-this-word`

to
`M-s s`

globally and to `s`

in the created `mc-repeat-map`

. Likewise,
`mc/mark-next-word-like-this`

is bound to `.`

in that map, and so on.

```
(defrepeatmap mc-repeat-map"M-s s" . mc/mark-next-like-this-word)
'(("C-M-." . mc/mark-next-word-like-this)
("C-M-," . mc/mark-previous-word-like-this)
("C->" . mc/skip-to-next-like-this)
("C-<" . mc/skip-to-previous-like-this))
("`repeat-mode' keymap to repeat `multiple-cursors' bindings.")
```

This may be too implicit for many people, but for me it’s just right—and that’s sort of the point of all of this, right?

I am aware of the futility of this—Reddit is almost certainly going to be around longer than my personal site will. And yet, this feels more correct in some way.↩︎

This is a placeholder that’s often used when not wanting to explicitly quantify over an argument.↩︎

Though it’s still much smaller than whatever it is that WeeChat auto-generates :)↩︎

If you’ve been doing category theory for any amount of time, you’ll
probably have stumbled upon enriched category theory as a way of
expressing categorical ideas internal to some context other than
**Set**. Reading into it, you might have come across these foreign
sounding concepts like weighted (co)limits and wondered what that was
all about—and then got lost for a few days, trying to decipher what
Kelly is talking about and why symbols resembling tensor
products are suddenly being thrown around. At least that’s what
happened to me.

After scouring the internet for good resources, I found two really enlightening blog posts: one by Todd Trimble and the other by John Baez—and they’re too good not to share. Plus, people always say that you don’t understand a concept unless you can explain it to someone else, so here’s my shot at it!

I will assume familiarity with basic notions of category theory (limits, colimits, adjunctions, monoidal categories, …), as well as elementary abstract algebra (in particular, rings and modules). If you’re not comfortable with these and have a lot of time to kill, I recommend Category Theory in Context by Emily Riehl for the former and A Course in Algebra by Ernest Vinberg for the latter.

Really, it’s good if you have heard about enriched category theory before, as this is where weighted colimits tend to naturally crop; also because I can’t possibly do the topic justice in a single blog post. I will still try, of course, but be warned. However, weighted colimits also appear in ordinary category theory, so if you don’t want to touch the enriched stuff just insert \(\mathsf{Set}\) whenever I write \(\mathcal{V}\) below—it will only get easier. Further, most of the main part of the text doesn’t use enrichment at all, so don’t be too alarmed.

First and foremost I must note that—more-so than elsewhere—these are very much not my own thoughts. I’m just retelling the story in order to understand it better myself. Sources and resources for everything are linked at the end. The key insights come from the mentioned blog posts by Todd Trimble and John Baez, as well as the accompanying (resulting) nLab article.

As I said, we first turn our attention to *enriched* category theory.
Before diving into the gory details, I will first discuss things a bit
more intuitively. In short, one studies not ordinary categories—whose
hom*sets* are always sets—but so-called \(\mathcal{V}\)-categories, whose
hom-*objects* are objects in some “environmental” category
\(\mathcal{V}\). This category is what replaces \(\mathsf{Set}\), so it
will usually be assumed to have some *very* nice properties. For the
purposes of this blog post, I will assume that \((\mathcal{V}, \otimes, 1)\) is a (small) complete and cocomplete closed symmetric monoidal
category.^{1} If you don’t know what some of these words mean, you can
read that as “it’s an environment in which we can think about category
theory in an effective way”.

In addition, I would also like to fix a \(\mathcal{V}\)-category \(\mathcal{C}\) for the rest of this blog post. For the moment, you can think of this like an ordinary category such that for any two objects \(a\) and \(b\) in \(\mathcal{C}\), we have that \(\mathcal{C}(a, b) ≔ \mathrm{Hom}_{\mathcal{C}}(a, b)\) is an object in \(\mathcal{V}\). Naturally, all the usual axioms of a category—like associativity and unitality of morphisms—ought to hold in this new setting. As you can imagine, this makes certain things more complicated. The fact that \(\mathcal{C}(a,b)\) is an object in \(\mathcal{V}\) means that it is now a black box—we can’t peek into it anymore! Writing \(f \in \mathcal{C}(a,b)\) is no longer legal, so we somehow have to make due with not talking about individual morphisms. A little bit more care has to be taken for the precise definition of an enriched category to make sense. First, however, I will show you a few examples.

Thankfully—lest the world explodes—categories enriched in \(\mathsf{Set}\) are exactly ordinary categories. Likewise, a lot of categories that people are interested in and you may be familiar with arise in this way: 2-categories (in the strict sense) are categories enriched over \(\mathsf{Cat}\), preadditive categories are those enriched over \(\mathsf{Ab}\), and \(k\)-linear categories are ones enriched over \(\mathsf{Vect}_k\). Further, rings can also be seen as categories. Namely, they have just a single object \(\star\) and \(\mathrm{Hom}(\star,\star)\) forms an abelian group—more on that later.

With these examples in mind, let us explore the technical definition of a category enriched over \(\mathcal{V}\). Formally, such a \(\mathcal{C}\) consists of:

- A collection of objects \(\mathrm{ob}\, \mathcal{C}\).
- For \(x, y \in \mathcal{C}\), a hom-object \(\mathcal{C}(x, y) \in \mathcal{V}\).
- For \(x, y, z \in \mathcal{C}\), a composition map in \(\mathcal{V}\): \[ \circ_{x, y, z} \colon \mathcal{C}(y, z) \times \mathcal{C}(x, y) \longrightarrow \mathcal{C}(x, z). \]
- For \(x \in \mathcal{C}\) an identities map \(e_x \colon 1 \longrightarrow \mathcal{C}(x,x)\).

Further, this data has to satisfy appropriate associativity and unitality conditions:

In the above diagrams, \(\alpha\), \(\lambda\), and \(\rho\) respectively denote the associativity, left, and right unitality constraints of \(\mathcal{V}\).

If these diagrams remind you of a monoidal category, they absolutely should! Much like you can think of ordinary categories as multi-object monoids, a decent mental model for \(\mathcal{V}\)-categories is to think of them as multi-object monoidal categories.

We furthermore need analogues for functors and natural transformations—they now also come with a \(\mathcal{V}\)- prefix. The functor laws get a bit more complicated, as we can’t simply say that \(F(f \circ g) = Ff \circ Fg\), for some arrows \(f\) and \(g\), and need to draw commutative diagrams instead (remember that we can’t talk about individual arrows anymore). However, most of the intuition you already have about functors and natural transformations should carry over just fine. I will leave the technical definitions of enriched functors and natural transformations as exercises to the reader—they are relatively straightforward to write down and not all that important for what follows.

The upshot of all of this is that, in order to do enriched category theory, we not only need analogues for functors and natural transformations, but also for all the other basic notions of ordinary category theory. Since limits and colimits are among the most important constructions, people naturally started to think about how one could express them in the enriched language—this is precisely what lead to the development of weighted colimits!

One interesting thing I want to highlight about enriched functors is the induced arrow on morphisms that an \(F \colon \mathcal{C} \longrightarrow \mathcal{V}\) always comes with. Namely, \(\mathcal{C}(a, b) \longrightarrow \mathcal{V}(F a, F b)\). Because \(\mathcal{V}\) is symmetric monoidal, we can use the tensor-hom adjunction and rewrite the above to look more like an action:

\[ \mathcal{C}(a, b) \otimes F a \longrightarrow F b. \]

Likewise, a \(\mathcal{V}\)-functor \(F \colon \mathcal{C}^{\mathrm{op}} \longrightarrow \mathcal{V}\) comes equipped with an “action” from the other side:

\[ F b \otimes \mathcal{C}(a, b) \longrightarrow F a. \]

Before we get to the fun stuff, we have to talk about one more important
technical detail: copowers. The basic idea is that in any ordinary
monoidal category \((\mathcal{A}, \otimes_{\mathcal{A}}, 1_{\mathcal{A}})\), we have the tensor-hom adjunction (also called
*currying*) \({-} \otimes b \dashv [b, {-}]\). In particular, this means
that

\[ \mathcal{A}(a \otimes_{\mathcal{A}} b, c) \cong \mathcal{A}(a, [b, c]), \qquad \text{for } a, b, c \in \mathcal{A}. \]

If we’re in an enriched setting, we want to somehow “switch out” the
tensor product of the monoidal category with some action, say \(\cdot \colon \mathcal{C} \times \mathcal{V} \longrightarrow \mathcal{C}\),
while retaining this nice property. As such, the *copower* of \(c \in \mathcal{C}\) *by a* \(v \in \mathcal{V}\) is an object \(c \cdot v \in \mathcal{C}\), such that for all \(b \in \mathcal{C}\), there is a natural
isomorphism

\[ \mathcal{C}(c \cdot v, b) \cong \mathcal{V}(v, \mathcal{C}(c, b)). \]

Above I have slightly abused notation; \(\mathcal{V}({-}, {-})\) now
denotes the *internal* hom of \(\mathcal{V}\), instead of the external
one. If \(\mathcal{V}\) is clear from the context, one also often writes
\([{-},{-}]\) here. Also do remember that \(\mathcal{C}(a,b)\) is an object
in \(\mathcal{V}\) now!

The best thing about copowers is their existence when it comes to
\(\mathsf{Set}\) and ordinary categories. If \(\mathcal{A}\) has all
coproducts, there is a canonical copower \(\cdot \colon \mathsf{Set} \times \mathcal{A} \longrightarrow \mathcal{A}\).^{2} For all \(X \in \mathsf{Set}\) and \(a \in \mathcal{A}\), it is given by

\[ X \cdot a ≔ \coprod_{x \in X} 1_{\mathcal{A}} \otimes_{\mathcal{A}} a \cong \coprod_{x \in X} a. \]

The fact that this is a copower follows from

\[ \mathcal{A}(X \cdot a, b) = \mathcal{A}(\coprod_{x \in X} a, b) \cong \prod_{x \in X} \mathcal{A}(a, b) \cong \mathsf{Set}(X, \mathcal{A}(a, b)), \]

for all \(b \in \mathcal{A}\). Because of the closeness to the tensor product, people sometimes call copowers “tensors” and write them with the same symbol as they write the tensor product.

Onto the main dish. The key idea is to reframe an ordinary colimit in terms of “looking like a monoidal product”. The weighted colimit then becomes something akin to the tensor product over a k-algebra \(R\). We like rings and modules, so let’s explore this further.

To recap, when looking at bimodules \(A\) and \(B\) over some \(k\)-algebra (ring) \(R\) we can define the tensor product of \(A\) and \(B\) over \(R\), in symbols \(A \otimes_R B\), as the coequaliser

\[ A \otimes_R B ≔ \mathrm{coeq} \left( A \otimes R \otimes B \rightrightarrows A \otimes B \right), \]

where the two parallel arrows are induced by the left and right actions \(\rhd \colon A \otimes R \longrightarrow A\) and \(\lhd \colon R \otimes B \longrightarrow B\), respectively.

For ease of notation, I will often write coequalisers like the above one as

\[ A \otimes R \otimes B \rightrightarrows A \otimes B \longrightarrow A \otimes_R B. \tag{1} \]

Categorifying this notion, the ring \(R\) can be seen as a one-object category enriched over \(\mathsf{Ab}\) with object \(1 \in R\). The multiplication is recovered as function composition in \(R(1, 1)\) and the addition is given by the abelian structure. A right \(R\)-module \(A\) is then an enriched functor \(A \colon R^{\mathrm{op}} \longrightarrow \mathsf{Ab}\) and a left R-module is an enriched functor \(B \colon R \longrightarrow \mathsf{Ab}\). Inserting the definition discussed above, we have that \(A\) consists of a single object \(A1\) and a single map \(A1 \otimes R(1, 1) \longrightarrow A1\). Likewise, we obtain \(B1\) and \(R(1,1) \otimes B1 \longrightarrow B1\) in \(\mathcal{V}\). Thus, we have induced arrows

\[ A1 \otimes R(1,1) \otimes B1 \rightrightarrows A1 \otimes B1. \]

Let us forget about enrichment for a while and just study ordinary categories now. The second observation we need is the well-known fact that any colimit can be represented as a coequaliser. Suppose \(\mathcal{D}\) to be a (cocomplete) category . Given a functor \(F \colon \mathcal{J} \longrightarrow \mathcal{D}\) we can express its colimit as

\[ \coprod_{a, b \in \mathcal{J}} \coprod_{f \in \mathcal{J}(a, b)} F a \rightrightarrows \coprod_{b \in \mathcal{J}} F b \longrightarrow \mathrm{colim}_\mathcal{J} F. \]

Note that we can use what we learned about (\(\mathsf{Set}\)-valued) copowers above and write \(\coprod_{f \in \mathcal{J}(a, b)} F a\) as \(\mathcal{J}(a, b) \cdot F a\), or even \(\mathcal{J}(a, b) \times F a\), as \(\mathcal{J}(a,b)\) is a set in this case. Behold:

\[ \coprod_{a, b \in \mathcal{J}} \mathcal{J}(a,b) \times F a \rightrightarrows \coprod_{b \in \mathcal{J}} F b \longrightarrow \mathrm{colim}_\mathcal{J} F. \tag{2} \]

What’s left is to define the two parallel arrows.^{3}

One arrow is induced by the “projection” \(\pi_2 \colon \mathcal{J}(a, b) \times F a \longrightarrow F a\). Note that \(\mathcal{J}(a, b) \times F a\) is really a copower and so the existence of such an arrow is not immediately clear. We have a functor \({-} \times F j \colon \mathsf{Set} \longrightarrow \mathcal{C}\) and so \(\pi_2\) is actually the application of the unique map

^{4}\(! \colon \mathcal{J}(a, b) \longrightarrow \{\star\}\) to that functor; i.e.,\[ ! \times F a \colon \mathcal{J}(a,b) \times F a \longrightarrow \{\star\} \times F a \cong F a. \]

The other arrow is induced by a collection of actions of \(\mathcal{J}\) on \(F\), indexed by arrows \(f \colon a \longrightarrow b\) in \(\mathcal{J}\); i.e.,

\[ (\mathcal{J}(a,b) \times F a \longrightarrow F b) = \left( \coprod_{f \in \mathcal{J}(a,b)} F a \longrightarrow F b \right) = \langle Ff \colon Fa \longrightarrow F b \rangle_{f \in \mathcal{J}(a,b)}. \]

So that’s the story with expressing colimits as coequalisers. What’s next is that we need to completely reframe this in terms of actions. For the second arrow we are already done: \(F\) can be seen as a left \(\mathcal{J}\)-module.

Using the symmetry of the Cartesian product \(\times\) of sets, the arrow \(\mathcal{J}(a, b) \longrightarrow \{\star\}\) can be reinterpreted as the components of a right action of \(\mathcal{J}\) on the terminal functor \(\mathbb{T} \colon \mathcal{J} \longrightarrow \mathsf{Set}\) that sends every object to the one-element set \(\{\star\}\):

\[ (\mathbb{T}b \times \mathcal{J}(a,b) \longrightarrow \mathbb{T}a) = (\{\star \} \times \mathcal{J}(a,b) \longrightarrow \{\star\}) \cong (\mathcal{J}(a,b) \longrightarrow \{\star\}). \]

Putting these two observations together, we really have two induced arrows with type signature

\[ \mathbb{T} b \times \mathcal{J}(a, b) \times F a \longrightarrow \mathbb{T} a \times F a. \]

Inserting these into Equation \(2\), this yields

\[ \coprod_{a, b \in \mathcal{J}} \mathcal{J}(a,b) \times F a \cong \coprod_{a, b \in \mathcal{J}} \mathbb{T} b \times \mathcal{J}(a, b) \times F a \rightrightarrows \coprod_{a \in \mathcal{J}} \mathbb{T} a \times F a \cong \coprod_{a \in \mathcal{J}} F a. \]

This is exactly the way the tensor product of bimodules is defined in
Equation \(1\), hence it is very tempting to write the resulting
coequaliser as \(1 \otimes_{\mathcal{J}} F\). As such, a colimit of a
functor \(F\) over \(\mathcal{J}\) can be seen as a tensor product of
functors with the terminal functor. Now, the terminal functor is not
very interesting; what if we replace it with something more complicated?
Well, that’s exactly the point where weighted colimits come into play!
Using a *weight* \(W\) instead of \(\mathbb{T}\), we would end up with
something like

\[ \coprod_{a, b \in \mathcal{J}} W b \times \mathcal{J}(a, b) \times F a \rightrightarrows \coprod_{a \in \mathcal{J}} W a \times F a \longrightarrow W \otimes_{\mathcal{J}} F. \]

Because this looks like a tensor product—and it’s universal, due to it being a colimit—it should support some sort of currying operation: given an arrow \(W \otimes_{\mathcal{J}} F \longrightarrow c\), for some object \(c \in \mathcal{C}\), we should be able to obtain an arrow \(W \implies \mathcal{C}(F, c)\). Now’s your time to guess what exactly a weighted colimit will be!

Still in the non-enriched setting, let me now give you the formal
definition of a weighted colimit. Suppose \(\mathcal{J}\) to be a small
category. Let \(W \colon \mathcal{J}^{\mathrm{op}} \longrightarrow \mathsf{Set}\) be a presheaf—the *weight*—and suppose we have a functor
\(F \colon \mathcal{J} \longrightarrow \mathcal{A}\). The *\(W\)-weighted
colimit of \(F\)* comprises an object \(W \otimes_{\mathcal{J}} F \in \mathcal{A}\), and a natural (in \(a \in \mathcal{A}\)) isomorphism

\[ \mathcal{A}(W \otimes_{\mathcal{J}} F, a) \cong [\mathcal{J}^{\mathrm{op}}, \mathsf{Set}] (W, \mathcal{A}(F, a)). \]

Note that, by the Yoneda lemma, the above isomorphism is uniquely determined by a natural transformation \(W \implies \mathcal{A}(F, W \otimes_{\mathcal{J}} F)\). As promised, this is exactly the representation we arrived at above.

A pair of an object \(c \in \mathcal{A}\) and a natural transformation \(W \implies \mathcal{A}(F, c)\) on their own; i.e., without the universal
property, is what one would normally call a *\(W\)-weighted cocone*.

The enriched definition is now exactly the same! If \(\mathcal{J}\) is a
small \(\mathcal{V}\)-category and we have \(\mathcal{V}\)-functors \(F \colon \mathcal{J} \longrightarrow \mathcal{C}\) and \(W \colon \mathcal{J}^{\mathrm{op}} \longrightarrow \mathcal{V}\), then we can
define the *\(W\)-weighted colimit of \(F\)* as an object \(W \otimes_{\mathcal{J}} F \in \mathcal{C}\), and a \(\mathcal{V}\)-natural
(in \(c \in \mathcal{C}\)) isomorphism

\[ \mathcal{C}(W \otimes_{\mathcal{J}} F, c) \cong [\mathcal{J}^{\mathrm{op}}, \mathcal{V}] (W {-}, \mathcal{C}(F {-}, c)). \]

This is the power of this definition—it extends in a straightforward way to the enriched setting. This may now be used to great effect: in case you know what this means, among other things weighted colimits can be used to define the right notion of enriched coend.

It’s probably about time for some examples. For the first two, let us focus on cocones only; this is perhaps a little easier to understand than also throwing the universal property in there. I learned these from Richard Garner during BCQT 2022.

Suppose our diagram category is the category with two objects and one non-trivial morphism; i.e., \(\mathcal{J} ≔ \{ \varphi \colon a \longrightarrow b \}\). Further, assume that the weight \(W\) picks out

^{5}the unique arrow \(\{ 0, 1 \} \longrightarrow \{ 1 \}\) in \(\mathsf{Set}\). The functor \(F \colon \mathcal{J} \longrightarrow \mathcal{C}\) we would like to look at sends \(a, b \in \mathcal{J}\) to \(x, y \in \mathcal{C}\) and \(\varphi\) to \(\theta \colon x \longrightarrow y\).Again by the Yoneda lemma we have that a cocone is given by a natural transformation \(W \implies \mathcal{C}(F, c)\). In this restricted setting, an arrow \(Wa \longrightarrow \mathcal{A}(Fb, c)\) just picks out two morphisms. Thus, the whole thing amounts to the commutativity of the following diagram:

In more plain language, we have a commutative diagram of the form

\[ (x \xrightarrow{\;\;\theta\;\;} y \xrightarrow{\;\;g\;\;} c) = (x \xrightarrow{\;\;\theta\;\;} y \xrightarrow{\;\;f\;\;} c), \]

A slightly more complicated example is the following. Assume again that \(\mathcal{J} = \{ \varphi \colon a \longrightarrow b \}\) as above, only this time don’t work over \(\mathsf{Set}\) but over \(\mathsf{Cat}\). This means that the weight \(W\) is now a functor from \(\mathcal{A}^{\mathrm{op}}\) to \(\mathsf{Cat}\). Suppose it picks out the arrow

\[ \{ 0 \;\; 1 \} \hookrightarrow \{ 0 \cong 1 \}, \]

where these are understood to be categories. Now, a weighted cocone becomes something 2-categorical. We still pick out arrows \(f\) and \(g\), but since the category we are looking at is not the terminal one, but contains an isomorphism, the commutative diagram also becomes more complicated. Namely, we required the commutativity of

Instead of the requiring \(\theta \circ g\) to

*equal*\(\theta \circ f\), we now only require the existence of an invertible 2-cell that mediates between the two.A subcategory \(\mathcal{D}\) of \(\mathcal{E}\) is said to be

*dense*in \(\mathcal{E}\) if we can, in some sense, approximate the objects of \(\mathcal{E}\) well enough with only objects in \(\mathcal{D}\) (think of the density of \(\mathbb{Q}\) inside \(\mathbb{R}\)). Dense categories are nice because they often tell us a lot about their super categories and are sometimes easier to reason about. For example, the category of finite-dimensional (left-)comodules of any (possibly infinite-dimensional) Hopf algebra is dense inside the category of all comodules, which makes them much easier to work with than modules.Formally, \(\mathcal{D}\) is dense in \(\mathcal{E}\) if the restricted Yoneda embedding along the inclusion functor \(\iota \colon \mathcal{D} \hookrightarrow \mathcal{E}\)

\[ \mathcal{E} \longrightarrow [\mathcal{E}^{\mathrm{op}}, \mathsf{Set}] \xrightarrow{\;[\iota, \mathsf{Set}]\;} [\mathcal{D}^{\mathrm{op}}, \mathsf{Set}] \]

is still fully faithful. Another way of saying this is that every object \(e \in \mathcal{E}\) is the \(\mathcal{E}(\iota, e)\)-weighted colimit of \(\iota\). Indeed, the isomorphism we have for a weighted colimit specialised to our situation looks like

\[ \mathcal{E}(e, a) \cong [\mathcal{D}^{\mathrm{op}}, \mathsf{Set}] (\mathcal{E}(\iota, e), \mathcal{E}(\iota, a)), \]

for all \(a \in \mathcal{E}\). This is exactly what it means for the above arrow to be fully faithful.

*Exercise*: Try to find a weight such that one recovers a normal,
unweighted, cocone.

*Exercise*: As you can imagine 1. and 2. can be used to produce all
kinds of relations between \(f\) and \(g\). As such, prove the following
statements:

A variant of 1.: in the case of the weight being \(\{0, 1\} \xrightarrow{\;\;\mathrm{id}\;\;} \{0, 1\}\), we obtain a not-necessarily-commutative diagram.

A variant of 2.: in the case that the weight is \(\{ 0 \} \hookrightarrow \{ 0 \longrightarrow 1 \}\) (i.e., we only have an arrow between 0 and 1 and not necessarily an isomorphism), we get an ordinary (non-invertible) 2-cell as the weighted cocone.

And that’s it! I’ve found this intuition very helpful in trying to wrap my head around these concepts—hopefully other people will too. As a parting gift, I leave you with some more things to think about.

First, one of the most important examples of weighted colimits—and coends, of course—is the tensor product of functors. If you ever wanted to be a ninja, now is the time! It’s a fun operation to think about and play around with, and I would invite you to do just that.

Lastly, the category of weights \([\mathcal{J}^{\mathrm{op}}, \mathcal{V}]\) is actually very special: it is the free cocompletion of \(\mathcal{J}\). Every functor \(G \colon \mathcal{J} \longrightarrow \mathcal{A}\) extends uniquely (up to unique isomorphism) to a cocontinuous functor \([\mathcal{J}^{\mathrm{op}}, \mathcal{V}]\) to \(\mathcal{A}\) by the assignment \(W \mapsto W \otimes_{\mathcal{J}} G\) (note the tensor product of functors!).

Monoidal Category Theory:

Saunders Mac Lane: “Natural associativity and commutativity”. In: Rice Univ. Stud. 49.4 (1963), pp. 28–46. issn: 0035-4996.

Pavel Etingof, Shlomo Gelaki, Dmitri Nikshych, and Victor Ostrik: “Tensor categories”. In: Vol. 205. Mathematical Surveys and Monographs. American Mathematical Society, Providence, RI, 2015, pp. xvi+343.

Enriched Category Theory:

Max Kelly: “Basic concepts of enriched category theory”. In: London Math. Soc. Lec. Note Series 64, Cambridge Univ. Press 1982, 245 pp. (ISBN:9780521287029).

Republished as: Reprints in Theory and Applications of Categories, No. 10 (2005) pp. 1-136 (link)

Copowers:

Weighted Colimits:

Richard Garner: Bicategories; lecture series at BCQT 2022, Leeds.

Emily Riehl: “Weighted Limits and Colimits”; lecture notes.

This is also sometimes called a

*cosmos*.↩︎If the category \(\mathcal{A}\) is locally small. I will ignore those kinds of technicalities for the purposes of this post.↩︎

I mostly follow Trimble and the nLab here. A more explicit description (in the case of limits, but it should be easy enough to dualise) is given, for example, in Category Theory in Context, Theorem 3.2.13.↩︎

The one-element set \(\{\star\}\) is the terminal object in \(\mathsf{Set}\), hence by definition there is exactly one arrow from any other set to it.↩︎

By sending \(a\) to \(\{0, 1\}\), \(b\) to \(\{ 1 \}\), and \(\varphi\) to \(\{0, 1\} \longrightarrow \{1\}\).↩︎

One of my favourite—and most used—modules is XMonad.Actions.TopicSpace. However, it seems relatively unknown among the general XMonad community. I fear this is due to the fact that the module is quite old and formerly had a rather high barrier to entry. Despite having been given shiny new documentation, lots of people probably did not bother revisiting it and thus still don’t really understand why they might be interested in using topics instead of workspaces. Time to change that!

First of all, this post is not to be seen as a tutorial on X.A.TopicSpace, but much rather as a showcase of how its functionality could be used day to day. If you like what you see, perhaps check out the docs and give it a spin yourself! I have already written an introduction to the module in the post about my research workflow:

XMonad has a module called TopicSpace, which upgrades the X11 workspace—virtual desktop—concept to so-called topics. These are workspaces with a “theme” associated to them; for example, I have a topic for every project that I’m currently working on. This results in a very clean separation of concerns. Plus, I always know where my windows are!

Every topic has a directory and a “startup hook”, firing when the topic is switched to and empty, associated to it. While most convenient for programming related tasks—e.g., spawn

`ghcid`

in the relevant directory or automatically build and open this website—it’s also quite convenient for mathematical projects.I have set up special keybindings to bring up an Emacs session in the topic directory, or spawn a terminal there. Switching to topics is done fuzzily via the XMonad prompt, which means I only have to type a few characters to get to my destination. This makes it feasible to have 30 topics, instead of the usual 9 or so, in the first place. As a result, it’s rather fast to go from thinking about a certain problem to working on it.

At a glance, this probably does not sound very impressive—so one can have a directory and some function associated to a workspace (hereafter also called “topic”), big deal. However, we will see that with a bit of creativity this can be used to great effect.

The most obvious application of all of this is to have workspaces that
do one and only one thing. For example, I have a topic dedicated to
connecting to a VPN, should the need arise. Naturally, I automatically
want to execute `openvpn`

and pick a random server whenever I happen to
enter that workspace and it’s empty (i.e., `openvpn`

is not already
running).

More such use cases include having a topic dedicated to my RSS feed
reader, instant messaging, or IRC. Since I only show workspaces with
windows on them in xmobar, I can just glance at my status bar to find
out whether I currently have, for example, IRC open. No additional
program for checking the status of things necessary! Obviously, this
*modus operandi* takes a bit of discipline to uphold over the course of
the day, but I find that such a separation of concerns greatly reduces
mental load regarding what’s currently happening on my computer.
Definitely worth it.

In terms of code, this—as well as the following examples—heavily use the new interface to XMonad.Util.Run, which allows one to spawn processes in a declarative and compositional way; I’ve written about this in another post. For example, my RSS topic is specified thusly:

```
-- import XMonad.Actions.TopicSpace (inHome)
-- import XMonad.Util.Run
"7:RSS" $ proc $ inEditor
, inHome >-> setFrameName "elfeed"
>-> execute (elispFun "elfeed")
```

Here, `inHome`

is a little helper function that takes a topic name and
an action, and creates a new topic with `$HOME`

as its associated
directory.

You can find all of my topics (and there are a lot of them) here.

More generally, programming projects in the same language almost always require me to open the same set of standard tools, so it’s extremely convenient to directly spawn them upon first visit. This allows for very little friction before starting to work on whatever I wanted to work on.

For example, I want to open Emacs and ghcid in every Haskell project of mine—so why not automate this? Using what X.U.Run gives us, we can quickly throw together a function that executes the given instruction inside of a terminal:

```
-- import XMonad.Actions.TopicSpace (currentTopicDir)
-- 'topicConfig' is my personal topic configuration.
-- | Execute a program in the topic directory (inside a terminal).
executeInTopic :: String -> X ()
= proc $ (termInDir >-$ currentTopicDir topicConfig)
executeInTopic p >-> execute p
```

Similar functions can be created for spawning the terminal and editor:

```
-- Whatever you're looking for, it's probably in X.A.TopicSpace
-- or X.U.Run.
-- | Spawn terminal in topic directory.
spawnTermInTopic :: X ()
= proc $ termInDir >-$ currentTopicDir topicConfig
spawnTermInTopic
-- | Spawn editor in the current topic directory.
spawnEditorInTopic :: X ()
= proc $ inEditor >-$ currentTopicDir topicConfig spawnEditorInTopic
```

Check the documentation of XMonad.Util.Run to see how `inEditor`

and
`termInDir`

are defined and may be customised.

In my mathematical and other work-adjacent projects I keep it pretty simple; an editor there is mostly sufficient.

But we can also get a little bit more fancy. Since the topic action is
just a general `X`

action, we can really do anything we want in there.
In addition to spawning programs, all of my Haskell projects should
default to the `Hacking`

^{1} layout:

```
spawnHaskell :: X ()
= sendMessage (JumpToLayout "Hacking")
spawnHaskell *> spawnEditorInTopic
*> executeInTopic "ghcid"
```

And Voilà, we can now attach this action to all the topics that we want!

Note that the `*>`

operator is—in this case—just the sequencing of
actions. If you’re more comfortable with `do`

notation, you can also
write the above as

```
spawnHaskell :: X ()
= do
spawnHaskell JumpToLayout "Hacking")
sendMessage (
spawnEditorInTopic"ghcid" executeInTopic
```

Furthermore, since the associated directory for a topic can easily be
made `$HOME`

by default (as we’ve seen, TopicSpace even exports the
`inHome`

function), spawning programs in certain topics can easily be
made to replace the default keybindings!

For the sake of completeness, I will showcase one slightly more
complicated example. My main shell environment is `eshell`

and getting
sane behaviour there presents one with a few more obstacles than
`spawnTermInTopic`

did. It also uses `inProgram`

instead of `inEditor`

,
allowing access to a different instance of the Emacs server.

```
-- | Spawn an eshell frame in the current topic directory.
spawnEshellInTopic :: X ()
= currentTopicDir topicConfig >>= \dir ->
spawnEshellInTopic $ inProgram "emacsclient -a '' -c -s eshell"
proc >-> execute (progn [ "eshell" <> quote "new-shell"
"eshell/cd" <> asString dir
, "eshell/clear-scrollback"
, "eshell-send-input"
, ])
```

All in all, we have something that looks a little bit like this:

Much in the same vein as my Haskell topics, I find the `website`

topic
to be extremely handy—you can probably guess what it’s used for. Its
associated function `spawnWebsite`

switches to the “Tall” layout, spawns
an Emacs frame in the topic directory, builds the website, and opens a
browser window pointing to the local copy:

```
spawnWebsite :: X ()
= switchToLayout "Tall"
spawnWebsite *> spawnEditorInTopic
*> executeInTopic "hakyll-build.sh --hold"
*> spawn "browser-new-window.sh localhost:8000"
```

The whole thing looks like this:

Hopefully these examples have convinced you to give TopicSpace a spin; perhaps you’ve even gotten some ideas of your own you’d like to try out. Although conceptually very simple, the module can be used in a variety of ways to automate boring tasks just that tiny bit more—definitely a win in my book!

As I’ve said before, basically my entire digital life happens in either Emacs or XMonad. Thus, a lot of time spent on my setup either goes towards working on the two configurations separately, or—as we’ll do today—bringing them ever closed together.

Specifically, I want to showcase a new^{1} XMonad module:
XMonad.Prompt.OrgMode. Building on top of XMonad’s prompt—which works
much like dmenu—it is designed to rapidly capture thoughts and ideas
whenever inspiration strikes and, importantly, to do so without
disrupting one’s current workflow. The module recently got support for
Org priorities, so I figured this was as good an excuse as
any to talk about it.

One theme in the post about my research workflow was how it’s possible
to use org-mode (with all of its appendages, like org-roam) to
organise one’s thoughts. `XMonad.Prompt.OrgMode`

was created as yet
another link in that chain. Unlike when writing tiny Emacs Lisp
scripts in XMonad, this does not involve any
custom elisp code one has to run; all of it is taken care of by the
module itself.

The upshot of all of this is a tool to quickly and unobtrusively jot down an idea—quiet the monkey mind—and then get back to work straight away. For me, having something like this is very important, lest I get distracted by some thought about another project and spend the next hour or so working on that instead of doing what I was actually supposed to do. Having the idea written down in a known place—perhaps even with an automatic reminder in my agenda—helps me get rid of that creeping anxiety that I’m forgetting things left and right.

The following showcases the core functionality of the module—taking notes!

In case you *really* don’t want to forget something, there is also the
ability to create `DEADLINE`

and `SCHEDULED`

tasks. Optionally, you can
also specify a priority, depending on the importance of the note. If
you add the org file in which the TODOs are placed to the
`org-agenda-files`

variable, then this will immediately show these tasks
in your agenda!

How exactly one may specify the `SCHEDULED`

and `DEADLINE`

keywords, as
well as a date, time, and priority is covered in detail in the
documentation for `X.P.OrgMode`

.

Last but not least, it’s possible to capture the current (primary)
selection and, depending on the type of thing in it, use that as either
the body or the header of the task. If it’s a URL, create a link (i.e.,
it will be of the form `[[link][input-text]]`

); if not, just add the
selection to the body of the note.

Of course, you can use all of this together as well—directly link to that one paper or blog post you wanted to read, or that one YouTube video you want to watch on the weekend!

To round things off—and quickly showcase another cool (and new!)
module, XMonad.Actions.Prefix—here is a sample keybinding that takes
“normal” TODO notes on `M-o c`

^{2} and uses the selection when called
with a universal argument:

```
-- uses {-# LANGUAGE LambdaCase #-}
orgKey :: (String, X ())
=
orgKey "M-o c", withPrefixArgument $ (`uncurry` orgSettings) . \case
(Raw _ -> orgPromptPrimary promptNoHist
-> orgPrompt promptNoHist)
_ where
= ("TODO", "~/todos.org") orgSettings
```

I’ve found `M-u`

to be quite convenient for XMonad’s universal argument
key, mirroring the `C-u`

convention from Emacs. In either case, simply
add the `usePrefixArgument`

combinator somewhere to your `main`

function
and give it your preferred keybinding. For example:

```
main :: IO ()
= xmonad
main . usePrefixArgument "M-u"
$ def{ modMask = mod4Mask -- use <Super> as mod
}
```

If you’re anything like me, this will soon become an integral part of your workflow and you won’t want to live without it. If not, then perhaps you still don’t understand what all the fuss is about; in either case, I’d like to hear from you!

As its name suggests, Emacs’s `query-replace`

function (bound to `M-%`

by default) can be used to replace occurences of one string with
another. In comparison to other tools that are used for similar
purposes—(a subset of) keyboard macros and multiple-cursors—the whole
process after entering the `from`

and `to`

strings is interactive all
the way through: it’s very fast to step through the individual matches
and decide on the spot whether one would like to replace them or not.
Needless to say, I like `query-replace`

a lot! In true Emacs fashion,
the function also takes way too many arguments: among other things, it
can operate on the current region, backwards, or only on things
surrounded by words boundaries.

However, there is one crucial feature missing from its default
functionality: the ability to create multiple `from → to`

pairs. But
this is Emacs, after all, which means that I can just write that
`query-replace-many`

function I’ve always wanted!

Originally, my motivation came through work, where I write a lot of LaTeX. When polishing papers, it sometimes happens that I would like to change or unify the notation of certain objects in the current equation/environment/file.

When an alteration like this only encompasses a single action, like
switching `T`

to `H`

, a simple `query-replace`

after narrowing to the
current region of interest is totally sufficient. For others, like
changing `T`

to `H`

*and* `S`

to `G`

, this solution, along with
multiple-cursors and other tools people usually go for, would already be
unsatisfactory—the whole region would need to be traversed twice. Now
imagine that you want to change `T`

to `U`

*and* `U`

to `T`

: chaos!
Save having to give some sort of temporary name to one of the objects,
which would be even slower, `query-replace`

is quite useless in this
situation. It’s possible to cook up a manual solution using the
alternative `query-replace-regexp`

function and capture groups, but I’m
unsure how many people know their elisp regular expressions well enough
for that to be time efficient. I don’t, and almost certainly never
will, so it seemed much easier to automate this instead!

Thankfully, since `replace.el`

sports a decent API, writing a version of
`query-replace`

that accepts multiple arguments turns out to be easy
enough. The high-level overview is this: we read in multiple queries
until an empty input is given^{1}, build up a regular expression of the
form `"\\(?:query-1\\|query-2\\|…\\)"`

, and—when it comes to
replacing—test the current thing to be replaced against all of the
queries to select the correct one.

The beauty of this is that, since it’s really just a thin wrapper over
two functions from `replace.el`

that do the heavy lifting, all of the
modules regular functionality, like the keybindings and history, just
work.

For example, in the following I replace `T`

with `U`

and, at the same
time, `U`

with `T`

. The first few matches are stepped through and the
rest is just accepted wholesale. At the bottom, you can see the default
`query-replace`

interface when interacting with the query.

The only cosmetic imperfection of this is that, while the replacement
candidate itself is correctly updated, we see the whole regular
expression `\(?U:\|T\)`

as the thing to be replaced instead of the bit
that’s actually matching currently. However, since this would seem to
require some work and one of course sees what’s to be replaced by
looking at the thing at point, I can very much live with this for the
moment.

Below is the full source code, in all of its hacky glory. Note that you
will need to `require`

the `s.el`

and `dash.el`

libraries for this to
work, if you haven’t loaded these already (if you use any amount of
packages at all, chances are that you have).

```
defun slot/get-queries (&optional pairs)
("Get multiple `query-replace' pairs from the user.
PAIRS is a list of replacement pairs of the form (FROM . TO)."
(-let* (((from to delim arg)
(query-replace-read-args" "
(s-join
(-non-nillist "Query replace many"
(cond ((eq current-prefix-arg '-) "backward")
("word"))
(current-prefix-arg when (use-region-p) "in region"))))
(nil)) ; no regexp-flag
cons (regexp-quote from)
(from-to ("\\" "\\\\" to))))
(s-replace ;; HACK: Since the default suggestion of replace.el will be
;; the last one we've entered, an empty string will give us
;; exactly that. Instead of trying to fight against this,
;; use it in order to signal an exit.
if (-contains? pairs from-to)
(list pairs delim arg)
(push from-to pairs)))))
(slot/get-queries (
defun slot/query-replace-many
(
(pairs &optional delimited start end backward region-noncontiguous-p)"Like `query-replace', but query for several replacements.
Query for replacement pairs until the users enters an empty
string (but see `slot/get-queries').
Refer to `query-replace' and `perform-replace' for what the other
arguments actually mean."
(interactivelet ((common (slot/get-queries)))
(list (nth 0 common) (nth 1 common)
(if (use-region-p) (region-beginning))
(if (use-region-p) (region-end))
(nth 2 common)
(if (use-region-p) (region-noncontiguous-p)))))
(
(perform-replace"\\(?:" (mapconcat #'car pairs "\\|") "\\)") ; build query
(concat cons (lambda (pairs _count)
(
(cl-loop for (from . to) in pairswhen (string-match from (match-string 0))
return to))
pairs)
:query :regexpnil nil start end backward region-noncontiguous-p)) delimited
```

This isn’t

*quite*what’s actually done, but it’s the right mental model to have (since this is how the function behaves). The gory details are that we use the fact that`replace.el`

’s default suggestion is always the last query that was entered by the user. What happens on an empty input is quite deep in the bowels of`query-replace-read-from`

. Since replacing these massive internal functions sounds like a real pain, leaning on that functionality suddenly appears much more reasonable. Thus, when we get back a query that has already been entered in one way or another, we bail out.↩︎

In the post about my research workflow, I briefly mentioned having to call Emacs—or other external programs—from within XMonad. I figured that this was perhaps something that could be of use to more people than just me. After a little bit of deliberation and coming up with a generic enough API, I decided to turn it into an XMonad module!

Yesterday these changes were merged into the xmonad-contrib repository
and they are now available for everyone to try out; provided one has the
git version of XMonad installed.^{1}

I’d like to use this opportunity to both showcase the module—how and why one would use it—and talk a little bit about its only redeeming implementation detail.

Wouldn’t it be neat to have some kind of EDSL for spawning external
processes? Something where one can just compose Haskell functions
together, not having to worry about the actual underlying string
manipulations? Something that’s composable, so that one does not have
to write the same `"emacsclient -c -a '' …"`

or `"alacritty --working-directory …"`

prefix over and over again? Well, at least
that’s what I thought on some rainy afternoon a few months ago.

The first use case that I came up with was scratchpads. The idea of these things is very simple: while we normally don’t like floating windows, it’s quite convenient to have some of them around that one can bring to the current workspace, as well as hide, with a single keybinding. This is very useful for things like email, a calendar, a daily agenda, a calculator, etc.

For scratchpads to work reliably, windows need to have some unique characteristic they can be recognised by, like a special class or instance name. Endowing an application with additional properties sounds exactly like what our EDSL should be good at, so let’s try that!

Using the new features of `XMonad.Util.Run`

, we could spawn an Emacs
frame with a special name for our scratchpad hooks to grab onto, and
execute `notmuch`

:

```
mailSession :: X String
= getInput $
mailSession >-> setFrameName mailInstName
inEditor >-> eval (function "notmuch")
```

You can read the `>->`

operator a little like a pipe, where you start
with what you want and thread that information through to the end: “I
want an editor with a certain frame name that also starts up notmuch”.

In full, the above function would produce the string (broken into a few lines for better readability)

```
"emacsclient -c -a ''
-F '(quote (name . \"notmuch-scratch\"))'
--eval '(notmuch)'"
```

which would be quite bothersome to type indeed.

Because the type of `mailSession`

is `X String`

and not just `String`

,
the setup for this is a little bit different than usual when using
scratchpads. You would use it like this:

```
myScratchpads :: X [NamedScratchpad]
= do
myScratchpads -- First, get the finished string.
<- getInput $
mailSession >-> setFrameName mailInst >-> eval (elispFun "notmuch")
inEditor -- Now we can insert it into our scratchpads as normal.
pure [ NS "Mail" mailSession (appName =? mailInst) quake ]
where
= "notmuch-scratch"
mailInst = customFloating $ RationalRect 0 0 1 (4 / 5)
quake
-- The call to @namedScratchpadManageHook@ in the manageHook also
-- needs to be slightly adjusted.
myManageHook :: ManageHook
= mconcat
myManageHook
[ …=<< liftX myScratchpads
, namedScratchpadManageHook ]
```

Normally you would also add your `myScratchpads`

list to all calls of
`namedScratchpadAction`

; e.g., when you define the keys to call your
scratchpads. However, since the former lives in `X`

now, this doesn’t
work anymore! Thankfully,
nowadays
the first argument to `namedScratchpadAction`

is actually unused and
only there for backwards compatibility. This means that it’s not
necessary to enter your scratchpads there at all if they are added to
your `manageHook`

. For example, in the following I just provide the empty list:

`"M-C-t", namedScratchpadAction [] "Mail") (`

This works all the same with the above definition of `myScratchpads`

.

A full example of how a scratchpad setup would look using this machinery can be found in my config.

Spawning frames is nice and all, but how about something more complicated, like Emacs’s batch mode so that we can use it properly in scripts? No problem at all!

For example, I have the following snippet in my config to get the currently selected text and call arxiv-citation with it to produce a citation entry in my bibliography files:

```
callArXiv :: String -> X ()
= do
callArXiv fun <- getSelection -- from X.U.XSelection
url $ inEmacs
proc >-> withEmacsLibs [ ElpaLib "dash", ElpaLib "s"
ElpaLib "arxiv-citation"
, Special "~/.config/emacs/private-stuff.el" ]
, >-> asBatch
>-> eval (progn [require "arxiv-citation", fun <> asString url])
```

When executed, this translates to something like

```
emacs -L /home/slot/.config/emacs/elpa/dash-20220417.2250
-L /home/slot/.config/emacs/elpa/s-20210616.619
-L /home/slot/.config/emacs/elpa/arxiv-citation-20220510.1137/
--batch
--eval '(progn
(require (quote arxiv-citation))
(arXiv-citation "<url-in-the-primary-selection>"))'
```

I certainly know which one I’d rather type—especially with ELPA directory names changing quite frequently! On that note, arxiv-citation is on MELPA now; yay!

As this is my main use case for it, the new features of
`XMonad.Util.Run`

are quite specialised for Emacs. However, even for
other programs they may well come in handy. Drawing from the point
about scratchpads again, here is a hypothetical one that spawns a ghci
session:

`<- proc $ inTerm >-> setXClass calcInstName >-> execute "ghci" ghci `

Further, something that’s very useful when dealing with topic-based workspaces is spawning a terminal or an editor already in the current topic directory:

```
import XMonad.Actions.TopicSpace -- for currentTopicDir and more
= …
topicConfig
spawnTermInTopic :: X ()
=
spawnTermInTopic $ termInDir >-$ currentTopicDir topicConfig
proc
-- Optionally, modify the path to the editor with a function.
spawnEditorInTopic :: (String -> String) -> X ()
=
spawnEditorInTopic with $ inEditor >-$ with <$> currentTopicDir topicConfig proc
```

Quite convenient if you ask me.

If you have or know of a use case you would like to support but which is awkward with the current set of functions and combinators do not hesitate to open a pull request or an issue!

The implementation is actually very straightforward—no really, check out the source if you don’t believe me!

One concept that’s still worth touching upon is the internal use of
difference lists. The basic idea of these things is that, instead of
concatenating strings one by one, we create functions `String -> String`

and then use function composition to do the work for us:

```
-- Ordinary string concatenation
"string1" <> "string2" <> "string3" <> "string4"
-- Using difference lists:
string4 :: String -> String
string1, string2, string3,= "string1" <> s
string1 s = …
string2 s
. string2 . string3 . string4 $ "" string1
```

Note how we have to apply the entire thing to `""`

at the end in order
to actually get a string back. As a concrete example, assuming we have
set `"Emacs"`

as our editor, the `inEditor`

function would essentially
be

```
inEditor :: String -> String
= " Emacs " <> s inEditor s
```

There are some further considerations to be made, since we are in the
`X`

monad and thus the type is actually `X (String -> String)`

instead
of just `String -> String`

, but that isn’t too important for us here.

Difference lists have some performance advantages over the traditional
concatenation of strings. The concatenation `(<>)`

on strings is left
associative by default and so

```
"string1" <> "string2" <> "string3" <> "string4"
"string1" <> "string2") <> "string3") <> "string4" ≡ ((
```

However, the complexity characteristics of this operation are working
against us here; the definition of `(<>)`

on `String`

^{2} is

```
(<>) :: String -> String -> String
<> ys = ys
[] : xs) <> ys = x : xs <> ys (x
```

We are merely traversing the first string, leaving the second one
completely untouched (and unevaluated!). All in all, this means that
`s₁ <> s₂`

is in `𝓞(|s₁|)`

; given an expression of the form

`"string1" <> "string2") <> "string3") <> "string4" ((`

we will have to walk over `"string1"`

three times! What we actually
want is a right-associative ordering—exactly what function compositions
gives us. Spelled out,

```
. string2 . string3 . string4 $ ""
string1 "")))
≡ string1 (string2 (string3 (string4 "string1" <> ("string2" <> ("string3" <> ("string4" <> ""))) ≡
```

which yields the desired behaviour. In fact, this is so canonical that
instead of using `(.)`

, we could have also—perhaps a bit
confusingly—used `(<>)`

directly:

```
. string2 . string3 . string4
string1 <> string2 <> string3 <> string4 ≡ string1
```

This is the fact that the *endomorphisms* for any type `a`

—the functions
`a -> a`

—form a *monoid*. That is to say that they come equipped with
an associative an unital operation: function composition. In Haskell,
`(<>)`

is, in some sense,
overloaded so that it
can be used with any monoidal composition one can think of!^{3}

The attentive reader may have concluded that the pipe operator that we
called `(>->)`

above is really just `(<>)`

in disguise, and that’s
exactly right! I, however, thought that for people not familiar with
Haskell, giving it a pipe-like appearance would be more conceptually
amenable to the threading idea.

I haven’t benchmarked this, so it’s not entirely clear to me whether
performance is actually relevant in this situation,^{4} but using
difference lists just feels right here, and so I did.

I have to say that I’m quite satisfied with this API. In fact, if I look at the old code that only resided within my personal config, it’s even a bit more ergonomic in a few places, despite having essentially no user-specific strings hardcoded anywhere! As I said before, if you try this and find something missing, do let me know and we’ll probably find a solution! If you try this and find it useful, also let me know :)

Of course, technically none of this needs to live only inside your
XMonad config at all. In combination with the excellent turtle
library, I reckon it would be quite easy to produce Haskell versions of
cool tools like magit.sh^{5}. Go nuts!

If you

*really*want to try this feature but don’t want to bother installing any unreleased—though stable—version, message me in any way and maybe we’ll hurry up and cut 0.17.1 soon!↩︎Really, this is the definition of

`(++)`

for arbitrary lists`[a]`

and`(<>) = (++)`

for`String = [Char]`

, but let’s not get into that here.↩︎Really, for any

*semigroup*, which is a slightly weaker notion of an operation that is merely associative, but doesn’t necessarily have a unit.↩︎I suspect that the answer is “probably not”—that didn’t stop me, however!↩︎

Available here. I also maintain a slightly modified and POSIX shell compatible version here.↩︎

After reading Gilles Castel’s excellent blog post about his research workflow, I decided that it was as good a time as any to write about mine—deeming it novel enough to hopefully contribute something to the discussion.

Just like Castel, I’m a new PhD student in mathematics, which means no lab work and—in my case—no code. Just you and your inability to understand basic concepts. As such, I often scribble things down on paper or a blackboard first and, when sufficiently convinced that the information is worth keeping around, type it up. Typesetting something is a surprisingly effective way to catch errors in handwritten manuscripts!

As basically my entire digital life happens in either Emacs or
XMonad, my setup is very heavily skewed in that direction; I will make
use of these tools almost every step of the way.
As such, there is a lot of tangential almost relevant bits that I could
cover here. However, since these aren’t directly related to my
*research* workflow—and there is a lot of great resources out there
already—I decided to not do this here.^{1}

XMonad has a module called TopicSpace, which upgrades the X11 workspace—virtual desktop—concept to so-called topics. These are workspaces with a “theme” associated to them; for example, I have a topic for every project that I’m currently working on. This results in a very clean separation of concerns. Plus, I always know where my windows are!

Every topic has a directory and a “startup hook”, firing when the topic
is switched to and empty, associated to it. While most convenient for
programming related tasks—e.g., spawn `ghcid`

in the relevant directory
or automatically build and open this website—it’s also quite convenient
for mathematical projects.

I have set up special keybindings to bring up an Emacs session in the
topic directory, or spawn a terminal there. Switching to topics is done
fuzzily via the XMonad prompt, which means I only have to type a few
characters to get to my destination. This makes it feasible to have 30
topics, instead of the usual 9 or so, in the first place. As a result,
it’s rather fast to go from thinking about a certain problem to working
on it. When I’m already inside a project, I leverage Emacs’s built-in
`project.el`

library to search through files and the like.

Here I keep things relatively simple; I have a big “library” directory in which essentially all books or papers that I’ve ever read reside. This may sound a bit chaotic, but since I never interact with this as-a-directory it is actually the easiest and cleanest solution for me.

To keep a bit of order, all files are named in a consistent and
descriptive way: `authors_title.pdf`

, where `authors`

is a list of last
names of all authors separated by hyphens and `title`

is the title of
the work, also separated by hyphens. For example:

` pastro-street_double-of-a-monoidal-category.pdf`

Also in this directory are `.xopp`

files, when I scribble on the
relevant PDFs in xournalpp; more on that later.

Instead of navigating to it, all interaction with the library is done
via hmenu, a small wrapper around dmenu to facilitate this kind of
behaviour. I merely have to press `M-y`

^{2} and can then fuzzy search
through the directory. Once I’ve made a choice, PDFs are automatically
opened in zathura and `.xopp`

files are opened in xournalpp.

My bibliography is organised in a similar spirit; see Citations.

For handwritten notes I… use real paper! A little elaboration is
probably in order, having talked about `.xopp`

files and xournalpp
above. I do have a Wacom tablet lying around and I’m quite happy
annotating PDFs with it. In lieu of printing everything out, this
alleviates a little bit of the usual pain with reading papers, like
coming back to one three weeks later and getting stuck on the same
calculation as last time. I do love those annotations!

However, there is just something deeply psychologically pleasing about ordinary pen and paper—nothing beats drawing up the first version of many ideas there. It’s a very “pure” experience: there’s no noise or distractions, nothing that could break, no additional layer of abstraction between you and the maths. Chalkboards—but not whiteboards, with their ever empty markers—fall into this category as well, especially when collaborating with others.

Not without my quirks (as I’m sure you’ve noticed), I’m a bit picky
about the particular writing setup. It’s either completely white A5^{3}
paper, paired with a good (mechanical) pencil/a very fine pen, or very
thick dotted paper, paired with a fountain pen.

Quite enjoying the experience, I tend to write quite a lot of manuscripts by hand first. Of course, anything that’s supposed to be permanent should be typed up properly!

Not wanting to go insane, I use LaTeX for all of my digital digital note
taking. My writing setup for `.tex`

files is pretty similar to Karthik
Chikmagalur’s—whose excellent post you should definitely check out—so I
will not belabour the point too much here. The tl;dr is AUCTeX,
CDLaTeX, and aas.

In case you’re not used to `prettify-symbols-mode`

: the inserted LaTeX
code was

```
\begin{definition} \label{def:day-convolution}
\emph{Day convolution} of two functors $F$ and $G$ is
The \[
F * G \defeq
\int^{C,D \in \cc} \cc(C \otimes D, \blank) \otimes FC \otimes GD.
\]
\end{definition}
```

I do use some smaller packages not mentioned in Chikmagalur’s article,
like math-delimiters and latex-change-env. The former is for
quickly changing between inline and display math, complete with slurping
punctuation symbols into display math and barfing them out of inline
math. For example, “`$1 + 1$.`

” becomes “`\[1 + 1.\]`

” (with line
breaks) and back.

The `latex-change-env`

package is for changing between different kinds
of environments, including display math, while offering to rename labels
across the project if necessary. When deleting a label from an
environment, it also remembers this for the session!^{4}

One very neat feature of AUCTeX that I find myself using more and more
often lately^{5} is the in-buffer preview. Usually when writing a draft
I’m not that interested in how exactly something looks in the PDF—that
part comes later, believe me. In cases like these, just calling
`preview-buffer`

is quite convenient and lets me use the screen real
estate that a PDF viewer would have taken up for something else.

I always use pure LaTeX for writing papers, drafts, or presentations.
However, I also take lots of notes in org-mode, which, as a crude
first approximation, is something like a markup language that’s *very*
well integrated into Emacs.

For the actual note-taking, I use the venerable org-roam—a free software alternative to the proprietary Roam Research program—to jot down things that I’d like to remember for more than three days. Org-roam describes itself as a “plain-text personal knowledge management system”, which fits the bill pretty well. In short, it’s a note taking system in the spirit of the Zettelkasten method, which is essentially about having lots of notes with lots of backlinks to related concepts:

In fact, using org-roam-ui, one can even visualise the entire Zettelkasten as an interactive and pretty graph in which notes become nodes and backlinks become edges!

Org-roam suggests
keybindings for all
of the most important concepts: creating notes, inserting them, showing
all of the backlinks of a file, etc. An important extra that I’ve added
is having two “types” of notes: `reference`

s, where things that I
learned but are otherwise known reside, and `novel`

s, where I put my own
ideas.

As I’m predisposed to quite easily forget details, I regularly engage
with my Zettelkasten, so as to keep things fresh in my mind. Reading
through all of the notes that are relevant to what I’m currently working
on, creating new backlinks, filling in gaps, even deleting old
information and re-organising some local region of the graph. Indeed, I
tag every new entry as a `draft`

until further notice, forcing me to go
back there especially. This results in pretty good recollection of the
most important facts, even with my brain.

I use elfeed to query the arXiv for new preprints that are of interest to me. Thankfully, the fields I’m subscribed to tend to be moving slow-ish and so I can manage to at least read the abstract of every paper that pops up in my feed. There is also a little bit of elisp involved to print arXiv entries in a more readable way than the default formatting.

When the abstract interests me, I usually directly download the paper
into my library and open it with zathura. This is fully automated via
arxiv-citation—more on that later. I merely have to press `C-c d`

while looking at a paper and magic happens!

In the above gif, on the right-hand side you can see a score associated to each entry. While reading every abstract has worked quite well for me thus far, it’s nice to get the papers that are “probably interesting” high up, so that I’m more likely to notice them sooner rather than later. I use elfeed-score for this, which integrates seamlessly into the rest of the machinery. It compares certain features of the entry (like the title and abstract) with a list of regular expressions, increasing the total score of the entry every time it matches something.

Speaking of the arXiv, in XMonad I have bound `M-s a`

to look up the
given string there. Likewise, zbmath is searched with `M-s z`

. When
these commands get a “universal argument”—an Emacs concept that XMonad
borrowed—they automatically start a search with the current selection
instead. Briefly, pressing `M-u`

before a command can modify it in
different ways. All of my search commands act on the primary
selection when given such an argument; `M-u M-s <letter>`

will look up
the currently selected text on the relevant “search engine”. One
instance where this is very useful is for quickly switching between the
arXiv and zbmath:

For citation management, I use a very simple system—no Zotero, JabRef,
or similar technology. Concretely, this means that I have a blessed
bibliography file somewhere within my home directory and I either
symlink (when I’m writing something alone) or copy (when working with at
least one coauthor) the file into the relevant project directory. In
case of a copy operation, I only have to update a single variable in
Emacs (`arxiv-citation-bibtex-files`

), which is good enough for me and
doesn’t seem to warrant a slightly more automated, yet probably much
more complicated solution.

Adding new citations is done via the now aptly named Emacs package
arxiv-citation^{6}, with a bit of
plumbing
on the XMonad side to get Emacs going. The basic idea is that—given an
arXiv or zbmath link—we first look up the paper on zbmath to see if it
was published and, if not, just use the arXiv data to construct our own
bibliography entry instead. By default, my keybinding for this acts on
the primary selection, so I merely have to highlight the link, press
`M-o a`

, sit back, and enjoy the show. The following gif hopefully
helps drive home the point, also showcasing the format of a not yet
published paper and a published one.

And that’s it! If nothing else, this post helped me to nail down some ideas that I had lying around and got me to finally clean up and publish many of the extensions talked about here—that’s already a win in my book.

I’m sure that some details will change over the course of the next three years as I mature mathematically and my needs change, but overall I feel pretty comfortable with this setup.

Thanks to everyone who reached out! I received some inquiries as to my configurations, so here are the most important bits again, for your convenience: my Emacs config, my XMonad config, org-roam, math-delimiters, arxiv-citation, latex-change-env, hmenu.

If you’d like some examples: being employed at a university also means I have to worry a bit about efficiently dealing with bureaucracy (notmuch.el), keeping some sort of up-to-date calendar and readable todo-notes (org-mode and goodies, as well as integration via XMonad’s OrgMode prompt), accessing the universities internal nextcloud server (khalel and

`davfs2`

or`TRAMP`

), … You get the idea.↩︎I will be using Emacs style notation throughout the entire article. This means that, for example,

`C-x a`

should be read as “hold down control and press x, then release both and press a”. You can of course safely ignore this, since the keys just serve illustrative purposes. An important note for Emacs users, lest anyone be confused: in XMonad,`M-`

usually does not__ refer to the Meta key, but to the`modMask`

that the user specified. This is indeed`<Alt>`

by default, but many people understandably rebind this straight away to something that’s less likely to interfere with other programs. In my case, that’s the Super key, so`M-s`

means`<Super>-s`

and not`<Alt>-s`

.↩︎Although, admittedly, when drawing up very large diagrams I’m sometimes forced to switch to A4 paper in landscape mode.↩︎

This is based on the hash of the contents of the environment—if that changes, the label is “lost”; though it can of course still be retrieved manually from the relevant hash map.↩︎

This is, in part, due to the new

`pixel-scroll-precision-mode`

in Emacs 29, making use of XInput 2.4 functionality. It makes scrolling through buffers that are quite dense with pictures rather smooth.↩︎Ostensibly, this should be an XMonad module, as it does not necessarily have anything to do with Emacs itself. However, I had already written the elfeed integration and so the most annoying part (scraping the arXiv xml for certain bits) was already done. On the other hand, there are more Emacs than XMonad users, so perhaps doing it like this will help more people anyways.↩︎