透過的な「パススルー」関数ラッパーを作成する方法は?


10

「透過的な「パススルー」関数ラッパー」とは、wrapperすべての引数を他の関数に渡した結果を返す関数のことwrappeeです。

Emacs Lispではこれはどのように行われますか?

NB:理想的なwrapper機能があるとらわれない程度wrappee関数のシグネチャ。つまり、wrappeeの引数の数、位置、名前などは何も知りません。最初に呼び出されたwrappeeかのように、すべての引数をに渡しますwrappee。(必要がへの呼び出しを置き換えるためにコールスタックを台無しに、しかし、ありませんwrapperへの呼び出しでwrappee。)

私は私の質問に部分的な回答を投稿しました:

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

これは、wrappeeがインタラクティブでない場合にのみ機能します。どうやら、インタラクティブ関数が引数を取得する方法は、(&rest args)呪文の対象とは異なる「チャネル」を表します。したがって、私がまだ必要としているのは、が対話型の関数である場合のwrappee(&rest args)署名に等しく不可知論的な対応物です。wrappee

(この質問は、この前の質問で説明されている問題によって動機付けられました。)


私が求めていることをさらに明確にする必要がある場合のために、以下にいくつかの例を示し、PythonとJavaScriptで同等のものを示します。

Pythonでは、このようなラッパーを実装するいくつかの標準的な方法を以下に示します。

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

# or

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

(ここで*argsは「すべての位置引数」を**kwargs表し、「すべてのキーワード引数」を表します。)

同等のJavaScriptは次のようになります。

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

// or

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

記録のために、私はこの質問が複数の引数を持つ関数にmapcarを適用する方法の複製であることには同意しません。2つの質問が私には明らかに異なって見えるので、私はその理由を説明するのに途方に暮れています。「リンゴがオレンジと同等と見なされるべきではない理由を説明してください」という質問のようなものです。ただの質問は非常にクレイジーであり、質問する人を満足させる答えを思い付くことができるかどうか疑わしいものです。


アドバイス/アドバイスの使用を検討しましたか?
wasamasa

@wasamasa:いいえ、その上、この質問にアドバイスやアドバイスがどのように適用されるかはわかりません。いずれにせよ、私adviceはかなり問題があるので、それを避けたいと思います。実際、この質問の動機は、助言された関数で私が持っている他の方法では扱いにくい問題の解決策を見つけようとすることでした...
kjo

1
@wasamasa:アドバイスは同じ問題を提示します。引数をどのように処理するかを指定できますが、インタラクティブにするには、引数の提供方法を​​指定する必要があります。IOW、interactiveスペックを提供する必要があります。
ドリュー

1
私が言ったことは、元のインタラクティブ関数にその前後に何をしたいかをアドバイスすることです。そうすれば、インタラクティブ仕様について心配する必要はありません。
wasamasa 2016年

2
@wasamasa:はい、でも違います。インタラクティブかどうかにかかわらず、アドバイスは常に特定の機能に対するものです。そして、それがコマンドである場合、問題はありません-そのインタラクティブな動作は、アドバイスがインタラクティブな動作を再定義しない限り、アドバイスされたコマンドに継承されます。この質問は、特定の機能/コマンドではなく、任意の機能/コマンドに関するものです。
ドリュー

回答:


11

もちろんinteractive仕様も含めて可能です。ここではelispを扱っています!(Lispは、最も重要な構成要素がリストである言語です。呼び出し可能なフォームは単なるリストです。したがって、好みに応じて構成できます。)

アプリケーション:一部の機能にいくつかの機能を自動的に追加したい。拡張機能には新しい名前を付けて、defadvice適用されないようにする必要があります。

まず、あなたの目的にぴったり合うバージョン。fsetシンボルの関数セル()にwrapper必要なすべての情報を設定しwrappee、追加のものを追加します。

どちらのwrappee定義でも機能します。の最初のバージョンwrappeeはインタラクティブで、2番目のバージョンはインタラクティブではありません。

(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."))))

ただし、拡張関数を構成するマクロを定義する方が便利です。これにより、後で関数名を指定することもできます。(自動化バージョンに適しています。)

以下のコードを実行した後、wrapper-interactive対話式およびwrapper-non-interactive非対話式で呼び出すことができます。

(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")

現時点では、宣言フォームを転送する方法はまだ見つかりませんでしたが、それも可能であるはずです。


2
うーん、誰かがこの回答に反対票を投じました。私は実際にはスコアを気にしませんが、気になるのは回答を反対投票する理由です。反対票を投じる場合は、コメントも残してください!これは私に答えを改善する機会を与えるでしょう。
トビアス

確かではありませんが、これを使用してパッケージのコードを読んでいる人は誰でもWTFにアクセスできます。ほとんどの場合、より賢明なオプションは、それを処理して手動でラッピングを実行する関数を作成することです(applyを使用するか、インタラクティブ仕様の一部を書き換えることによって)
wasamasa

2
@wasamasa部分的に同意します。それにもかかわらず、自動計測が義務付けられている場合があります。例はedebugです。さらに、- interactive仕様が関数本体よりもかなり大きい関数があります。このような場合、interactive仕様の書き換えは非常に退屈な作業になる可能性があります。質問と回答は、必要な原則に対応しています。
トビアス2016年

1
個人的には、この回答は、質問の範囲だけでなく、マクロの自然な適用、およびdefunからマクロにどのように移行するかを示しているため、非常に有益です。ありがとう!
gsl

11

で非常によく似た問題を解決する必要があったnadvice.elので、これが解決策です(これはnadvice.elのコードの一部を使用します)。

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

これまでに投稿された他のソリューションと比較して、これwrappeeは別のインタラクティブな仕様で再定義された場合に正しく機能するという利点があります(つまり、古い仕様を使い続けません)。

もちろん、ラッパーを本当に透過的にしたい場合は、より簡単に行うことができます。

(defalias 'wrapper #'wrappee)

これは、実行時にラップするものを見つけるラッパーを定義できる唯一の回答です。たとえば、実行時に検索されるコマンドによって定義されたアクションを実行するショートカットを追加したいとします。advice-eval-interactive-specここで提案されているように使用すると、その動的ラッパーに対応するインタラクティブな仕様を構築できます。
Igor Bukanov 2016

called-interactively-p返品tは可能wrappeeですか?ありfuncall-interactivelyませんがありませんapply-interactively
クレメラ2017

1
@compunaut:もちろん、必要に(apply #'funcall-interactively #'wrappee args)応じて行うことができます。ただし、関数がインタラクティブに呼び出される場合にのみ行う必要があります(apply (if (called-interactively-p 'any) #'funcall-interactively #'funcall) #'wrappee args)
ステファン

ハ、ありがとう!どういうわけか私の箱の外を考えることができませんでした。
クレメラ2017

1

編集:トバイアスの答えは、ラップされた関数の正確なインタラクティブなフォームとdocstringを取得するため、これよりも優れています。


Aaron Harrisとkjoの答えを組み合わせると、次のようなものを使用できます。

(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)))))

使用法:

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

次のいずれかでラッパーを呼び出します。

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

M-x wrapper-func

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.