STRUCTURED EDITING
Writing Lisp code often involves moving forms around or wrapping forms in new
forms. If you're using lispy-mode or lispyville-mode–both turned on with
Doom's lispy config in the init.el file–you can more easily build out and
modify Lisp code.
SLURPING AND BARFING
Slurping and barfing are terms for describing moving parentheses to either include or exclude forms from other forms. Let me show you what I mean.
In the tic-tac-toe project, we wrote code to find positions on the board to win or block the opponent's win. To begin, we wrote this:
(find-if (lambda (triplet) (= 20 (sum-triplet *test-board* triplet)))
*triplets*)
This finds any triplet that sums to 20 on the board. But we then wanted to
search the result of that find-if form to search for the empty space in the
triplet.
(find-if (lambda (element) (= (nth element board) 0))
(find-if (lambda (triplet) (= 20 (sum-triplet *test-board* triplet)))
*triplets*))
If you use structural editing, this can actually be difficult at first. You might naturally try to write the code like this:
(find-if (lambda (element) (= (nth element board) 0)))
(find-if (lambda (triplet) (= 20 (sum-triplet *test-board* triplet)))
*triplets*)
And then, in order to wrap the bottom find-if form with the top find-if
form, you might try to delete the final parenthesis of the top find-if and
then move it to the end of the bottom find-if form:
(find-if (lambda (element) (= (nth element board) 0)) ; deleted paren
(find-if (lambda (triplet) (= 20 (sum-triplet *test-board* triplet))) *triplets*)) ; added
; paren
What actually happens, however, is that the entire top find-if form is deleted
when you try to delete just the last parenthesis! This very confusing behavior
is from lispy-mode. It is designed to keep parentheses balanced at all times.
If you want to move only one parenthesis, then, what do you do?
One option would be to kill the find-if form intended to become the inner
form…
(find-if (lambda (element) (= (nth element board) 0)) ; bottom form killed
and then paste it at the end of the outer find-if…
(find-if (lambda (element) (= (nth element board) 0)) (find-if (lambda (triplet) (=
20 (sum-triplet *test-board* triplet))) *triplets*))
and then move the cursor to the beginning parenthesis of the inner find-if and
press ENTER to place it on a new line.
(find-if (lambda (element) (= (nth element board) 0))
(find-if (lambda (triplet) (= 20 (sum-triplet *test-board* triplet)))
*triplets*))
This is okay, but there is an even easier method called slurping.
Start with this code:
(find-if (lambda (element) (= (nth element board) 0)))
(find-if (lambda (triplet) (= 20 (sum-triplet *test-board* triplet)))
*triplets*)
Place the cursor on the last parenthesis of the top form. While still in Evil
Normal mode (not Insert mode), press > (the lispyville-mode keybinding for
lispyville-slurp). The parenthesis will move to the end of the bottom
find-if form, making it an inner nested form.
Slurping is especially useful for any time you need to "build out" from some code. Consider this:
(find-if (lambda (triple)
(= (sum-triple *test-board* triple) target-sum))
*triples*)
This is how we started building out the code for detecting squeezes. But then we
realized that we needed to add more conditions. For a strategy to be a squeeze,
the triplet need to sum to the target, and it needs to be diagonal as well as
some other conditions. That means that we needed to wrap several predicate
functions in an and form.
;; These need to be wrapped in an (and ...) form
(= (sum-triple board triple) target-sum)
(diagonal-p triple)
We can do that by first writing the and form before the other two forms:
(and) (= (sum-triple board triple) target-sum)
(diagonal-p triple)
Put the Evil Normal-mode cursor on the end paren of the and form, and then
slurp up the other two forms.
(and (= (sum-triple board triple) target-sum)
(diagonal-p triple))
Once we added the rest of the conditions, we wanted to then bind the return
value of the find-if form to a local variable using let. The process looks
like this:
;; Write the LET and variable we want in the LET.
(let ((squeeze-p))) (find-if (lambda (triple)
(and (= (sum-triple board triple) target-sum)
(diagonal-p triple)
(not (human-in-middle-p board))
(side-empty-p board)))
*triples*)
Then use lispyville-slurp on the three ending parentheses of the let form to
complete the new form.
After adding an if form, we need to make this a function, so we can write the
function definition above the entire let form:
(defun detect-squeeze (board target-sum))
(let ((squeeze-p
(find-if (lambda (triple)
(and (= (sum-triple board triple) target-sum)
(diagonal-p triple)
(not (human-in-middle-p board))
(side-empty-p board)))
*triples*)))
(when squeeze-p
(find-empty-position board *sides*)))
And then just slurp up the entire let into the defun form.
Again, you could just kill and paste the forms around, but killing can
sometimes be dangerous (if you kill a word using M-backspace, you've just
replaced your function in the kill-ring). It's nicer to simply keep the code on
the screen and using slurp to do what you want.
The reverse of slurp is barf, bound to < in Doom Emacs lispyville-mode.
I don't find it nearly as useful as slurping, but maybe you can find a use for
it.
DRAGGING FORMS FORWARD/BACKWARD
Another operation you'll probably want to do a lot is move forms around. For example, maybe you wrote this:
(defun choose-move (board)
(or (make-three-in-a-row board)
(block-opponent-win board)
(take-random-position board)
(block-two-on-one-play board)
(block-squeeze-play board)))
Then you realized that the block-two-on-one and block-squeeze-play
strategies need to take higher priority over all but the make-three-in-a-row
strategy. You could, of course, kill/paste them into the proper position. But if
you want, you can also use lispyville-drag-backward–bound to M-k–while the
cursor is on the opening parentheses of the block- forms to move them.
lispyville-drag-forward–bound to M-j–goes in the opposite direction.
They also work on individual symbols. For example, let's say you have a call to
nth:
(nth 4 my-list)
But then you decide you want to use member instead.
(member 4 my-list)
Unfortunately, you now need to switch the location of 4 and my-list in the
arguments to member. Fortunately, you can use lispyville-drag- functions for
that, too. Place the cursor over the 4 and use lispyville-drag-forward (M-j)
to switch the 4 and my-list.

