nptclのブログ

Common Lisp処理系nptの開発メモです。https://github.com/nptcl/npt

lambdaと記載できる場所はどこか

よく目にする(lambda ...)という表記はかなり特殊なものであり、 マクロを抜きにするならば、限定された場所でしか記述できません。
マクロのlambdaとリードマクロの#'があるため、 式の中で気軽に記載することができるのです。
マクロの例としてはこんな感じ。

(setq *call* (lambda () :hello))

このlambdaマクロは、次のように展開します。

(setq *call* #'(lambda () :hello))

さらにリードマクロ#'より、次のように展開されます。 (余計なことを言うと、リードマクロの展開タイミングはここじゃないかもしれない)

(setq *call* (function (lambda () :hello)))

最終的にspecial operatorのfunctionの引数に展開されるわけです。
じゃあfunctionの引数のlambdaはマクロ展開されないのか?
されません。
functionの引数はquoteの引数と同じように特殊な場所であり、 評価対象ではないのでマクロ展開されないのです。

ではマクロのlambdaを考えない場合は、lambdaと記載できる場所はどこでしょうか?
答えは下記の通り。

  • functionの引数
  • 関数呼び出し時の関数名

functionの引数は上記の例で説明しました。
関数呼び出し時の関数名とは、普通に関数を呼び出すときのリストの第一要素の事です。

((lambda () :hello))
  →:HELLO

これだとわかりづらいですよね。

((lambda (a b) (+ a a b b)) 10 20)60

どうでしょうか。
次の実行とほぼ同じとなります。

(defun testplus (a b)
  (+ a a b b))

(testplus 10 20)60

以上で、lambdaの記載できる場所の説明は終わりです。

functionと関数呼び出しの構文の違い

こっちが本題だったりしますが、 special operatorの(function name)と、関数呼び出しの(name ...)では、 どちらもsymbol(lambda ...)を記載することができました。 両者は同じものを許容するのでしょうか?

実は違います。
(function name)が許容するものは下記の通り。

  • symbol
  • (lambda ...)
  • (setf name)

それに対して、(name ...)が許容するものは次の通り。

  • symbol
  • (lambda ...)

つまり関数呼び出しでは、(setf name)を直接呼び出すことが許されていません。
これはちょっと意外でした。

【参考】http://www.lispworks.com/documentation/HyperSpec/Body/03_abab.htm

If the car of the compound form is not a symbol, then that car must be a lambda expression, in which case the compound form is a lambda form.

つまりは下記のコードは規約違反です。

(defun (setf aaa) (v c)
  (rplaca c v)
  v)

(let ((c (cons 10 20)))
  ;; (setf (aaa c) 200)
  ((setf aaa) 200 c)  ;; ★エラー
  (format t "~A~%" c))

面白いことに、clispではこのコードが全く問題なく動作します。

clispによる実行結果
(200 . 20)

setfを直接呼び出したいならfuncall/applyを使いましょう。

(defun (setf aaa) (v c)
  (rplaca c v)
  v)

(let ((c (cons 10 20)))
  ;; (setf (aaa c) 200)
  (funcall #'(setf aaa) 200 c)  ;; ★問題なし
  (format t "~A~%" c))

実行結果

(200 . 20)

そんな馬鹿なことしてないでsetfマクロ使えという声が聞こえてきそうですが、 私もそう思います。

なお、当たり前ですが、defsetfdefine-setf-expanderで定義されたものの場合は、 関数じゃないのでこの方法では呼び出しできません。