Wilfred / suggest.el

discover elisp functions that do what you want
367 stars 14 forks source link

I think you know what the problem is just as well as I do. #42

Closed nickdrozd closed 6 years ago

nickdrozd commented 6 years ago
;; Inputs (one per line):
"hal"

;; Desired output:
"ibm"

;; Suggestions:
(??? 1 "hal")

Wouldn't it be neat if suggest could come up with a function like ????

https://www.youtube.com/watch?v=qDrDUmuUBTo


Actually, Emacs does have a rot13 function, so I tried adding it to suggest-functions and running this:

;; Inputs (one per line):
"suggest"

;; Desired output:
"fhttrfg"

;; Suggestions:

I didn't get an answer. Instead, Emacs froze until I hit C-g. I checked *Messages* to see what had happened, and it was completely filled up with these:

Showing all blocks ... done
Showing all blocks ... done
Showing all blocks ... done
Showing all blocks ... done
Showing all blocks ... done
Showing all blocks ... done
Showing all blocks ... done
Showing all blocks ... done

Oh, and the suggest buffer had been wiped clean.

Now where, you ask, did that message come from? Hey, it's hs-show-all:

(defun hs-show-all ()
  "Show everything then run `hs-show-hook'.  See `run-hooks'."
  (interactive)
  (hs-life-goes-on
   (message "Showing all blocks ...")
   (let ((hs-allow-nesting nil))
     (hs-discard-overlays (point-min) (point-max)))
   (message "Showing all blocks ... done")
   (run-hooks 'hs-show-hook)))

Wait, why would rot13 trigger hs-show-all? Isn't it just a string manipulation function? No, it turns out that things are a little more complicated:

(defvar rot13-translate-table
   (let ((str (make-string 127 0))
         (i 0))
    (while (< i 127)n
      (aset str i i)
      (setq i (1+ i)))
    (setq i 0)
    (while (< i 26)
      (aset str (+ i ?a) (+ (% (+ i 13) 26) ?a))
      (aset str (+ i ?A) (+ (% (+ i 13) 26) ?A))
      (setq i (1+ i)))
    str)
  "String table for ROT13 translation.")

;;;###autoload
(defun rot13 (object &optional start end)
  "Return ROT13 encryption of OBJECT, a buffer or string."
  (if (bufferp object)
      (with-current-buffer object
    (rot13-region start end))
    (rot13-string object)))

;;;###autoload
(defun rot13-string (string)
  "Return ROT13 encryption of STRING."
  (with-temp-buffer
    (insert string)
    (rot13-region (point-min) (point-max))
    (buffer-string)))

;;;###autoload
(defun rot13-region (start end)
  "ROT13 encrypt the region between START and END in current buffer."
  (interactive "r")
  (translate-region start end rot13-translate-table))

rot13 calls rot13-string, which creates a temporary buffer with just the string inserted into it. rot13-string then passes the entire contents of the buffer (as min and max points) to rot13-region, which mutates the string in the buffer according to ROT13, and then returns the whole contents of the buffer as a string.

If you're anything like me, you looked at that code and thought: What the shit is this? Why does the simple string function call the elaborate buffer-mutating function as a subroutine rather than the other way around? What kind of idiot would design a package like this?

In fact, nobody designed the package like that. Way back in 1992 (and presumably as early as 1988), rot13.el didn't even have the rot13 function. The only function it had was rot13-other-window, which causes another window to display the ROT13 contents of the current window's buffer. (Note that this ROT13-display is a condition of the window rather than the buffer, and it apparently cannot be undone except by deleting the window or calling toggle-rot13-mode.) Indeed, the package is a self-described "hack" designed "mainly to show off the char table stuff".

Is there a moral to this story? Maybe we shouldn't judge the quality of code without knowing its etiology.


By the way, the manual and the source code commentary employ strikingly different examples to illustrate a common use of ROT13. According to the manual,

Mailing list messages that might offend or annoy some readers are
sometimes encoded in a simple code called “rot13”—so named because it
rotates the alphabet by 13 letters.  This code is not for secrecy, as it
provides none; rather, it enables those who wish to to avoid seeing the
real text of the message.  For example, a review of a film might use
rot13 to hide important plot points.

whereas the source code says that

;; ROT13 encryption is sometimes used on USENET as a read-at-your-
;; own-risk wrapper for material some might consider offensive, such as
;; ethnic humor.
Wilfred commented 6 years ago

Great write-up, thank you :)

Yeah, so functions in suggest-functions need to be pure, and rot13 isn't pure -- it messes with the current buffer. It's safe to add rot13-string to suggest-functions, but that only rotates the text by exactly 13 letters. You need to rotate "hal" by 1 letter to get "ibm".

Is there anything actionable here? I'm hesitant to add rot13-string because I'm struggling to imagine any situation in which users are actually looking for its behaviour.

nickdrozd commented 6 years ago

This was quite an issue, wasn't it? It was New Year's Day, I had blasted off into outer space, and because I had recently started using Helpful, a simple Suggest debugging session turned into an archaeological excavation.

There are three parts here:

  1. A request for adding a some rot-N function, should one exist in any of the "standard libraries". I guess there isn't one, so nothing to be done there.

  2. A bug report / general observation as to why adding rot13 to suggest-functions didn't work. This is actually not true anymore; I just tried adding it and it ran fine:

;; Inputs (one per line):
"abc"

;; Desired output:
"nop"

;; Suggestions:

(rot13 "abc") ;=> "nop"
(apply #'rot13 (list "abc")) ;=> "nop"
(apply #'rot13 (cons "abc" nil)) ;=> "nop"
(apply #'rot13 (-repeat 2 "abc")) ;=> "nop"
(apply #'rot13 (make-list 1 "abc")) ;=> "nop"

I don't know if something changed with Suggest or if something changed from Emacs 25.2 to 26.1 to make this happen.

  1. Some musings on the motivations behind software development. These are obviously irrelevant :)
Wilfred commented 6 years ago

Looks like rot13 just calls rot13-string on Emacs 26.1.

Anyway, I'm going to close this as I don't think there's anything to do :)