Substituir uma função localmente, mas permitir chamadas para a função original

7

O recurso de aconselhamento permite modificar o comportamento de uma função globalmente. Uma definição de conselho pode fazer chamadas para a função original.

(defadvice foo
  (around foo-bar activate compile)
  "Always set `qux' to t when running `foo'."
  (let ((qux t))
    ad-do-it))

O clpacote fornece a fletmacro para substituir uma função localmente.

(defun foo ()
  "global")
(flet ((foo ()
          "local"))
  (some-code-that-calls-foo))

Isso não permite uma referência à foofunção original . E se a substituição local precisar chamar a função original?

(defun foo ()
  "global")
(flet ((foo ()
          (concat (foo) "+local")))
  ;; this will cause an infinite loop when (foo) is called
  (some-code-that-calls-foo))

Essa abordagem direta não funciona, por um bom motivo: (foo)é uma chamada recursiva à definição local.

Qual é a maneira não complicada de substituir localmente uma função, que permite chamar a função original a partir do código de substituição?

Aplicação: aplicar um patch em algum código existente, em um caso em fooque não deve ser recuperado globalmente, mas o código precisa chamar o original. Aqui está o exemplo mais recente que eu queria:

(defadvice TeX-master-file
  (around TeX-master-file-indirect-buffer activate compile)
  "Support indirect buffers."
  (flet ((buffer-file-name (&optional buffer)
           (<global buffer-file-name> (buffer-base-buffer buffer))))
      ad-do-it)))

Eu queria voltar a ligar buffer-file-namelocalmente e ligar para o original buffer-file-name. Ok, neste caso específico, há uma solução alternativa, que é usar a buffer-file-namevariável. Mas o ponto da minha pergunta aqui é a técnica geral. Como posso vincular uma função (aqui buffer-file-name) localmente, mas chamar a definição global da minha redefinição?

Isso é para o meu .emacs, que continuo trabalhando no Emacs 19.34, para que soluções que exijam o Emacs 24.4 estejam disponíveis. No entanto, prefiro soluções que lidem com a ligação lexical de maneira limpa - mas a correção de macacos é inerentemente relacionada à ligação dinâmica.

Gilles 'SO- parar de ser mau'
fonte
Eu não tenho certeza de que cl-letfestá disponível em emacs 24.3 e antes, mas aqui é um relacionados Q & A: emacs.stackexchange.com/a/16495/221
François Févotte

Respostas:

6

Armazene a função original (obtida com symbol-function) em uma variável local e use funcallpara chamar o objeto de função armazenado nessa variável. Pesado, mas funciona principalmente.

(defadvice TeX-master-file
  (around TeX-master-file-indirect-buffer activate compile)
  "Support indirect buffers."
  (let ((original-buffer-file-name (symbol-function 'buffer-file-name)))
    (flet ((buffer-file-name (&optional buffer)
             (funcall original-buffer-file-name (buffer-base-buffer buffer))))
        ad-do-it)))

Isso funciona principalmente , na medida em que faz o que deveria fazer, mas pode quebrar em raras circunstâncias. Como o Emacs Lisp não tem uma maneira primitiva de definir os slots de função dos símbolos localmente (apenas os slots variáveis, com let), fletdefine as ligações e as restaura unwind-protect. Se o código morrer porque o aninhamento de chamada excedeu max-lisp-eval-depthou se a ligação for modificada durante a execução desta função (por exemplo, porque você está depurando o aviso), é possível que o slot de função do símbolo não seja restaurado. Você pode tomar precauções contra a perda acidental de algumas funções.

Outro método é armazenar uma cópia da função. Isso tem a vantagem de que a função original nunca é perdida.

(fset 'original-buffer-file-name (symbol-function 'buffer-file-name))
(defadvice TeX-master-file
  (around TeX-master-file-indirect-buffer activate compile)
  "Support indirect buffers."
  (flet ((buffer-file-name (&optional buffer)
           (original-buffer-file-name (buffer-base-buffer buffer))))
      ad-do-it)))

Isso seria bom nesse caso específico, porque buffer-file-nameé uma função interna que provavelmente não será recuperada, mas não rastreará redefinições da função global (por exemplo, para adicionar conselhos a essa função).

Gilles 'SO- parar de ser mau'
fonte
Você tem uma nadvicereceita para o mesmo?
Kaushal Modi
11
@kaushalmodi Não. Só uso novos recursos se eles tiverem um benefício definido. Eu ainda uso 24.3 diariamente, por isso continuo com o mecanismo de aconselhamento que funciona desde o Emacs 19 (ou anterior?).
Gilles 'SO- stop be evil'