7

Peter Norvig の Paradigms of Artificial Intelligence Programming を読んでいて、自分では解決できない問題に遭遇しました (これは Lisp の紹介です)。この問題は実際には非常に小さな問題ですが、私の小さな脳が解決できる問題ではないことは明らかです。

関数の値がラムダの場合、その関数をリストの最初の要素として使用するとエラーになるのはなぜですか。例えば:

舌足らずの発音:

(defun some-func ()
  #'(lambda (x) x))

;; At REPL
;; Does not work
> ((some-func) 1)
;; Does work
> ((lambda (x) x) 1)
;; Also works
> (funcall (some-func) 1)

それが理にかなっていることを願っています!

4

4 に答える 4

6

これは良い質問で、Common Lisp がかなり混乱する可能性がある質問です。問題は、歴史的な理由から、Common Lisp には 2 つの名前空間があります。1 つは関数用で、もう 1 つは値用です。これを実現するために、関数適用の先頭位置とそれ以外の評価規則が 2 つあります。1 つ目はシンボルを関数名として評価し、2 つ目はシンボルを変数参照として評価します。明らかに、値が実際に関数である場合があります。たとえば、mapcar関数を作成する場合は、次のようにします。

(defun my-mapcar (f l)
  (if (null l)
    '()
    (cons (f (car l)) (my-mapcar f (cdr l)))))

しかし、これは機能しませんf。不明な関数であると文句を言うでしょう。このような場合のためにfuncall、 と呼ばれる特別な関数があり、関数と関数の引数を受け取り、通常どおり関数を適用します。funcallは単純な関数であるため、その引数はすべて通常どおり (値として) 評価されます。したがって、上記はそれを使用して修正する必要があります。

(defun my-mapcar (f l)
  (if (null l)
    '()
    (cons (funcall f (car l)) (my-mapcar f (cdr l)))))

あなたがおそらく今疑っているように、何かを値としてではなく関数として評価したいというミラーケースがあります。たとえば、これは機能しません。

(my-mapcar 1+ '(1 2 3))

1+関数ではなく変数を参照するためです。これらの場合のために、functionその内容を関数として評価し、値として返す特別なフォームが呼び出されます。

(my-mapcar (function 1+) '(1 2 3))

と省略できます#'

(my-mapcar #'1+ '(1 2 3))

この話はこれで終わりではありません。例をいくつか挙げます。

  • 場合によっては、引用符で囲まれた単純な名前が関数として機能する'1+ことがあり#'ます。

  • 同様のハックを使用できますlambda-- したがって、使用できます(my-mapcar '(lambda (x) (1+ x)) '(1 2 3))。実際、(list 'lambda '(x) '(1+ x))これはさらに悪い (そして IIRC、非移植性) を使用できますが、使用は(lambda (x) (1+ x))暗黙的に a でラップされているため機能します(フォームをマクロとして#'展開してみてください) 。lambdaそして、あなたはそれを見るでしょう)。関連するハックにより、lambda式を関数アプリケーションの先頭として使用することができます (これは、あなたが試したことの 1 つです)。

  • letなどはローカル値をバインドします。場合によっては、代わりにローカル関数をバインドする必要があります。このために、新しいバインド構造がありますfletlabels

これらすべてが奇妙に見えたり、過度に複雑に見えたりする場合、それはあなただけではありません。Common Lisp と Scheme の主な違いの 1 つです。(この違いは、両方の言語で共通のイディオムの変更につながります。Scheme コードは、Common Lisp コードよりもはるかに頻繁に高階関数を使用する傾向があります。この種の宗教的な質問でいつものように、一部の人々は、CL が行うことを支持して主張し、次のように主張します。高階関数は紛らわしいので、明示的なコード内リマインダーが好きです。)

于 2011-05-01T19:33:12.813 に答える
4

((lambda (x) ...) ...)エバリュエーター ルールでハードコードされた特殊なケースです。フォームの最初の要素を評価し、結果を一般的なケースとして使用するのではありません。funcall他の形式を評価した結果である関数を使用またはapply呼び出す必要があるのは普通のことです。

于 2011-05-01T19:21:32.757 に答える
2

Common Lisp がラムダを返す関数を式の最初の項目にすることを許可しない理由は、Lisp-1 と Lisp-2の違いに関係しています。

Common Lisp が((some-func) 1)と同等であることを許可した場合、 require ではなく(funcall (some-func) 1)say も許可しないことは矛盾していると見なされる可能性があります。 (let ((f #'some-func)) (f 1))(funcall f 1)

于 2011-05-01T19:59:49.947 に答える
1

そのような形式をサポートしないことには、実際には非常に正当な理由があります: それらはあいまいです。Common Lisp での関数名の定義方法を考えてみましょう。

関数名 n . 1. (環境内で)その環境内の関数名前であるシンボルまたはリスト 。2.シンボルまたはリスト(setf symbol) (setf symbol)

これを考えると、次のようなフォームはどのように動作する必要がありますか?

((setf car) 10 x)

このセットxの car を valueに設定する必要があります10か、それともフォームを実行し(エラーになります)、その戻り値を引数and(setf car)で呼び出す関数として使用しようとする必要がありますか? Common Lisp はどちらの振る舞いも規定していません。結局のところ、私が見る限り、規格に適合する実装がより広い範囲の演算子名をサポートするために有効なフォームの定義を拡張することを妨げるものは標準にはありません (したがって、特別なケースの setf 関数はここでも実際には役に立ちません)。10x

于 2011-05-02T17:45:07.840 に答える