Guile基礎/ラムダ式(lambda)

変更履歴

概 要

目 次

参考資料

lambda式の構文と意味

lmabda式は次の文法に沿って記述します.
(lambda formals body) syntax
   formals     ::=  (identifier*)
                   |  identifier
                   |  (identifier+ . identifier)
   identifier  ::=  識別子
   body        ::=  definition* expression+
   definition  ::=  定義(define形式など)
   expression  ::=  式
補足: Gule 3.0以降,lambda 式の本体は定義と式を交互に記述してもよいことになっています.つまり,以下に示すように,lambda 式の本体は上記の body を複数指定してよいことになります.

(lambda formals body+)

参照: Guile[6.10.3 Internal definitions] の最後に次の一文があります.
Relatedly, it used to be that internal definitions had to precede all expressions in the body; this restriction was relaxed in Guile 3.0.

lambda式は,formals を仮引数とし,body を本体とする手続きを作ります.さらに,lambda式を評価した時点の環境が組み合わされるので,正確にいえばクロージャーを作ります.そのクロージャがlambda式の評価結果になります.

lambda式そのものを評価した時点では本体(body)は評価されません. 本体(body)はlambda式によって作り出された手続きを呼び出したときに評価されます. 手続きの本体の評価は,手続きそのものを実行するまで遅延されると言えます. define形式やletrec式やletrec*式によって相互再帰的な手続きが定義可能であるのは,このことに基づいています.

lambda式によって生成された手続きを実引数を指定して呼び出したとき,仮引数(formals)が実引数に束縛されることによって環境が拡張されます.その拡張された環境のもとで本体(body)が評価されます.最後に評価された式の値が手続き呼び出しの結果(返り値)になります.

記録

上で述べた補足事項(bodyを複数指定できること)を具体的に確認しておきましょう.
;; bodys.scm
(define (bodys-test)
  (define a 10)
  (define b 20)
  (display "a+b=") (display (+ a b)) (newline)
  (define x 100)
  (define y 200)
  (display "x+y=") (display (+ x y)) (newline))
guile> (load "bodys.scm")
      ...... コンパイルメッセージ ......
guile> (bodys-test)
a+b=30
x+y=300

仮引数のパターンと意味

(lambda () body)

上記の () は引数がないことを示しています. 従って,このラムダ式は無引数の手続きを作ります. 無引数の手続きは次の式によって呼び出します.
(proc)
ここで,proc は無引数の手続きを表します. この手続き呼び出しを実行すると,Guileは,lambda式を評価したときの環境(つまり,手続きを作り出したときの環境)のもとで body に指定された定義と式を先頭から順に評価していきます.そして,最後に評価した式の値を手続き呼び出しの結果(返り値)として返します.

無引数の手続きはbodyの評価を保留(または遅延)していると言えます. 例えば,
(lambda () (display "Hello, world!") (newline))
という無引数の手続きは,それが呼び出されるまでdipslayとnewlineの実行を保留(または遅延)していると言えます.無引数の手続きは,実行を保留(遅延)するための構文要素として色々なところで利用されるため,サンクthunk)と呼ばれています.

具体例

以下では,無引数の手続き hello を定義したあと,それを呼び出しています.
guile> (define hello (lambda () (display "*** Hello, world! ***") (newline)))
guile> (hello)
*** Hello, world! ***

(lambda ($x_1$ ... $x_n$) body)

このlambda式は,$n$個の引数を持つ手続きを作ります.$x_1$〜$x_n$は仮引数として使用する変数を表していて,それぞれの仮引数は空白で区切りって丸括弧で囲みます.

このlambda式によって作られた手続きは次の形式で呼び出します.
(proc $a_1$ ... $a_n$)
ここで,proc はlambda式によって作られた手続きを示し.$a_1$〜$a_n$ は実引数を表しています.この手続き呼び出しを実行すると,Guileは,$x_1$〜$x_n$ のそれぞれを $a_1$〜$a_n$ に束縛することによってlambda式を評価したときの環境を拡張し,その拡張した環境のもとで body を先頭から順に評価していって, 最後に評価した式の値を手続き呼び出しの結果(返り値)として返します.

実引数はちょうど$n$個指定しなければなりません.

具体例

以下では,1引数の手続き mysqr を定義して, それを適当な実引数に対して呼び出しています.
guile> (define mysqr (lambda (x) (* x x)))
guile> (mysqr 5)
$1 = 25
さらに以下では,2引数の手続き addsqr を定義して, それを適当な実引数に対して呼び出しています. なお,addsqr を定義する際に,上記の mysqr を利用しています.
guile> (define addsqr (lambda (x y) (+ (mysqr x) (mysqr y))))
guile> (addsqr 3 4)
$2 = 25

(lambda $x$ body)

このlambda式は,可変個の実引数を受け取る手続きを作ります.$x$は仮引数として使用する変数を表しています.仮引数を1つだけ指定して,丸括弧で囲みません.

このlambda式によって作り出された手続きは次のいずれかの式で呼び出します. 以下の proc はlambda式によって作り出された手続きを表していて,$a_1$〜$a_n$ は実引数を表しています.

具体例

以下では,仮引数(args)を表示するだけの手続き write-args を定義し, 適当な実引数に対してその手続きを呼び出しています.
guile> (define write-args (lambda args (write args) (newline)))
guile> (write-args)
()
guile> (write-args 1)
(1)
guile> (write-args 1 2)
(1 2)
guile> (write-args 1 2 3)
(1 2 3)
仮引数(args)をそのまま表示しているのですが, 表示結果はすべて実引数からなるリストになっています. これは,Guileが実引数からなるリストを作って,仮引数の args をそのリストに束縛しているためです.

具体例

以下のプログラムは,可変個の数値を受け取って, それらの総和を返す手続き sum-args を定義しています.
;; sum-args.scm
(define sum-args 
  (lambda args 
    (let loop ((xs args) (sum 0))
      (if (null? xs)
          sum
          (loop (cdr xs) (+ sum (car xs)))))))
以下では,上記の手続きを適当な実引数に対して呼び出しています.
guile> (load "sum-args.scm")
      ...... コンパイルメッセージ ...... 
guile> (sum-args)
$1 = 0
guile> (sum-args 1)
$2 = 1
guile> (sum-args 1 2)
$3 = 3
guile> (sum-args 1 2 3)
$4 = 6

補足

上記の形式の欠点は,実引数からなるリストを新たに作成することと, それぞれの実引数を利用するためにリスト要素を取り出す操作が必要になることです. 上で示した sum-args 手続きのように, 実引数の最大個数が設定できない場合には(おそらく)上記の形式を使うしかないと思います.しかし,実引数の最大個数が確定しているときには case-lambda 式や lambda* 式のオプション引数を使って実装することを検討してみるとよいでしょう.

(lambda ($x_1$ ... $x_n$ . $x_{n+1}$) body)

このlambda式は,$n$個以上の引数を持つ手続きを作ります.ただし,$n \geq 1$ です. $x_1$〜$x_{n+1}$ は仮引数として使用する変数を示しています. $x_1$〜$x_n$ は空白で区切り,最後の $x_{n+1}$ だけピリオド(.)で区切ります. ピリオド(.)の前後には空白が必要です. また,仮引数全体を丸括弧で囲みます.

このlambda式によって作り出された手続きは次の式で呼び出します.
(proc $a_1$ ... $a_{n+m}$)
ここで,proc はlambda式によって作られた手続きを示し.$a_1$〜$a_{n+m}$ は実引数を表しています. この手続き呼び出しを実行すると,Guileは,仮引数 $x_1$〜$x_{n+m}$ を次のように束縛することによってlambda式を評価したときの環境(以下,単に「環境」)を拡張します. 次に,Guileは,以上のように拡張した環境のもとで body を先頭から順に評価していって,最後に評価した式の値を手続き呼び出しの結果(返り値)として返します .

実引数は$n$個以上指定しなければなりません. ただし,$n$個以上ならば幾つでも指定できます. これは,実引数の最小個数が分かっているけれども, より多くの実引数を指定できるようにしたいときに使用します.

具体例

以下のプログラムは,2個以上の実引数を受け取って, 第1引数(x),第2引数(y),第3以降の引数(z)を表示する手続きを定義しています.
;; write-args.scm
(define write-args 
  (lambda (x y . z)
    (display "  x=") (write x)
    (display "  y=") (write y)
    (display "  z=") (write z) 
    (newline)))
以下では,実引数が2個〜4個の場合の実行結果を示しています. それぞれ,仮引数の z には3番目以降の実引数からなるリストが束縛されています.
guile> (load "write-args.scm")
      ...... コンパイルメッセージ ......
guile> (write-args 'A 'B)
  x=A  y=B  z=()
guile> (write-args 'A 'B 'C)
  x=A  y=B  z=(C)
guile> (write-args 'A 'B 'C 'D)
  x=A  y=B  z=(C D)

補足

上記の形式の欠点は,可変個の引数を使用する場合と同じです. つまり,実引数からなるリスト ($a_{n+1}$ ... $a_{n+m}$) を新たに作ることと, それらの実引数を利用するためにリスト要素を取り出す操作が必要になることです. 上記の形式は,引数の最大個数が設定できない場合に使うと言うよりは, オプション的な引数を用意したいときに使うことが多いと思います. そういった場合には case-lambda 式や lambda* 式のオプション引数を使って実装することを検討してみるとよいでしょう.
(おしまい)