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.


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.


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)

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.