Jak manipulować listą argumentów w nadvice.el?

12

W odpowiedzi na kolejne pytanie dotyczące nowego systemu porad :

W starym stylu advice.elmożna było manipulować poszczególnymi elementami listy argumentów zalecanej funkcji, nie czyniąc żadnych twierdzeń dotyczących tych elementów, które nie są tak zmanipulowane. Na przykład następujące porady:

(defadvice ansi-term (around prompt-for-name last)
  (let ((name (read-from-minibuffer "Tag: ")))
    (and (not (string= name ""))
         (ad-set-arg 1 (concat "Term: " name)))
    ad-do-it))

pozwala na (opcjonalne) dostarczenie argumentu nazwy bufora do ansi-termwywołania, podczas gdy ansi-termnadal uzyska swój pierwszy argument, pytając zgodnie z własną interaktywną formą.

(W celu późniejszego wykorzystania ansi-termpodpis jest (PROGRAM &optional BUFFER-NAME), a jego interaktywna forma monituje o PROGRAM z kilkoma możliwymi ustawieniami domyślnymi, ale nie robi nic w odniesieniu do NAZWY BUFORA.)

Nie jestem pewien, czy jest to możliwe w nadvice.el. Jeśli tak, nie jestem pewien, jak to zrobić. Znalazłem kilka sposobów na zastąpienie listy argumentów zalecanej funkcji.

Na przykład z * info * (elisp) Kombinatory porad :

`:filter-args'
 Call FUNCTION first and use the result (which should be a list) as
 the new arguments to pass to the old function.  More specifically,
 the composition of the two functions behaves like:
      (lambda (&rest r) (apply OLDFUN (funcall FUNCTION r)))

Inne kombinatory zapewniają podobne możliwości, a wspólny wątek wśród nich polega na tym, że chociaż lista argumentów funkcji może zostać zastąpiona, obcięta, rozszerzona i in., Nie ma widocznego sposobu, aby porady funkcji modyfikowały argument na danej pozycji na liście bez twierdząc cokolwiek na temat reszty .

W omawianym przypadku autor porady nie wydaje ansi-termtylko nazwy bufora, ponieważ nie jest możliwe zbudowanie listy, która ma wartość na pozycji 1, ale nic, nawet nilna pozycji 0. W ogólnym przypadku autorowi porady wydaje się niemożliwe arbitralne modyfikowanie argumentów poza pozycją 0.

Wydaje się to niefortunne, ponieważ w celu uzyskania podobnego efektu konieczne jest skopiowanie i wklejenie kodu: w szczególności albo mogę skopiować ansi-terminteraktywną formę i rozszerzyć ją według własnego gustu, albo mogę ansi-termcałkowicie skopiować i rozszerzyć go w podobny sposób. W obu przypadkach muszę teraz przedefiniować część dystrybucji Emacs Lisp w moim pliku init, co wydaje mi się niepożądane ze względu zarówno na trwałość, jak i na estetykę.

Moje pytanie brzmi zatem: czy można tego rodzaju manipulowanie listą argumentów można wykonać nadvice.el? Jeśli tak to jak?

Aaron Miller
źródło
3
Dlaczego nie zdefiniujesz własnego interaktywnego polecenia oprócz ansi-term? Myślę, że to najlepsze rozwiązanie tutaj.
lunaryorn
1
Oczywiście nic nie powstrzymuje mnie przed zrobieniem tego, ale wymagałoby to zastąpienia lepszej części dziesięciolecia pamięci mięśniowej, której chciałbym uniknąć, gdybym mógł.
Aaron Miller

Odpowiedzi:

5

Wydaje się to niefortunne, ponieważ w celu uzyskania podobnego efektu konieczne jest skopiowanie i wklejenie kodu: [...] Mogę skopiować ansi-terminteraktywny formularz

Przeciwnie, myślę, że dobrym pomysłem byłoby skopiowanie i wklejenie interaktywnej formy zalecanej funkcji, nawet jeśli tak naprawdę nie musisz tego robić tutaj.

Czytam ci pytanie od góry do dołu. Kiedy dotarłem do bloku kodu, zgadłem, że twoja rada prawdopodobnie zmienia nazwę bufora. Ale nie wiedziałem, dopóki nie podałeś podpisu jako komentarza.

W omawianym przypadku autor porady nie wydaje ansi-termtylko nazwy bufora, ponieważ nie jest możliwe zbudowanie listy, która ma wartość na pozycji 1, ale nic, nawet nilna pozycji 0.

Rzeczywiście nic nie jest niczym innym jak niczym. :-) Ale tutaj nie ma to większego znaczenia.

Jak widać w cytowanej dokumentacji, wartość zwrócona przez poradę jest używana jako argumenty zalecanej funkcji. Zwracana wartość musi być listą wszystkich argumentów, a nie tylko tych, które uległy zmianie.

Pozostając jak najbliżej starej porady, oto co powinieneś zrobić, używając nadvice:

(defun ansi-term--tag-buffer (args)
  ;; As npostavs pointed out we also have to make sure the list is
  ;; two elements long.  Which makes this approach even more undesirable.
  (when (= (length args) 1)
    (setq args (nconc args (list nil))))
  (let ((name (read-from-minibuffer "Tag: ")))
    (and (not (string= name ""))
         (setf (nth 1 args) (concat "Term: " name))))
  args)

(advice-add 'ansi-term :filter-args 'ansi-term--tag-buffer)

Ale radzę zamiast tego zdefiniować takie porady:

(defun ansi-term--tag-buffer (program &optional buffer-name)
  (list program
        (let ((tag (read-from-minibuffer "Tag: ")))
          (if (string= tag "")
              buffer-name
            (concat "Term: " tag)))))

Ten wariant w rzeczywistości jest oczywisty.

Tarsjusz
źródło
W przypadku pierwszego wariantu musisz rozszerzyć argslistę w przypadku połączenia typu „ (ansi-term "foo")inaczej”, w przeciwnym razie (setf (nth 1 args)...pojawiłby się błąd.
npostavs
Tak, masz rację. Kolejny powód do korzystania z drugiego wariantu - pierwszy ma błąd ;-) Pozwala, dla celów demonstracyjnych, założyć, że buffer-namejest to obowiązkowe.
Tarsius
„Przeciwnie, myślę, że dobrym pomysłem byłoby skopiowanie i wklejenie interaktywnej formy zalecanej funkcji” - dlaczego tak jest? Wklejanie i kopiowanie kodu jest złym pomysłem praktycznie w każdym innym przypadku; dlaczego nie tutaj?
Aaron Miller
Właściwie nie uważam, że „kopiuj-wklej” jest właściwym terminem w tym przypadku, po prostu użyłem go, ponieważ to zrobiłeś. Ale nawet jeśli użycie tego terminu byłoby właściwe, to „nie kopiuj-wklej” jest po prostu heurystyką, a nie absolutną regułą. Inne heurystyki, które, jak sądzę , mają tutaj zastosowanie, to „nadaj sensowne nazwy zmiennym i argumentom” oraz „gdy masz wybór między komplikowaniem czegoś lub byciem gadatliwym, idź z gadatliwym”.
tarsius
1
Um, właściwie to wciąż jest zepsute, :filter-argsrada otrzymuje pojedynczy argument, który jest listą argumentów zalecanej funkcji, więc pierwszy wariant powinien spaść, &resta drugi wariant musiałby użyć jakiegoś konstruktu destrukcyjnego, aby uzyskać ładne nazwy.
npostavs
3

Oto jak bym to zrobił:

(defun my-ansi-term-prompt-for-name (orig-fun program
                                     &optional buffer-name &rest args)
  (apply orig-fun program
         (or buffer-name
             (let ((name (read-string "Tag: ")))
               (and (> (length name) 0)
                    (concat "Term: " name))))
         args))
(advice-add 'ansi-term :around #'my-ansi-term-prompt-for-name)

kiedy to ja przedstawiłem :filter-args, osobiście uważam, że rzadko jest to wygodne.

Stefan
źródło