今回はタイトルの通りlet式について説明しようと思います。let式を使うと、ふつうの手続き型言語でいうところのローカル変数を利用できるようになります。
余談になりますが、この連載では手続き型言語に慣れてはいるけれどLISP(Scheme)は実用性がないし理解しにくいのではないかという疑問を持って入って来られた方にもLISPを理解できるよう配慮して執筆しています。ですから、LISPのLISPたる所以であるリストというデータ構造と、それを式として表現するという題目には、まだ、触れていません。まずは、LISPが(括弧の多さを除けば)ふつうのプログラミング言語だという点から説明しています。リストについては数回後に説明しますから安心してください。
復習しましょう。LISPの式は次のような構造をしているのでした。
(<式1> <式2> <式3>...)
この括弧で囲まれた一連の式はリストを形成しています。この中で<式1>はオペレータになっています。オペレータには関数とスペシャルフォームがありますが、今回は関数にフォーカスします。
この式を表すリストが関数呼び出しを表すとき、<式1>は、lambda式です。続く<式2>、<式3>... は関数の引数となり、<式1>が適用される前に評価されるのでした。コードを見てみましょう。
((lambda (r) (* 3.141592654 (* r r))) (* 5 2.54))
506.70747916365997
>
> (define area-of-circle
(lambda (r) (* 3.141592654 (* r r))))
> (area-of-circle (* 5 2.54))
506.70747916365997
>
さて、話を元の式に戻しましょう。
((lambda (r) (* 3.141592654 (* r r)))
がlambda式(関数)で上の形式で言えば<式1>になっています。このlambda式は仮引数 r をひとつ取ります。さて、この関数呼び出しにおいて仮引数 r に対応する実引数は、上の形式では<式2>の部分を指します。つまり、(* 5 2.54)
ですね。これは、lambda式が適用される前に評価されるのでした。つまり、仮引数 r にバインドされる実引数は、12.7
です。REPLで r を評価してみましょう。
> r
net.prizo.scmlib2.sc_exception: unbound variable: r
at net.prizo.scmlib2.symbol.eval(symbol.java:53)
at net.prizo.scmlib2.environ.eval(environ.java:73)
at net.prizo.scmlib2.scheme.run(scheme.java:79)
at java.lang.Thread.run(Thread.java:745)
; unbound variable: r
>
12.7
が返って来ます。これは、手続き型言語と何ら変わりはありませんね。複数の引数を持つ関数は次のように書きます。
> ((lambda (a b c) (- (* b b) (* 4. a c))) 1. 4. 4.)
0.0
>
> (define discriminant
(lambda (a b c) (- (* b b) (* 4. a c))))
> (discriminant 1. 4. 4.)
0.0
>
1.
, 4.
, 4.
です。ところで、この程度の式ならば、lambda式をわざわざ書かなくてもいいと思われるかも知れません。その要求を満たしてくれるのがlet式なのです。let式を使って、上の式を書き換えると次のようになります。
> (let ((a 1.)(b 4.)(c 4.))
(- (* b b)(* 4. a c)))
0.0
1.
、b には 4.
、c には 4.
、のようにシンボルに値がバインドされます。これらのシンボルが有効なスコープはlet式の中だけです。let式を抜けた後、各シンボルを評価すると(let式の)外側でバインドされた値になります。a、b、c は手続き型言語のローカル変数に相当します。let式の一般形は次のような形をしています。
(let ((<シンボル> <式>)...) <式> <式> <式>...)
begin
や lambda
と同様に逐次実行され最後の式の値がlet式の値となります。ところで、let式が構文糖(シンタックスシュガー)であることに気付かれましたか。便宜的にローカル変数と呼んでいるものは、じつのところlambda式の仮引数に相当します。つまり、let式は、その場でlambda式を生成し、即座に実引数を与えて、そのlambda式を呼び出しているのです。(理解できないようでしたら、今は深く考えなくて結構です)
ここで、不思議な関数をひとつ紹介します。
> (define gen-seqno
(let ((n 0))
(lambda ()
(set! n (+ n 1))
n)))
> (gen-seqno)
1
> (gen-seqno)
2
> (gen-seqno)
3
>
> (define gen-seqno-another
((lambda (n)
(lambda ()
(set! n (+ n 1))
n)) 0))
> (gen-seqno-another)
1
> (gen-seqno-another)
2
> (gen-seqno-another)
3
>
letについては以上です。次回は、ループについて説明する予定です。