13 January 2024

lambdaは身近な存在だが、感覚で使ってしまっていると思ったので改めて調べてみた。 lambdaはLispで最も根源的な概念と言える。そのため特殊な扱いを受けている。 純Lispの定義でもlambdaは直接定義されていた。

基本

lambda式は(無名)関数を表現する特別な式である。 Hyperspecにはマクロのように記載されているのでややこしいが、マクロとしてのlambdaと言語組込みの特別扱いされるlambdaがあり、マクロとしてのlambdaは本来のlambda式から関数オブジェクトを取り出すショートカットとして定義されているもの、という位置付けらしい。

(lambda (x y)
  (+ x y))

=> #<FUNCTION (LAMBDA (X Y)) {700861A20B}>

本来のlambda式は関数オブジェクトを返す関数、というわけではない。なので式の第一要素に置ける。

((lambda (x) (1+ (1+ x))) 10)
=> 12

これが例えばalexandriaのcompose(合成した関数を返す関数)だったとしたらエラーになり、funcallが必要になるだろう。

((alexandria:compose #'1+ #'1+) 10)

; in: (ALEXANDRIA:COMPOSE #'1+ #'1+) 10
;     ((ALEXANDRIA:COMPOSE #'1+ #'1+) 10)
; 
; caught ERROR:
;   illegal function call
; 
; compilation unit finished
;   caught 1 ERROR condition

(funcall (alexandria:compose #'1+ #'1+) 10)
=> 12

lambda式には#’が必要なのか?

lambda式に#'を付けることで明示的に関数オブジェクトを取り出せる。 こうなるとfuncallで関数として呼び出さなければエラーになる。

(#'(lambda (x) (1+ (1+ x))) 10)

; in: #'(LAMBDA (X) (1+ (1+ X))) 10
;     (#'(LAMBDA (X) (1+ (1+ X))) 10)
; 
; caught ERROR:
;   illegal function call
; 
; compilation unit finished
;   caught 1 ERROR condition
; Evaluation aborted on #<SB-INT:COMPILED-PROGRAM-ERROR {700C0609A3}>.

(funcall #'(lambda (x) (1+ (1+ x))) 10)
=> 12

SBCLではlambdaをmacroexpand-1すると#’付きの形に展開されるので同じことだが、式の先頭にlambdaを置いてもエラーにならないためその場合だけ特別扱いされているようだ。

(macroexpand-1 '(lambda (x) (1+ (1+ x))))
#'(LAMBDA (X) (1+ (1+ X)))

mapcarなどにlambda式を渡すときには、#’を付けて関数オブジェクトであることを示すのが本来的には正しいらしい。 #’を付けないでも上述のlambdaマクロによって#’を付けていることになるが、これは単なる糖衣構文であり好まない人も多いらしい。

手持ちの本を調べてみると、mapcarなどに関数オブジェクトを渡す際に#’を付けているのは、

  • Paul GrahamのANSI Common Lisp
  • 実践Common Lisp(Practical Common Lisp)
  • 実用Common Lisp(PAIP)

逆に#’を付けていないのは

  • Land of Lisp
  • はじめてのLisp関数型プログラミング――ラムダ計算からリファクタリングまで一気にわかる

「実践Common Lisp」と「はじめてのLisp関数型プログラミング」にはコラムや注釈でこの違いについても触れられていた。