レキシカル環境からカスタム関数を取得できるようにする

私はレキシカルバインディングを使用するように自分のコードを変換しようとしています。

ユーザがカスタマイズ可能なリスト( template-replace)からラムダ関数( replacer-inner へのlet-bound)を呼び出す関数( format-template マッチ文字列に基づいた関数の関数(-functions )です。 replacer-inner関数は引数を取らずに文字列を返しますが、変数 foo bar および/または bazlist の引数の一部として format-template に渡されます。

(setq lexical-binding t)

(defcustom template-replace-functions
  '(("email"
     (lambda() user-mail-address)
    ("apples"
     (lambda ()
       (cond ((= foo 1)
              "one")
             ((= foo 2)
              "two")
             ((= foo 3)
              "three")
             (t "four"))))
    ("bananas"
     (lambda ()
       (if (eq bar 'move)
           "movement" "sit still")))))
  "Association list of replacement functions.

For each STRING, the corresponding FUNCTION is called with no
arguments and must return a string."
  :type '(repeat (group string function))
  :group 'spice-girls)

(defun format-template (list)
  (let* ((foo (nth 0 list))
         (bar (nth 1 list))
         (baz (nth 2 list))
         template
         (replacer-outer
          (lambda ()
            (replace-regexp-in-string
             "\${\([^\s\t\n]*?\)}"
             (lambda (match)
               (let* ((key (match-string 1 match))
                      (replacer-inner
                       (cadr (assoc key template-replace-functions))))
                 (if (and replacer-inner
                          (stringp (funcall replacer-inner)))
                     (funcall replacer-inner) "")))
             template t t))))
    (setq template
          ...)
    (if template (funcall replacer-outer))))

たとえば、 list には barbaz にレットバインドされた3つの要素が含まれています。 match-stringのキーは、 template-replace-functions の関数に関連する "bananas" 内部には:

(lambda ()
       (if (eq bar 'move)
           "movement" "sit still"))

この時点で、上記のラムダ関数は、動的バインディングを使っても問題ないが、字句バインディングを使ってうまくいくわけではない bar を知る必要があります。

私の質問は、lambda関数が foo bar のlet-bound値から取る replacer-inner baz

replace-inner を含む replace-outer 関数は、実際の関数の2つの場所のいずれかから呼び出されるため、何かを混乱させるだけです。私はここでインラインで書くことができたかもしれませんが、問題に追加する場合は、この方法でインクルードしました。)

編集:

途中で...

(setq lexical-binding t
  foo 0
  bar 0)

(setq inline-fun-1
      '(lambda ()
         (setq return
               (if (eq foo 1)
                   "Pass" "Fail"))))

(defmacro lex-fun ()
  `(let* ((foo 1)
          (bar 1)
          return)
     (funcall ,inline-fun-1)))

(lex-fun)    ; -> "Pass"

(defun inline-fun-2 ()
  (setq return
        (if (eq bar 1)
            "Pass" "Fail")))

(defmacro lex-fun ()
  `(let* ((foo 1)
          (bar 1)
          return)
     (funcall ,inline-fun-2)))

(lex-fun)    ; -> Lisp error: (void-variable inline-fun-2)

したがって、マクロ内のラムダ関数を拡張すると動作するように見えますが、名前付き関数ではありません。しかし、私はユーザーのラムダまたは名前付きの関数を許可したいです。これを回避するには?

3
追加された 編集された
ビュー: 2
@rnkn Emacsにとっては関数ではありません。それは単なるリストであり、いくつかの状況では暗黙的に変換されます。しかし注目すべき違いがあります。ラムダリストは周囲のバインディングを閉じることができず、決してバイトコンパイルされません。彼らは歴史的な理由から純粋に存在するのであって、良いアイデアではありません。今日のEmacs Lispコードでは、この機能に頼るべきではありません。
追加された 著者 Ishmaeel,
@Stefanインタプリタ(Emacs)がそれを関数と見なし、ユーザがそれを関数と見なして、実際に言えば、それは関数ではありませんか? Emacsはそれを関数として見るのに十分なほど素晴らしいものではありませんか?
追加された 著者 Iker Jimenez,
あなたの(lambda()user-mail-address)は正式には関数ではありません。代わりに、それは3つの要素のリストです。これは、関数のソースコードと同じように見えます。また、Emacsは、そのリストを funcall に渡すと、他のものに切り替えることができます。しかし、あなたが本当にそれが関数であると言いたいのであれば、 `((" email "、(lambda()user-mail-address)...と書く必要があります。
追加された 著者 sds,

1 答え

短い答え:できません。これが、この種のバインディングが「字句」と呼ばれる理由です。変数が定義域から逃げるのを防ぎます。

この問題に対処する方法の1つは、バナナがバナナの値を抽出する方法を知っている「バナナ」関数にデータ構造を渡すことができたということです。私。

(defcustom template-replace-functions
  '(("bananas" . (lambda (env) 
                   (let ((x (gethash "x" env "")))
                     (do-replacements-with x))))
    ...)))

これは、 env がハッシュテーブルであることを前提としています。

さて、 eq ではなく eql を使用する方が良いでしょう。私はEmacs Lispがどのようなタイプのデータでも eq の振る舞いをどのように保証しているとは思わない(例えば、同じ名前のインターンシンボルも< code> eq )。

1
追加された
私はeqに関する最後の発言に投票します。 eqは数値以外のすべてのデータ型とまったく同じであることをご存知ですか?また、2つの記号のないものは、同じ名前を共有しても同じではないため、式の下で等しくてはいけません。
追加された 著者 Ishmaeel,
すべての点で、Emacs LispはCommon Lispではありません。 Emacs LispがCのようになったときにリストが削除される可能性があるので、リストを避けることもできます。最後の段落を答えから削除してください。それは混乱し、誤解を招く。途中でインターンされたシンボルはeqの下では等しい。なぜなら、Emacs Lispには単一のオブジェクト配列しかないからである。
追加された 著者 Ishmaeel,
@ wvxvwそれはどのような議論ですか? Haskellの型システムの背後にも理由がありますが、Emacs LispはHaskellにもなりません。 Emacs LispはCommon Lispではないので、それを乗り越えて、Common Lispの動作に基づいてEmacs Lispの助言を与えないでください。
追加された 著者 Ishmaeel,
@wvxvwまあ、何でも。 Emacs Lispは最近のCLとは多少異なります(C ++は歴史的な関係にもかかわらずCから来ています).eqについてのあなたの主張は単純に前者には当てはまりませんが、これ以上検討する必要はありません。しかし、CLについての回答が投票された場合、驚かないでください。
追加された 著者 Ishmaeel,
@ lunaryornもしあなたが私が書いたことを読んだら、 "私は同じ名前を持つ内部の記号でもeqの下で等しくなければならないと確信していません"。私は無神論者のシンボルについては何も言いませんでした。 eq の使い方が気にならないのは、これが決してうまく行かないCommon Lispによるものです。私はEmacs Lispの実装の詳細を知らないので、私の側の迷信かもしれません。しかし、私はまだそれを避けるだろう:あなたは決して知らない、多分将来のEmacs LispはCommon Lispのようなものだろうか?
追加された 著者 Yann Trevin,
@lunaryorn Common Lispはそれを恣意的に行いません。それには理由があります。速度/メモリの最適化です。誰かが知っているのは、ある日、Emacs Lispはメモリの管理方法に最適化を追加しようとします。それが問題になるかもしれません。
追加された 著者 Yann Trevin,
@rnknまあ、それはちょうど幼稚園です。もちろん、あなたができることの制限ではありません。あなたは確かにそれを行うことができます。あなたが望むように動作するかどうかは、別の問題です。
追加された 著者 Yann Trevin,
@lunaryorn Emacs Lispは、歴史的にはCommon Lispに非常に近い親族です。これには多くの同様の懸念があり、ハスケルとは非常に異なっています。しかし、これはCommon Lispの動作ではありません。これは、ランタイムのデザイナーがいつか会うべきものです。これはまた、特殊な関数を使ってJavaの文字列を比較しなければならない理由、Cのリテラル文字列へのポインタを比較するのは良い考えではありません。そして、もし彼らが今でなければ、彼らは将来になるかもしれない。
追加された 著者 Yann Trevin,
@ lunaryorn私はあまり気にすることはできませんでした。
追加された 著者 Yann Trevin,
私はできるから、する!私はマクロを使って部分的な解法を含めるように質問を編集しましたが、それでも完全な解はありません。
追加された 著者 Iker Jimenez,
カスタム関数に env を渡す際の問題は、ユーザが foo の値を必ずしも知っているわけではないので、 code> bar と baz を使用すると、ユーザーフレンドリーではないようです。しかし、これは私に、未知の語彙的環境をユーザに伝えることを再考させました。悪いことがあります。
追加された 著者 Iker Jimenez,