Contents
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 (++)
for arbitrary lists [a]
and (<>) = (++)
for String = [Char]
, but let’s not get into
that here.
is
(<>) :: 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 situation,I 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.