Posted on 2022-05-25 · last modified: 2023-03-07 · 7 min read · emacs, haskell, xmonad
Contents
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!
These changes now live as part of the xmonad-contrib repository and
are—from version 0.17.1 onwards—available for everyone to try out.
You can read the
which would be quite bothersome to type indeed.
Because the type of
Normally you would also add your
This works all the same with the above definition of
When executed, this translates to something like
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!
Further, something that’s useful when dealing with topic-based
workspaces
is spawning a terminal or an editor already in the current topic
directory:
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!
Note how we have to apply the entire thing to
There are some further considerations to be made, since we are in the
However, the complexity characteristics of this operation are working
against us here; the definition of
We are merely traversing the first string, leaving the second one
completely untouched (and unevaluated!). All in all, this means that
we will have to walk over
which yields the desired behaviour. In fact, this is so canonical that
instead of using
This is the fact that the endomorphisms for any type
Alternatively, one could use the git versions of
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.
xmonad
and
xmonad-contrib
; refer to
INSTALL for more information.Main use cases§
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.
Scratchpads§
The first use case that I came up with was scratchpads. The idea of these things is 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 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 ofXMonad.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
mailSession = getInput $
inEditor >-> setFrameName mailInstName
>-> eval (function "notmuch")
>->
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)'"
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]
myScratchpads = do
-- First, get the finished string.
mailSession <- getInput $
inEditor >-> setFrameName mailInst >-> eval (elispFun "notmuch")
-- Now we can insert it into our scratchpads as normal.
pure [ NS "Mail" mailSession (appName =? mailInst) quake ]
where
mailInst = "notmuch-scratch"
quake = customFloating $ RationalRect 0 0 1 (4 / 5)
-- The call to @namedScratchpadManageHook@ in the manageHook also
-- needs to be slightly adjusted.
myManageHook :: ManageHook
myManageHook = mconcat
[ …
, namedScratchpadManageHook =<< liftX myScratchpads
]
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")
myScratchpads
.
A full example of how a scratchpad setup would look using this machinery
can be found in my config.
Calling Emacs in scripts§
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 ()
callArXiv fun = do
url <- getSelection -- from X.U.XSelection
proc $ inEmacs
>-> withEmacsLibs [ ElpaLib "dash", ElpaLib "s"
, ElpaLib "arxiv-citation"
, Special "~/.config/emacs/private-stuff.el" ]
>-> asBatch
>-> eval (progn [require "arxiv-citation", fun <> asString url])
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>"))'
Other programs§
As this is my main use case for it, the new features ofXMonad.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:
ghci <- proc $ inTerm >-> setXClass calcInstName >-> execute "ghci"
import XMonad.Actions.TopicSpace -- for currentTopicDir and more
topicConfig = …
spawnTermInTopic :: X ()
spawnTermInTopic =
proc $ termInDir >-$ currentTopicDir topicConfig
-- Optionally, modify the path to the editor with a function.
spawnEditorInTopic :: (String -> String) -> X ()
spawnEditorInTopic with =
proc $ inEditor >-$ with <$> currentTopicDir topicConfig
Implementation considerations§
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 functionsString -> String
and then use function composition to do the work for us:
-- Ordinary string concatenation
"string1" <> "string2" <> "string3" <> "string4"
-- Using difference lists:
string1, string2, string3, string4 :: String -> String
string1 s = "string1" <> s
string2 s = …
string1 . string2 . string3 . string4 $ ""
""
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
inEditor s = " Emacs " <> s
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"
(<>)
on String
Really, this is the definition of
is
(++)
for arbitrary lists [a]
and (<>) = (++)
for String = [Char]
, but let’s not get into
that here.(<>) :: String -> String -> String
[] <> ys = ys
(x : xs) <> ys = x : xs <> ys
s₁ <> s₂
is in 𝓞(|s₁|)
; given an expression of the form
(("string1" <> "string2") <> "string3") <> "string4"
"string1"
three times! What we actually
want is a right-associative ordering—exactly what function compositions
gives us. Spelled out,
string1 . string2 . string3 . string4 $ ""
≡ string1 (string2 (string3 (string4 "")))
≡ "string1" <> ("string2" <> ("string3" <> ("string4" <> "")))
(.)
, we could have also—perhaps a bit
confusingly—used (<>)
directly:
string1 . string2 . string3 . string4
≡ string1 <> string2 <> string3 <> string4
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!Really, for any semigroup, which is a slightly weaker notion of
an operation that is merely associative, but doesn’t necessarily
have a unit.
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 situationI suspect that the answer is “probably not”—that didn’t stop me,
however!
, but using
difference lists just feels right here, and so I did.