There are some mythical figures in the folklore of emacs and their names are BSG’s office secretaries. Bernard Greenberg (a.k.a. BSG), the inventor of Multics Emacs, was an employee of Honeywell and probably those mythical figures were employees of Honeywell too. According to the legend:
Multics Emacs proved to be a great success—programming new editing commands was so convenient that even the secretaries in his office started learning how to use it. They used a manual someone had written which showed how to extend Emacs, but didn’t say it was a programming. So the secretaries, who believed they couldn’t do programming, weren’t scared off. They read the manual, discovered they could do useful things and they learned to program.
So, those mythical figures weren’t told programming Lisp is programming and they eventually learned to extend emacs. In the next three posts, I am going to talk about extending emacs. But I am not going to mention the p word. pst!
In order to define a function, the keyword defun
is used.
(defun calculate-area (radius)
(* radius radius pi)
)
This is a function. And one can call this function by either the ielm REPL, the *scratch*
buffer and then C-x C-e
(see above demo). My preferred method is:
M-: (calculate-area 5)
Just like many languages, the last value returned is also the value returned by the function. This function can’t be called by M-x calculate-area
and therefore is not a command. A command is an interactive function. A small modification to this is:
(defun calculate-area (radius)
(interactive)
(* radius radius pi)
)
Now, by adding the call to interactive
, calculate-area
is a command. But M-x calulcate-area
gives error. The following is another modification.
(defun calculate-area (radius)
(interactive "nRadius: ")
(message "%f" (* radius radius pi))
)
One could feel the “%f” thing (formatting a floating point number) is mysterious. The following might semantically make more sense. It’s completely fine, in my opinion, to program like that, rather than opt for brevity or apply any clever trick. In the end, the source code is meant to be read by humans, inkl. the creator of the command.
(defun calculate-area (radius)
(interactive "nRadius: ")
(message (number-to-string (* radius radius pi)))
)
Usually, one programs creates commands for their side effects. And I actually don’t like using interactive
to ask for user input. That’s also quite mysterious (e.g. the “n” initial has meaning). I usually make commands with no argument and use one of those read-*
function (probably only read-string
and read-number
). If possible, I don’t use read-*
at all.
(defun calculate-area ()
(interactive)
(setq radius (read-number "Radius: "))
(message "%f" (* radius radius pi))
)
It’s also nicer to have a docstring
(defun calculate-area ()
"Calculate the area of circle with radius"
(interactive)
(setq radius (read-number "Radius: "))
(message "%f" (* radius radius pi))
)
And it’s even nicer to use let
rather than setq
because it prevents confusion.
(defun calculate-area ()
"Calculate the area of circle with radius"
(interactive)
(let ((radius (read-number "Radius: ")))
(message "%f" (* radius radius pi))
))
In summary: a command is an interactive function which can be called by M-x
. A non-interactive function cannot be called by M-x
, instead by M-:
. One usually creates interactive functions a.k.a. commands for their side-effects.
I don’t think I have a vast knowledge in emacs. I am surely a poseur. But I would say the above knowledge alone, with a bit of exploration and imagination of course, is enough to get me quite far.
Suppose I want to build a command to create reproducible examples, or reprexes. I have an idea of how to run that: I have something in my clipboard kill ring.
, then I need to run the following R programming: reprex::reprex()
, and then I will have the reprex in the system clipboard which I can paste into a Github issue or Stackoverflow or whatnot.
The most important part of it is to find out how to execute a line of R programming. My first step is to use C-h f
to find a possible function (see day 8). I search for “ess eval” and there are quite a number of hits. After digging through all of them, it seems that ess-eval-linewise
is what I need.
And then I give it a try:
(ess-eval-linewise "rnorm(100)")
So far so good. Then I can create a really crude version of the reprex emacs command.
(defun reprex ()
(interactive)
(ess-eval-linewise "reprex::reprex()")
)
It gets the job done! But I still need to manually put some R code into the clipboard kill ring. What if I can just select some R code, run reprex
and then I can have a reprex? All I know about this is that a selected region in emacs is called… a region. The starting location of the region is called a mark, the current location of the cursor is called a point.
And the logic would be: If there is an actively selected region, copy save the region from the mark to the point into the kill ring, run “reprex::reprex()” in R; if there is no actively selected region, I assume that the R code is already in the kill ring, run “reprex::reprex()” in R.
At this point, it is absolutely okay to search online on how to check for active region. Probably that is faster.
I know that functions returning just Boolean values are usually called predicate functions. In some lisp languages, they are usually named with an ending question mark. In emacs lisp, they are named with an ending “-p”. Some digging reveals use-region-p
(and it is a function, not a command). I can quickly give a test. Select something, M-: (use-region-p)
gives t
; select nothing, M-: (use-region-p)
gives nil
.
After a region is selected, I usually press M-w
to copy the region to the kill ring. C-h k
reveals that M-w
maps to (kill-ring-save)
. Reading the help file, the signature of the function is: (kill-ring-save BEG END &optional REGION)
. This signature means that this function must have arguments BEG
and END
while REGION
is optional. And then it says:
When called from Lisp, save in the kill ring the stretch of text between BEG and END, unless the optional argument REGION is non-nil, in which case ignore BEG and END, and save the current region instead.
So I test it this way: M-: (kill-ring-save nil nil t)
. So, I basically give BEG
and END
as nil, but REGION
as non-nil. And sure enough, it saves a region in the kill ring. And now we have all the ingredients.
(defun reprex ()
(interactive)
(when (use-region-p)
(kill-ring-save nil nil t))
(ess-eval-linewise "reprex::reprex()")
)
I can go this far by consulting the help system. Not too bad for a nonsensical-speaking emacs poseur. Tomorrow I will give another example.