SCENARIO: An Emacs user gets up and leaves the room, leaving their Emacs instance running and unattended. As a prank, you decide to overwrite the built-in
if operator with another operator that is just like
if except that it occansionally swaps its then- and else-clauses. How can this be done? Here are three challenges, in ascending order of difficulty, related to this flaky
flaky-if, which is just like
if except that, say, 1% of the time it swaps its then- and else- clauses.
As a test, the following expression should evaluate to around 101:
NOTE: This will not fulfill the goal of the prank, but it’s a good warmup for Challenge #2.
Make the built-in
if operator act the same way as
flaky-if from Challenge #1 without screwing up the running Emacs instance.
As before, the following should evaluate to around 10:
WARNING: Take heed of the italicized qualification. You can really jack up an Emacs session if you get this wrong, and that will quickly alert the mark that their computer has been tampered with.
After Challenge #2, the built-in
if operator is overwritten with the flaky version. Find some Elisp code which, when evaluated under the regime of the flaky
if, behaves strangely but without being completely busted.
NOTE: “Completely busted” includes throwing a stack overflow error, or slowing to a crawl, or anything like that. Something should happen, and it should somewhat resemble the code’s intended behavior. This is an open-ended challenge. Feel free to change the 1% flip-rate to something like one-in-a-million.
First, some review. The signature of the
if operator is
(if test then &rest else), and it is evaluated as follows. First the
test expression is evaluated. If it comes out to non-nil, then
then is evaluated and returned; otherwise, the zero or more
else expressions are evaluated in sequence with the last one being returned if there is one or
With normal function calls, each argument is evaluated exactly once at call-time.
if doesn’t work like that, and so neither will
flaky-if. The standard Lisp facility for defining function with different evaluation patterns is
defmacro defines a new function that acts as a compiler directive. Rather than receiving evaluated arguments, it receives its arguments unevaluated; that is, it receives Lisp code as arguments. The typical output of a macro is another Lisp expression which will itself get evaluted later. The macro function will contain logic to shuffle around its initial arguments, possibly evaluating any of them.
Macro definitions frequently employ backtick-comma-at notation. Whereas the quote operator
' returns a list whose elements are unevaluated, the backtick operator
` returns a list whose elements are evaluated if preceded with a comma
,, evaluated and spliced in if preceded with a comma-at
,@, and unevaluated otherwise.2
One way to define
flaky-if would be to shuffle around the
else blocks inside some
if expression. But observe that the same effect could be achieved simply by negating the
test expression. A function can be chosen at compile time to be attached to
test; 1% of the time it will be a negating function, and the other 99% of the time it will be a function that does nothing.
The final result of the macro needs to be a Lisp expression, so all that remains is to plug in the pieces:
And that’s a solution to Challenge #1.
Challenge #2 is trickier.
flaky-if relies on the builtin
if, but if the goal is to redefine
if, then that won’t be possible. (Or will it? Try it and see what happens.)
The thing that
if is needs to be saved somewhere for use in the new
if macro that will be defined. There are a lot of ways that you might think to do this, and most of them are wrong. One way that seems to work, and I don’t exactly understand how, is to take the function-value of the symbol
if and store it in the function slot of some other symbol, say
old-if. Then the new
if macro can rely on
old-if, which does the same thing as the builtin
if, and the macro will otherwise look like the definition of
For Challenge #3, I changed the
random call in the
if macro to
(random 1000000) and then evaluated the file
tetris.el. This resulted in something that looks like Tetris, but with some unusual collision detection:
flaky-ifoperator be defined in a non-Lisp language? If so, how? If not, why not?
Are there any other ways to solve Challenge #2?
Ben Bitdiddle’s answer to Challenge #1 is similar to the given solution, but it doesn’t work. What’s wrong with it? How can it be salvaged?
- Alyssa P Hacker thinks that there must be something wrong with the given solution to Challenge #1. She proposes the following instead:
Eva Lu Ator disagrees, saying that the two definitions are equivalent, and that Alyssa’s solution is actually a little slower. Who is right?
- Louis Reasoner finds it wasteful to call
randomon every invocation of
flaky-if. Instead, he thinks it should suffice to call it just once at macro definition time:
What does his version of
- The goal of this prank is ultimately to lead the mark into a debugging hell from which they may never return. The best solution, therefore, is the one that is hardest to debug. With that in mind, is it better to use a macro that behaves randomly at compile-time or at run-time? Does it make a difference if the mark is evaluating code interactively or byte-compiling? Is there a way to combine compile-time and run-time randomness to make the behavior even harder to debug?
if challenge was initially posed by Joe “begriffs” Nelson. The
setf line in the solution to Challenge #2 was developed by Erik Anderson. Christopher “skeeto” Wellons raised some issues concerning byte-compilation.
(require cl-lib) first if needed.
2 As a quick check of understanding, evaluate the following expression:
3 I can’t seem to get the number of this list right, but I don’t feel like delaying this post to fix it. It has something to do with the Org code blocks.