Como escrever um invólucro transparente de função "passagem"?

10

O que quero dizer com um "wrapper de função" transparente "de passagem" "é uma função, vamos chamá-lo wrapper, que retorna o resultado de passar todo o seu argumento para alguma outra função, vamos chamá-lo wrappee.

Como isso é feito no Emacs Lisp?

NB: O ideal wrapperfunção é agnóstico sobre a wrappeeassinatura de função; isto é, não sabe nada sobre o número, posições, nomes, etc. dos wrappeeargumentos de; apenas passa todos os seus argumentos wrappee, como se wrappeetivesse sido o originalmente chamado. (Porém, não é necessário mexer na pilha de chamadas para substituí-la wrapperpor uma chamada para wrappee.)

Publiquei uma resposta parcial à minha pergunta:

(defun wrapper (&rest args) (apply 'wrappee args))

Isso funciona apenas quando nãowrappee é interativo. Aparentemente, a maneira como as funções interativas obtêm seus argumentos representa um "canal" diferente do que é coberto pelo encantamento. O que eu ainda preciso, portanto, é uma contrapartida igualmente magnética da assinatura para o caso em que é uma função interativa .(&rest args)wrappee(&rest args)wrappee

(Esta pergunta foi motivada por um problema descrito nesta pergunta anterior .)


Caso seja necessário um esclarecimento adicional sobre o que estou pedindo, abaixo estão alguns exemplos, mostrando os equivalentes em Python e JavaScript do que estou procurando.

No Python, algumas maneiras padrão de implementar esse wrapper são mostradas abaixo:

def wrapper(*args, **kwargs):
    return wrappee(*args, **kwargs)

# or

wrapper = lambda *args, **kwargs: wrappee(*args, **kwargs)

(Aqui *argssignifica "todos os argumentos posicionais" e **kwargs"todos os argumentos das palavras-chave".)

O equivalente a JavaScript seria algo como isto:

function wrapper () { return wrappee.apply(this, arguments); }

// or

wrapper = function () { return wrappee.apply(this, arguments); }

Para o registro, eu discordo que esta questão seja uma duplicata de Como aplicar o mapcar a uma função com vários argumentos . Não consigo explicar por que, uma vez que as duas perguntas parecem tão obviamente diferentes para mim. É como ser perguntado "explique por que uma maçã não deve ser considerada equivalente a uma laranja". A mera pergunta é tão louca que alguém duvida que possa encontrar uma resposta que satisfaça a pessoa que a pergunta.

kjo
fonte
Você considerou usar conselhos / nadvice?
wasamasa
@wasamasa: não, e, além disso, não vejo como o conselho / nadvice se aplicaria a essa pergunta. De qualquer forma, acho as advicecoisas suficientemente problemáticas para que eu prefira ficar longe disso. Na verdade, a motivação para esta questão estava tentando encontrar uma solução para um problema de outra forma intratável que tenho com uma função adviced ...
KJo
11
@wasamasa: Conselho apresenta o mesmo problema. Você pode dizer o que fazer com qualquer um dos argumentos, mas para torná-lo interativo, você precisa especificar como os argumentos devem ser fornecidos. IOW, você precisa fornecer uma interactiveespecificação.
Tirou
11
O que eu quis dizer é aconselhar a função interativa original a fazer o que você quer que aconteça antes e depois dela, para que você não precise se preocupar com as especificações interativas.
wasamasa
2
@wasamasa: Sim, mas isso é diferente. O conselho é sempre para uma função específica , interativa ou não. E se for um comando, não haverá problema - seu comportamento interativo é herdado para o comando recomendado (a menos que o conselho redefina o comportamento interativo). Esta pergunta é sobre uma função / comando arbitrário , não sobre um particular.
Tirou

Respostas:

11

Obviamente, é possível incluir a interactiveespecificação. Estamos lidando aqui com elisp ! (Lisp é a linguagem na qual as construções mais importantes são as listas. Os formulários que podem ser chamados são apenas listas. Assim, você pode construí-los conforme desejar.)

Aplicativo: você deseja adicionar algumas funcionalidades a algumas funções automaticamente. As funções estendidas devem receber novos nomes para que isso defadvicenão seja aplicável.

Primeiro, uma versão que se encaixa exatamente no seu propósito. Definimos a célula de função ( fset) do símbolo wrappercom todas as informações necessárias wrappeee adicionamos nosso material extra.

Funciona para as duas wrappeedefinições. A primeira versão do wrappeeé interativa e a segunda não.

(defun wrappee (num str)
  "Nontrivial wrappee."
  (interactive "nNumber:\nsString:")
  (message "The number is %d.\nThe string is \"%s\"." num str))

(defun wrappee (num str)
  "Noninteractive wrappee."
  (message "The number is %d.\nThe string is \"%s\"." num str))

(fset 'wrapper (list 'lambda
             '(&rest args)
             (concat (documentation 'wrappee t) "\n Wrapper does something more.")
             (interactive-form 'wrappee)
             '(prog1 (apply 'wrappee args)
            (message "Wrapper does something more."))))

Mas é mais conveniente definir uma macro que construa as funções estendidas. Com isso, podemos até especificar os nomes das funções posteriormente. (Bom para uma versão automatizada.)

Depois de executar o código abaixo, você pode ligar de forma wrapper-interactiveinterativa e wrapper-non-interactivenão interativa.

(defmacro make-wrapper (wrappee wrapper)
  "Create a WRAPPER (a symbol) for WRAPPEE (also a symbol)."
  (let ((arglist (make-symbol "arglist")))
  `(defun ,wrapper (&rest ,arglist)
     ,(concat (documentation wrappee) "\n But I do something more.")
     ,(interactive-form wrappee)
     (prog1 (apply (quote ,wrappee) ,arglist)
       (message "Wrapper %S does something more." (quote ,wrapper))))))

(defun wrappee-interactive (num str)
  "That is the doc string of wrappee-interactive."
  (interactive "nNumber:\nsString:")
  (message "The number is %d.\nThe string is \"%s\"." num str))

(defun wrappee-non-interactive (format &rest arglist)
  "That is the doc string of wrappee-non-interactive."
  (apply 'message format arglist))

(make-wrapper wrappee-interactive wrapper-interactive)
(make-wrapper wrappee-non-interactive wrapper-non-interactive)
;; test of the non-interactive part:
(wrapper-non-interactive "Number: %d, String: %s" 1 "test")

Observe que, até o momento, ainda não havia uma maneira de transferir os formulários de declaração, mas isso também deveria ser possível.

Tobias
fonte
2
Alguém votou negativamente nesta resposta. Eu realmente não me importo com a pontuação, mas o que me interessa é o motivo da votação negativa da resposta. Se você fizer um voto negativo, também deixe um comentário! Isso me daria a chance de melhorar a resposta.
Tobias
Não sei ao certo, mas isso deve fazer com que qualquer pessoa que leia o código de um pacote usando ele vá para WTF. Na maioria dos casos a opção mais sensata é a de lidar com isso e escrever uma função fazendo o embrulho manualmente (seja com aplicar ou por reescrever partes da especificação interativa.
wasamasa
2
@wasamasa concordo parcialmente. No entanto, há casos em que a instrumentação automática é obrigatória. Um exemplo é edebug. Além disso, existem funções em que a interactiveespecificação-é consideravelmente maior que o corpo da função. Nesses casos, a reescrita da interactiveespecificação pode ser bastante entediante. A pergunta e a resposta abordam os princípios exigidos.
Tobias
11
Pessoalmente, acho essa resposta bastante instrutiva, não apenas no que diz respeito ao escopo da pergunta, mas também porque mostra uma aplicação natural de macros e como se passa de desafinada para macro. Obrigado!
GSL
11

Eu tive que resolver um problema muito semelhante nadvice.el, então aqui está uma solução (que usa parte do código do nadvice.el):

(defun wrapper (&rest args)
  (interactive (advice-eval-interactive-spec
                (cadr (interactive-form #'wrappee))))
  (apply #'wrappee args))

Comparado com as outras soluções postadas até agora, esta tem a vantagem de funcionar corretamente se wrappeefor redefinida com uma especificação interativa diferente (ou seja, não continuará usando a especificação antiga).

Obviamente, se você deseja que seu invólucro seja verdadeiramente transparente, você pode fazê-lo de maneira mais simples:

(defalias 'wrapper #'wrappee)
Stefan
fonte
Essa é a única resposta que permite definir um wrapper que encontre o que ele envolve no tempo de execução. Por exemplo, quero adicionar um atalho que execute uma ação definida por algum comando que é procurado em tempo de execução. Usando advice-eval-interactive-speccomo sugerido aqui, posso construir a especificação interativa que corresponde a esse invólucro dinâmico.
Igor Bukanov 14/08/16
É possível fazer called-interactively-pvoltar tem wrappee? Não há, funcall-interactivelymas nãoapply-interactively
clemera
11
@ Compunaut: Claro, você pode fazer (apply #'funcall-interactively #'wrappee args)se quiser. Mas você só deve fazer isso se a função for chamada interativamente, algo assim (apply (if (called-interactively-p 'any) #'funcall-interactively #'funcall) #'wrappee args).
Stefan
Ha obrigado! De alguma forma, não conseguia pensar fora da minha caixa.
Clemera
1

edit: A resposta de Tobias é mais agradável que essa, pois obtém a forma interativa precisa e a sequência de caracteres da função agrupada.


Combinando as respostas de Aaron Harris e kjo, você pode usar algo como:

(defmacro my-make-wrapper (fn &optional name)
  "Return a wrapper function for FN defined as symbol NAME."
  `(defalias ',(or (eval name)
                   (intern (concat "my-" (symbol-name (eval fn)) "-wrapper")))
     (lambda (&rest args)
       ,(format "Generic wrapper for %s."
                (if (symbolp (eval fn))
                    (concat "`" (symbol-name (eval fn)) "'")
                  fn))
       (interactive)
       (if (called-interactively-p 'any)
           (call-interactively ,fn)
         (apply ,fn args)))))

Uso:

(my-make-wrapper 'find-file 'wrapper-func)

Wrapper de chamada com um dos seguintes:

(wrapper-func "~/.emacs.d/init.el")

M-x wrapper-func

phils
fonte