2017年07月19日

numb-lambdaの式とシンボル(2)

前回はLISPの式とシンボル、そしてシンボルに関数(lambda式)をバインドする方法を説明しました。今回は、再びシンボルとスペシャルフォームについて説明します。

いつものように、numb-lambdaを起動してください。いきなり、シンボルを評価してみましょう。まずは、"if"と入力してみてください。
> if
<special form>

次にaというシンボルを評価してみましょう。
> a
net.prizo.scmlib2.sc_exception: unbound variable: a
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: a
>

エラーとなってしまいました。aには、まだ何もバインドされていません。
では、早速、シンボルaに何かバインドしてみましょう。今回は単純に数値9をバインドします。そして、即座にシンボルaを評価してみましょう。
> (define a 9)
> a
9


関数と同様に define スペシャルフォーム使うと、シンボルに値をバインドできます。そして、バインドしたシンボルを評価すると、バインドしたデータが返ってきます。
それでは、シンボルbを評価して、まだ何もバインドされていないことを確認し、数値5をバインドして、シンボルbを評価してみます。
> b
net.prizo.scmlib2.sc_exception: unbound variable: b
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: b
> (define b 5)
> b
5
>

予想どおりの結果になりましたか。
その後、このaとbは式の中のどこにでも記述できるようになります。例えば-関数を適用してみましょう。
> (- a b)
4
>

これも期待どおりの結果になっていると思います。

ところで、defineは何者だったか覚えていらっしゃるでしょうか。そう、スペシャルフォームでしたね。スペシャルフォームを評価するとき、式となるリストのうち、2つ目からの式を評価するかどうかは、個々のスペシャルフォームに対して任意でした。defineについて、もう一度見てみましょう。
> (define a (+ 5 4))
>

ここで、注意しなければならないのは、defineの(リストの)2つ目の式は評価されず、3つ目の式は評価されるということです。2つ目の式はシンボルです。これを評価してしまうとシンボルにバインドされている値が返ってきます。ここでは、シンボルの値ではなくシンボルそのものが欲しいのでaは評価されません。これは、defineスペシャルフォームの仕様です。

次に別のスペシャルフォームを見てみましょう。
> set!
<special form>
> (set! a (+ (- a 5) 2))
> a
6


set!(セットバンと読みます)も副作用を期待するスペシャルフォームで、既に定義され何らかの値がバインドされたシンボルを上書いてバインドします。ここで、副作用という語を使いました。LISPは本来、関数型言語です。多くの手続き型言語とは異り、式が評価された値を重視します。表示する(display)ことや、値を変更(set!)することは、式が評価された値を期待するものではないため副作用期待した式ということになります。

次にスペシャルフォームifを見て行きましょう。ifの一般形は次のようになります。
(if <式1> <式2> <式3>)
ここで、<式1>は特別な式で真偽値を返す式でなくてはなりません。numb-lambdaとScheme言語では真偽値は、それぞれ#t(真)と#f(偽)のように外部表現されます。それぞれ、REPLで評価してみてください。
> #t
#t
> #f
#f


このとおり、評価しても与えた式が、そのまま返ってきます。ifスペシャルフォームは、<式1>が#tのとき、<式2>を評価し、その値を評価値とします。このとき、<式3>は評価しません。同様に<式1>が#fのとき、<式3>が評価され、その値を評価値として返します。<式2>は評価されません。
それでは、ifスペシャルフォームを使用して与えられた式が奇数か偶数かを判定する式を記述し評価してみましょう。それに際して関数odd?を利用します。このように、シンボルの最後にクエスチョンマークが付いている関数はLISPでは述語と呼びます。
> odd?
<procedure>
> (if (odd? (+ (- 9 5) 2))
(begin (display "odd")(newline))
(begin (display "even")(newline)))
even


復習するとbeginスペシャルフォームは、beginに続く式を逐次評価して最後の式をその評価値として返すものでした。

ただ、これは副作用(defineとnewlineがそれを意味する)を使った式でLISPの世界では、あまり歓迎されません。一般にLISPの世界では次のように書きます。
> (if (odd? (+ (- 9 5) 2)) 'odd 'even)
even


ここで、'odd, 'evenという式を使いました。この'は、続く綴りを持つシンボルを返すスペシャルフォームです。一般的なLISPの式とは形式が異りますが、それぞれ、(quote odd)、(quote even)の省略形だと考えてください。つまり、quoteがスペシャルフォームです。ここで、驚かれるかも知れませんが、シンボルはデータなのですですから、シンボルは式でもあるのです。また、シンボルは文字列ではありませんので注意が必要です。

最後に復習として、関数を書いて締め括りましょう。
>  (define odd-or-even
(lambda (e)
(if (odd? e) 'odd 'even)))
> (odd-or-even (+ (- 9 5) 2))
even


しかし、こんな関数は実際には必要がありません。何故ならodd?があれば十分ではありませんか。この事実により、LISPのプログラムが簡潔に読み書きできるのです。ただし、括弧を数えるのは楽ではないかも知れません。しかし、それは、エディタに任せましょう。後にコラムとしてemacsエディタを使ってSchemeプログラムの開発を行う方法を簡単に紹介する予定です。(numb-lambdaはSchemeから派生した言語ですのでSchemeモードを使うことができます)