2

Context

Call me an uncultured non-functional programmer (or call emacs-lisp and uncultured non-functional language), but when I code in elisp I tend to use the following pattern (Edit: As an alternative to let*)

(let (variable1 variable2)
    (setq variable1 (some-function))
    (setq variable2 (some-function))

I tend to find this more readable for my uncultured eyes.

Unfortunately, this means there is a lot of jumping back and forward between let and setq.

Question

Is there a way for me to quickly and easily insert a name into the surrounding let as shown:

(let ()
    ...
    CURSOR

becomes

 (let (variable)
    ...
    CURSOR stays still

Approaches considered

emr has some functions for emacs lisp refactoring. But not the one I want. The closest is extracting variables to let (together with assignment). I had a look at extending this but balked at the amount of code I would need to understand.

I had a brief look at erefactor, but relevant functionality wasn't there.

Code it myself This isn't hard, it's just I would prefer to use someone else's implementation should it exist. This feels like the sort of thing that should exist.

Edit: An example of why you might want to do this

Here is a function that sums numbers on the two previous lines and inserts the result.

(defun sum-lines ()
    (let (a b)
         (previous-line 2)
         (setq a (string-to-int (thing-at-point 'line))
         (forward-line 1)
         (setq b (string-to-int (thing-at-point 'line)))
         (forward-line 1)
         (insert (format "%S" (+ a b))))

Do you actaully want to write this as

(let* (
   (a (save-excursion (previous-line 2) (thing-at-point 'line))
   (b (save-excursion (previous-line 1) (thing-at-point 'line))

or

(let* (
   (a (progn (previous-line 2) (thing-at-point 'line))
   (b (progn (next-line 1) (thing-at-point 'line))

or

(defun line-at (n)
     (save-excursion
        (previous-line n)
      (string-to-int (thing-at-point 'line))

(insert (+ (line-at 2) (line-at 1)))
  • 3
    You write code for people (including yourself in 6 months) to read. When you insist on using your idiosyncratic style, you are making life worse for everyone. – sds May 11 '17 at 17:00
  • 1
    That's rather a harsh downvote... I would encourage you to temper your behaviour and consider your community. There is a lot to say about coding standard – Att Righ May 11 '17 at 17:05
  • 3
    Given that your style is more verbose, less efficient, and not idiosyncratic, I wouldn't make any particular effort to accommodate it. But maybe you could simply go one step further and just drop the let binding ;-) – Stefan May 11 '17 at 17:41
  • 2
    Please see Emacs Lisp Coding Conventions and Google Common Lisp Style Guide. – sds May 11 '17 at 18:13
  • 1
    I don't understand what "insert a name into the surrounding". Can you please write the "before" and "after" the operation? Writing Lisp code walkers is something every lisper does eventually. It's interesting in and of itself. Also, I'm against of "don't do it" answers in general. There's too much baseless bias in programming community as to how programs should be written on one hand, and on the other hand, a failed experiment could often be a stepping stone to a breakthrough, much more often than a successful iterative one. – wvxvw May 12 '17 at 5:48
1

my 2 cents :)

(defun my-let-add (var)
         (interactive "swhat do add? ")
         (save-excursion
           (beginning-of-defun)
           (search-forward "(let" nil t)
           (search-forward ")" nil t)
           (left-char)
           (unless (looking-back "(")
             (insert " "))
           (insert var)))

nice reference: http://wikemacs.org/wiki/Emacs_Lisp_Cheat_Sheet#Positions

1

Well the profound consensus appears to be "don`t do that", meaning that I'm probably not going to get an answer, and probably shouldn't do that. I guess I'm going to have to answer this myself.

I think the answer for the (probably too broad for SE) meta-question of "jumping between let's, let*'s and the body of a function is irritating' is a combination of:

  • (Obvious but easily forgotten) advice to have small functions, localise side effects, make judicious use recursion and save-excursion, making use of common lisp coding standards (as suggested by @sds) to make up for the comparative brevity of emacs's coding guide.
  • When you have side-effect-free code (as cl coding standards encourage but is rarely the case in emacs lisp code) make use of emr-el-extract-to-let to avoid jumping around.

  • Use let* if your variables depend upon one another. This pattern is more common within emacs lisp code than the use of unassigned let expressions (though this is far from universal). emr-extract-to-let will place items in a pre-existing let*. I'm not sure if emr-extract-to-let will order things correctly in let* however.

  • If you are mutating global state and then using setq based upon this state then consider point 1. Alternatively, a common pattern in emacs lisp code seems to be using a let-binding with an initial value and ignore it. In which case you could use emr-extract-to-let on the value nil, possibly with some scripting.

  • Put up with it.

I can't help but feeling that functional coding conventions don't necessarily sit well with mutable state (particularly the (point) and the various functions that depend upon it).

  • 1
    The pattern of let-binding variables, then setting them is what letrec compiles to. So, assuming you'd actually need to do it that way because of mutually recursive bindings, just use letrec. If you don't, well, don't, just use whatever is maximally readable and conveys the meaning best. – wasamasa May 11 '17 at 21:35
1

My version (avoids inserting into (let ...) which is not the dominating node of the variable being promoted):

(defun wvxvw/promote-var-to-let (varname)
  (interactive (list (thing-at-point 'symbol)))
  (save-excursion
    (while (not (looking-at "(let"))
      (backward-up-list))
    (when (looking-at "(let")
      (forward-word)
      (forward-sexp)
      (backward-char)
      (insert "\n" varname)
      (forward-char)
      (backward-sexp)
      (indent-sexp))))

Note that introducing let automatically, when none found is a non-trivial task. It isn't possible to know if you are crossing the boundary of where let can be inserted (eg. if you step over defun). This is so because if you have a macro that expands to lambda, you cannot insert let outside of it, but trying to figure out if particular lisp form will expand to lambda isn't generally possible.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.