SchemeAndImpl / 3.UsingScheme


SchemeAndImpl

Scheme を使ってみる(チュートリアル)

この章では、ユーザの観点からどのように Scheme が働くか、そして簡単な Scheme プログラムの書き方を説明します。実際にお使いの実際の Scheme システムで実験しながら、進んでください。

この章は、前の章と独立して読まれることは意図していません。この章の練習問題を行なう前に、前の章のどこを読んでいるべきかの注意書きを含めました。

この章はまた実際に Scheme システムを走らせて確認することなしに読んでも、あまり意味がありません。

(もしあなたが前の章の Hunk A を読んでいないなら[Schemeとは何か (Hunk A)]を読んでから戻ってきてください。)

対話的プログラミング環境 (Hunk B)

==================================================================
ここが Hunk Bの始まりです。
==================================================================

大抵の Scheme 実装は対話的です。Scheme システムはただのコマンドインタープリタをもったプログラムに過ぎません。それは起動したときに、プロンプトを表示し、あなたに式の入力を促します。そして、それは式の結果のテキスト表現を出力します。

(あなたの Scheme システムは、GUIを持っているかも知れませんが、基本的な考え方は同じです。Scheme に何をするかを告げ、それは指示に従い行い、何が起こったかをあなたに告げ、次のコマンドをたずねます。GUIがあるなら、あなたは Scheme にボタンクリックなどで、何をするかを伝えられるのかも知れません。)

これは OSのコマンド・インタープリタすなわち「シェル」ととても似ています。シェルは単なる言語 -- 大抵は本当に醜い言語ですが -- のインタープリタです。

対話的プログラミング環境のすばらしいところは、プログラムを走らせた後、それがどこかへ行ってしまうことがないことです。あなたはプログラムの「内側」いいて、それに対して何をするか告げられますが、完全に終わるまでただ走る代わりに、それは戻ってきて、次は何をするかあなたに聞いてきます。

変数の値は保存され(still around)ていて、もし見たいのであれば見ることができます。これにより、デバッグが簡単になります。変数や手続きの定義を入力し、手続きを走らせ、期待したようであるかを見てみます。そうでなければ、再定義することができます。結果的に、あなたはプログラムの内部にいて、Scheme はディスパッチャーのように働き、実行したい箇所や、結果を検査したい部分を実行できるようにしてくれます。これにより、あなたがプログラムを組み上げ、小さな部分ごとにテストするのが楽になり、次第にそれらの部分から、大きな部品を作り上げていくようにさせてくれます。

このセクションでは Scheme で簡単な例をやっていきますが、前の章のものに似た例から始めて、十分にゆっくりやっていきます。Scheme はすでに適切にあなたのシステムにインストールされていると仮定しています。もしそうでなければ、 Scheme を入手してインストールするか、誰かにインストールしてもらう必要があります。

(付記:あなたはわれわれの Scheme であり、フリーの RScheme を使いたいかも知れない。もちろん Scheme の実装は、他にも商用的なものもフリーのものもあります。もし別の Scheme を使っているなら、その操作方法はとても似ているはずです。)

Scheme システムが走っているマシンの前でこのテキストを読み進めるのは、とてもいい考えです。そうすれば対話的にそれを使うのに慣れることができるでしょう。あなたがそうしていると仮定していますので、「これをしてください」「あれをしてください」といいます。そうしなければならないという訳ではないですが、 Scheme を学ぶにはそれが一番いい手段です。

Scheme を起動する:Scheme を実行させること

まず、Scheme を起動します。UNIX上で RScheme を使っているなら、UNIX の % プロンプトで rs とタイプすることでおそらく起動します。(うまくいかなければ、RScheme はあなたのシステムでは別の名前でインストールされているかも知れません、その場合は多分、 rscheme でしょう。その名前を変わりに使ってみてください。UNIXを使っているのでなければ、自分のシステムでプログラムを立ち上げるやり方で RScheme を起動してください。おそらくアイコンをクリックするようなやり方で。もしUNIXを使っているんだけど、自分のシェルが違ったプロンプト > などを表示している場合、別に気にしないでください。)

[私のクラス(UT CS)の学生に注意:われわれのマシンでは RScheme は /p/bin/runscheme としてインストールされています。それを UNIX プロンプトで入力するか、もしパスに /p/bin が入っているなら、ただ runscheme としてもよいです。]

%rs 

さて、Scheme システムが起動して、大抵、名前とバージョン番号が含まれている自分についての情報を表示します。プロンプトが Scheme> だとしましょう。ですがあなたのシステムではたぶん少し違うかも知れませんが。(RScheme の場合は、top[0]=>) のようになっています。最初のいくつかの数字は、システムの状態についての情報です。そして => というのは入力の準備が出来ているこという意味です。)

Scheme はそれから、あなたが式を入力して、<RETURN を打つのを待ちます。(私が言っているのは、キーボード上の "RETURN" あるいは "ENTER" キーを打つということです。ある Scheme システムでは、これらは別のキーかも知れませんが、そのときは "ENTER" キーを打ってください。あなたの使っているシステムのドキュメントにどのキーが何をするかは載っているでしょう。)

Scheme はあなたが入力している間、文字を画面にエコーし、あなたが <RETURN> を打つまでは何もしません。<RETURN> を打つまではタイプミスを、DELETE あるいは BACKSPACE キーをしようして、戻って直すことができます。(ちょうどOSのコマンド・シェルでやるのと同じですね。)

それでは、変数定義 (define myvar 10) を入力し、<RETURN を打ってみましょう。 画面で起こったのはこんなふうになっているでしょう。

Generational Real-Time Garbage Collector Version 0.5
RScheme version 0.7
Scheme>(define myvar 10)
#void
Scheme>

ここで、myvar という名前の変数を、初期値 10を与えて、定義しました。Scheme はタイプ入力されたものを読み、どんな意味か特定し、それからある記憶領域を変数へ束縛するために確保し、その領域を10(へのポインタ)で初期化します。 Scheme はその割り当てられた記憶領域が myvar として知られるようになった事実と、その値が何であるかを記録に残します。

Scheme がこの式を評価した後に出力したものは、あなたのシステムでは違うかもしれない(#void はないかも知れない)。それは Scheme の標準では、定義の式が返す値について取り決めはないからです。(あなたの Scheme ではもう少し多くの情報が出力されることも有り得ます。あるいは違うもの、あるいはまったく何もなしがdefine を呼び出した結果かも知れません。それは心配しないでいいです。)

あなたは大抵定義の結果の値を使いません。-- ただ後で使用するものを定義したかっただけなのですから。あなたが使っているものの実装に依存しますが、実装者が定義の返り値として選んだものが表示されるわけです。あるシステムでは、特殊な使用できない値が返され、Scheme は画面がおかしくなるのを避ける為に、これらの意味がない値を出力しません。

ミスから復旧する:誤りを犯してそれを復旧すること

時々、Scheme で対話的に実行していて、ミスを犯すことがあるでしょう。これは普通のことですが、もしそうしてしまったら、心配しないでください。Scheme は何か異常を発見したとき、文句を言うのです。大方のテキストベースの Scheme システムでは、特殊なプロンプトを表示し、ミスを直すコマンドが入力できるようにします。他のシステムでは、デバッガが起動されるかも知れません。それはミスを診断したり直したりするプログラムです。今のうちは、Scheme がミスを修正しようとするのをあきらめさせるコマンドを知っておく必要があります。そして普通の「トップレベル」対話モードに戻るわけです。後のほうで、あなたのシステム上のデバッグ機能についても学ぶべきですが、いまは普通の Scheme プロンプトに戻れるだけでいいでしょう。

式を中途で止めるコマンドを探し当てたとします(マニュアルを読むか、ヘルプシステムに頼るなどして)、それをやってみてください。わざとミスを起こし、システムが何をするかそして自分でミスから復旧できるかを確認してください。

ここにいい間違いの例があります。そして Scheme が式の構造を仮定した(hypothetical)応答、そして普通の Scheme プロンプトへの復旧。これをあなたのシステムで試してみて、同じことができることを確認してください。

Scheme>(2 3 4)
ERROR: attempt to apply non-procedure 2
break[1]>,toplevel
Scheme>

[RScheme ユーザへの注意:RScheme での ,toplevel コマンドは、,top と省略できます。]

ここで式 (2 3 4) を入力しましょう。これはまずい書き方です。Scheme システムはそれを特殊フォームではない式だと解釈し、手続き呼び出しとして会h策しようとします。そして、最初のサブ式の結果を他のサブ式に適用します。この場合は、最初のサブ式は 2 で、評価後も 2ですが、これはまったく手続きではありません。この時点で、Scheme は「2 を手続きとして使おうとした」と文句を言って、デバッグするために "break loop" へモード変換(switch)しました。

ブレイク・ループは、特殊なデバッグ用プロンプトbreak[1]> を表示し、どうするかを尋ねます。われわれは、普通の対話に戻るために特殊コマンド ,toplevel を入力し、Scheme は戻って新しい Scheme プロンプトを表示しました。

お使いのシステムで、プロンプトとコマンドは違っているでしょう。(例えば、特殊コマンドはコンマ,ではなくコロン:から始まるかも知れません。そしてコマンド名も違うかも知れません)。それがどんなものであれ、簡単であり、できるだけ早くに覚えてしまうべきです。あなたのシステムのドキュメントを見てみましょう。

ここに別の普通にあるミスを挙げます。 すぐにそうしてしまうでしょうから、ミスをやってみて、どうやってそこから出るかもやってみましょう。

Scheme>a-variable
ERROR: unbound variable: a-variable
break[1]>,toplevel
Scheme>

ここでは、Schemeに a-variable という式を評価するよう頼んでいます。a-variable はただの変数の名前と同様、普通の識別子であるため、Schemeはそれが変数名であると仮定しました。そして、その値をわれわれは尋ねたことになるのです。でも、a-variable という名前の変数はないので、Scheme は文句を言います。Scheme の世界でいう、ある記憶領域に名前をつけるために、われわれはその変数を定義して、記憶領域に「束縛」する必要がありますが、それをまだしていません。Scheme はその名前ではどの記憶領域も見つけることができず、ましてやその値をとってくるなどはできないのです。

(あなたのシステムでは、定義していない変数に対しても自動的に束縛が作られて、 set! が使えるかもしれません。これは Scheme の標準では要求されていませんし、プログラムは一般にこれを使うようなことをするべきではありません。)

先ほどのように、特殊なエスケープコマンドを使って、この壊れた式の評価を中止し、Schemeと通常の対話モードに戻りました。

リターンキーと括弧:対話的な入力に書式付けをすること

Scheme における普通に起こる誤りは、式を閉じる括弧を忘れることです。もし、閉じ括弧を忘れたら -- いくつか式がネストしている場合に起こりがちですが --、大抵のシステムはハングして、あなたが式を完成させるのを待っているでしょう。

これはバグではなくて、機能的な特徴です。あなたが <RETURN> (改行) を入力欄に打てば、そのようにコードが画面上で整形されます。最後の閉じ括弧を入力して、もう一度 <RETURN> を打ったときに、Scheme は式全体が入力されたと認識し、それを評価して、結果を出力します。

なので、式を入力し <RETURN> を打ったのに、Scheme が何もしないときには、開いた括弧をすべて閉じたのかを確認してください。もし、そうなっていなければ、ただ足りなかった括弧を入力し、もう一度 <RETURN> を打ってください。

(別の可能性としては、あなたのシステムで、Scheme に式を評価させるのに、何か特別なことをしなければならないかも知れません。違ったキーを打ったり、ボタンやメニューをクリックしたり。そのようなシステムでは、<return> はただ入力しているテキストをフォーマットするだけに使われ、他のキーで Scheme に先に行ってタイプしたものを評価するように指示を出すのかも知れません。)


Scheme を中止する:動かなくなった Scheme システムをまた動かす

避けられないことではありますが、 時にはコーディングしたルーチンが無限ループ(あるいは無限再帰)を起こして、制御が返ってこない場合(get stuck)があります。ここで知らなければいけないのは、そのようなループを止め、Scheme の普通の対話プロンプトへ戻ってくるやり方です。Scheme システムは一般的にシステムがしていることに「割り込み」を掛け、新しくプロンプトを得られるようになっています。

大抵のUNIX上で動く Scheme システムでは、<ctrl>-C が使えます。コントロールキーを押し、c キーも打ち、これで割り込みが送れます。他のシステムでは、他のキーボード・コマンドか、ボタンかメニューをクリックするのでしょう。どれが自分のシステムでのコマンドか見つけてください。これは必要なことです。

大抵の場合、システムがハングしたら、すべての開いた括弧を閉じたのか確認するべきです -- ただ、あなたの入力が完了するのを待っているだけかも知れません。それがうまくいかなければ、プログラムが無限ループに入ったか、待つつもりはなかった他の計算に入ってしまったかも知れません。<CTRL>-C か同等の操作でその処理に割り込みを掛けてください。

これさえもうまくいかない場合があるかも知れません。結局のところ、Scheme システムだってバグがあるのです。とても普通ではない環境においては、Scheme プログラムをもっと乱暴に殺す必要が出てきます。もし Window システムをしようしているなら、ただ、Scheme が走っている Window を殺すことができるかも知れません。UNIX下では、Scheme プロセスのIDを知るために ps コマンドが使え、そのプロセスを kill コマンドで殺すことができます。(これには -9 オプションが必要です。)

Scheme から出る(を終わらせる):Scheme を去らせること

Scheme を対話的に使っているとき、それから出ることができるようにする必要があります。対話的 Scheme システム(やはりプログラムに過ぎません)に終わりを告げるコマンドを与えるのです。

大抵のシステムでは、,exitのような特別なコマンドを持っています(コンマで始まったり、規約はいろいろでしょうけど)。(それは ,quit,halt, ,bye であるかも知れません。) 手続き呼び出しを普通のやり方で評価することで、システムを殺すことができるかも知れません。(exit), (halt), (quit), (bye) などです。

多くのシステム(特に UNIX 下のもの)では、もしトップレベルにいるなら、システムを殺すのに、割り込みのキーが使えます。例えば、トップレベルのプロンプトで、<ctrl>-D を打つことで、いけるかも知れません。

もっと式を試すこと:より多くの種類の式を試してみる

エラーのある式を入力することには慣れたでしょう。それではまともに動くやつも試して見ましょう。

もし Scheme システムから出ているなら、もう一度起動してください。

加算の式 (+ 2 3) を入力して、<return> を打ってください。(ここからは、「<return> を打ってください」というのを省略します。結果が表示された後に Scheme が見せるプロンプトも省略します。)

Scheme>(+ 2 3)
5

再び、Scheme は式を評価し、5(へのポインタ)という結果を表示しました。そして、何か他のものを入力するプロンプトを出しました。その値はどこにも保存されていないことに注意してください。ただ結果を表示しただけなのです。

ですが、われわれが myvar に以前与えた値は、まだそこにあります。変数を参照する式、ただの変数名ですが、それを入力することにより、Scheme にその値が何であるかたずねましょう。

Scheme>myvar
10

Scheme は myvar と名付けられた記憶領域の記録を取っていました。そして、myvar という式をその値を調べることによって評価します。そして、その結果を表示し、別のプロンプトを表示します。それがいつもやることなのです。

myvar の束縛に保存された値を変更するためには、そして新しい値を見るためには、単に set! 式を入力し、それから変数の名前を入力してください。このようになります。

Scheme>(set! myvar 32)
#void
Scheme>myvar
32

set! 式の結果としては、違うものを見るかも知れません。 標準の Scheme は set! の戻り値を特定していません。なぜならそれは一般に副作用のために使われるのであって、結果は問題ではないからです。define はといえば、あなたのシステムは違ったものを返すかも知れません。あるいは役に立たない値を表示するのは抑制されていて、まったく何も見ないかも知れません。

ある Scheme システムでは、set! 式の値は、値がセットされた変数の名前です。なので、こんなふうに見えるかも知れません。

Scheme>(set! myvar 32)
myvar
Scheme>myvar
32

(他のシステムでは、また違ったものです。あなたが内容を破壊した変数の古い値のように。) プログラムをプラットフォームに依存しない(portable)ものにしたければ、set! によって返される値に依存してはいけません。上の例では、set! が返す値は何も重要ではありません。ただ、新しいプロンプトが得られる前に何が表示されたかというだけです。set! で重要なのは、束縛している変数の値を更新するという効果です。見てわかるように、set! には効果がありました。というのは、myvar 式を評価したときに、新しい値が返ってきます。32 と表示されました。

もっと複雑な式も使うことができます、どんなものでも。今度は、変数を 5 増加させて、Scheme にその変数の値を再び尋ねてみましょう。

Scheme>(set! myvar (+ myvar 5))
#void
Scheme>myvar
37

さて、引数として1つの数値を取って、その2倍の数値を返す手続きを定義しましょう。そして、それに引数 2 を与えて呼んでみましょう。

Scheme>(define (double x) (+ x x))
#void
Scheme>(double 2)
4

最初の式を評価した後、Scheme はdouble の定義を記録します。2番目の式を入力したとき、Scheme はその手続きを呼んで、結果を返し、それを表示します。

Scheme は 変数と、以前にわれわれが入力した値の記録を取っているので、われわれは、myvar の値を double を呼ぶことにより、2倍にできます。

Scheme>(double myvar)
74

古い手続きを新しい手続きで置き換えることもできます。(実際、われわれは double を定義するときにこれを行なっています。-- 以前に定義されたのは、+ を使ってされていました。Scheme は起動時に定義を知っています。)*1

Scheme>(define (quadruple x) (double (double x)))
#void
Scheme>(quadruple 15)
60

さて、Scheme では事前に定義されている手続きである display を使ってみましょう。

Scheme>(display "Hello, world!")
Hello, world!
#void

ここで display は Hello, world! と画面に出力する副作用を持っています。そして void# という値を返し、表示するのです。

画面に表示されるものには、いろいろな違いがあるかも知れません。どれも気にする必要はありません。あなたのシステムは副作用と同じ行に、改行なしで、戻り値を出力したかも知れません。display の主要な目的はそれの効果なので、その戻り値は定義されていません。なので、#void の他に別なものが見えるかも知れないし、まったく何もないかもしれません。このように見えるかも知れません。

Scheme>(display "Hello, world!")
Hello, world!
"Hello, world"

もしそうなら、あなたのシステムでは display は、あなたが頼んだオブジェクトを戻り値として返すということを意味します。Scheme はその戻り値を表示しますが、それが文字列オブジェクトだと知らせるために、二重引用符でくくります。これはそう驚くことではありません。Scheme が式を評価した後、戻り値を表示することを思い出してください。

それでは数値を表示してみましょう。

Scheme>(display 322)
322
#void

Booleans and Conditionals: trying out basic control flow

Sequencing: trying out begin and procedure bodies


Other Flow-of-Control Structures: cond, and, and or

Making Some Objects: messing around with pairs

Lists: using lists


*1 double が組み込み手続きであるような書かれ方であるが、こちらの MzScheme では、そうではなかった。