プログラミング再入門

プログラミングをもう一度ちゃんと勉強する読書ノート

Scheme修行 第14章 名前をつけましょう(その1)

ローカルな変数を作るletを習います。

ノート:

leftmost

leftmostのおさらい。
leftmostは

  1. S式に適用し
  2. アトムを値とする

その値は

  1. S式の先頭要素がアトムの場合、そのアトム
  2. S式の先頭要素がアトムでない場合(リストの場合)、そのリストにleftmostを適用した値

例を評価してみる。

> (let ((l '(((a) b) (c d))))
    (leftmost1 l))
a
> (let ((l '(((a) ()) () (e))))
    (leftmost1 l))
a
> (let ((l '(((() a) ()))))
    (leftmost1 l))
. . mcar: expects argument of type <mutable-pair>; given ()
> 

この定義ではリストの先頭に空リストがある場合に不都合なので、定義を拡張。
leftmostは

  1. S式に適用し
  2. S式を値とする(あるいはアトムまたは空リストを値とする)

その値は

  1. S式の先頭要素が空リストの場合、空リスト(最初のS式が空リストの場合は仕方が無いのでそのまま空リストが値となる)
  2. S式の先頭要素がアトムの場合、そのアトム(一時変数が使えないのでもう一度carを適用する必要がある)
  3. S式の先頭要素がアトムでない場合(リストの場合)、
    1. そのリストにleftmostを適用した値がアトムの場合、そのアトム(一時変数が使えないのでもう一度leftmostを適用する必要がある)
    2. そのリストにleftmostを適用した値がアトムでは無い場合(空リストの場合)、リストの残りの要素にleftmostを適用した値

評価してみると:

> (let ((l '(((a) b) (c d))))
    (leftmost2 l))
a
> (let ((l '(((a) ()) () (e))))
    (leftmost2 l))
a
> (let ((l '(((() a) ()))))
    (leftmost2 l))
a
> 

ここで複数回出て来る(car l)とか(leftmost (car l))に名前をつけてそれを参照する形に変換する。letが使えるが、脚注にある様にletはlambdaを使った構文糖衣と言える。ただしandやletを関数として定義するのは無理なのかも知れない。(evalとか駆使すれば出来るのか?)

(leftmost (car l))だけをletを使って変数に割り当てる。(leftmost (car l))はelseに入った場合必ず1回は実行されるので、letでその結果に名前を割り当てる事に意味がある。式として関数定義の中に重複して登場しても実行されない可能性がある場合にはletでその結果に名前を付けようとするとかえって不要な関数適用をしてしまう。

(leftmost (car l))はelseの中にしか出て来ないのでelseの中でlet構文を使う。
letを使った定義をleftmost3として動作確認をする。

> (let ((l '(((a) b) (c d))))
    (leftmost3 l))
a
> (let ((l '(((a) ()) () (e))))
    (leftmost3 l))
a
> (let ((l '(((() a) ()))))
    (leftmost3 l))
a
> 
rember1*

名前の最後に*が付いているのは木構造を扱う関数。

まず元の定義をrember1-1*として動作確認。

> (let ((a 'salad)
        (l '((Swedish rye)
             (Friench (mustard salad turkey))
             salad)))
    (rember1-1* a l))
((swedish rye) (friench (mustard turkey)) salad)
> (eq? 'mississippi 'mISSISSIppi)
#t
> (let ((a 'meat)
        (l '((pasta meat)
             pasta
             (noodles meat sauce)
             meat tomatoes)))
    (rember1-1* a l))
((pasta) pasta (noodles meat sauce) meat tomatoes)
> 

ここではリテラルではなくシンボルのデータを木構造にして扱っており、Schemeではシンボルは大文字と小文字の区別をしない為、全て小文字として処理されている。

引数aが不変なので、letrecでリストのみを引数とする内部関数を定義する。letrecを使った定義をrember1-2*として動作確認。

> (let ((a 'salad)
        (l '((Swedish rye)
             (Friench (mustard salad turkey))
             salad)))
    (rember1-2* a l))
((swedish rye) (friench (mustard turkey)) salad)
> (let ((a 'meat)
        (l '((pasta meat)
             pasta
             (noodles meat sauce)
             meat tomatoes)))
    (rember1-2* a l))
((pasta) pasta (noodles meat sauce) meat tomatoes)
> 

下線を引いた(R (car l))のところがおかしく見えませんか。

そこまで明白におかしくはないが、elseに入っているcondの最初の条件で1回適用し、その条件が#fになった場合にもう一度同じ引数に同じ関数を適用してしまう。最後のelseに入った場合には最低1回は(R (car l))を実行するのでletで結果に名前をつけておく意味はある。

letを使った定義をrember1-3*として動作確認をする。

> (let ((a 'salad)
        (l '((Swedish rye)
             (Friench (mustard salad turkey))
             salad)))
    (rember1-3* a l))
((swedish rye) (friench (mustard turkey)) salad)
> (let ((a 'meat)
        (l '((pasta meat)
             pasta
             (noodles meat sauce)
             meat tomatoes)))
    (rember1-3* a l))
((pasta) pasta (noodles meat sauce) meat tomatoes)
>