富永研究室 ハンドブック プログラム言語

Scheme言語

[Comp] [Class]   [Upper]


[参考文献] [関連情報]


■ 基本操作

● 起動と終了

実行は、評価したい式(S式)をそのまま入力し、最後に改行すればよい。
> (exit)    

● プログラムの構成

プログラムは、defineによる変数および関数の定義を並べたものである。 各行の ; 以降はコメントになる。 プログラムのソースは、自由書式であり、改行を自由に入れてよい。 読みやすいプログラムを目指し、下記のように、 適切に改行を入れ、インデントする。
(define (fib n)
  (cond ( (= n 0) 0 )
        ( (= n 1) 1 )
        ( else    (+ (fib (- n 1)) (fib (- n 2))) )
))

● ソースの読込み

ロードされたファイル中の式が順に実行される。ただし、評価値は表示されない。 フォルダの区切は、\ が特殊文字なので、エスケープして \\ とする。
> (load 'ファイル名)
> (load "ファイル名")

> (load 'フォルダ\\ファイル名)
> (load "フォルダ\\ファイル名")

● パスの指定

ロードするファイルのフォルダを指定するには、cd 関数を用いる。
> (cd 'フォルダ名)
> (cd "フォルダ名")

● 実行ログ

実行結果を指定ファイルに記録することができる。 ファイルが既に存在するときは追加記録される。一時的に記録を中止することもできる。
> (transcript-on 'ファイル名)     ; 実行記録開始
> (transcirpt-off 'ファイル名)    ; 実行記録中止


■ 式と評価

● データ

Scheme言語で扱われるデータは、以下の9種類である。
  • 空リスト ※ ()
  • 数値 ※ 123456789 -23.456
  • 記号 ※ abc =
  • 論理値 ※ #t #f
  • 文字 ※ #\a #\0 #\*
  • 文字列 ※ "Good bye!" "2*(3a-5)=7"
  • ベクタ ※ #<1 23 3.5>
  • ペア ※ (a b ab) (1 (2 3) ())
  • 関数 ※ (lambda (x) (* x x))
  • ● データと変数

    数値はそのまま入力する。 引用符のない文字(予約語を除く)の並びは、変数名と解釈され、評価されるとその値が返る。 大文字と小文字は区別されない。 最初に引用符 ' を付けると、文字の並びを、変数として評価しないで、 1つの記号データとして扱うことができる。 数値は ' を付けても数値データである。 最初に引用符 ' を付けると、リストも、S式として評価しないで、 1つのデータとして扱うことができる。 空リスト () には、' を付けなくてもよい。 真偽値も1つのデータである。真は #t、偽は #f で表す。 ただし、#f と () は同一視される。
    > (* (- 3 1) (+ 1 2))      ※ (3-1)*(1+2) の計算
    6
    > 'abc                     ※ 'abcを評価すると記号abcが返る
    abc
    (define a 6)               ※ 変数aに数値6を代入
    6
    > (= (* 2 3) a)            ※ 2*3=a が真かどうか、変数aを評価してから比較
    #t
    > (> (- a 2) 4)            ※ a-2>4 が真かどうか、変数aを評価してから比較
    ()                         
    > (define a '(1 2 3))      ※ 変数aを再定義すると、以前の値は捨てられる
    (1 2 3)
    > (cdr a)                  ※ まず、変数aの値を求めてから関数cdrを適用する
    (2 3)
    

    ● リスト式と関数定義

    左右の括弧の対応が正しくとれた上で、改行(リターン)を入力すると実行される。 それまでの改行は、区切りとしかみなされないので、適当に改行してよい。 なお、例における ^R は改行入力の意味である。

    Schemeの実行は、データ、変数、リスト式を評価して、その値を返すことである。 データの評価値は、データ自身である。変数の評価値は、代入されている値である。 リスト式は、先頭要素を関数名または手続名とみなし、残りの要素を引数として評価される。 定義と代入・入出力・ファイル操作などは、副作用を行われる。 関数名のときは、全ての引数が評価される。手続名のときは、それぞれで異なる。

    > (set! a 4)               ※ aは評価されず、そのまま変数名と解釈する
    4
    > (define (sq x) (* x x))  ※ 1引数関数sqが定義される
    sq
    > (sq 5)                   ※ (sq 5) すなわち (* 5 5) が実行される
    25
    


    ■ エラー対応

    ● 入力未完了

    何も実行されないが、キー入力を受け付けるときは、右括弧が足らない場合が多い。 括弧の対応を確認し、必要な個数の右括弧を入力した上で改行する。 文字列の入力において、右側の引用符を付け忘れた場合も同様の症状が起こる。 この場合、"を入力して改行してみる。それでも反応がないときは、 右括弧を幾つか入力してから改行してみる。
    > (define (fact n)^R                       ※ 見易くするための改行
        (if (= n 1) 1 (* n (fact (1- n)))^R    ※ 右括弧が足りない
      ))^R
    fact
    > (fact 4)^R            ※ 関数factを引数4で呼び出して実行(4!=24の計算)
    24
    

    ● ループ

    実行されているようだが、いつまでたっても何の反応も返ってこないときは、 ^C すなわち CTRL-C で実行を強制中断することができる(何回やっても駄目ならリセット)。 この場合、関数定義が無限ループに陥っている可能性が大である。 再帰法を使っている場合は初期条件を、 do構文を使っている場合は制御変数の更新部分と終了条件を見直す。

    ● エラー表示

    エラー時には、以下のように表示されるので、メッセージを見て原因を探る。 デバッグは、エラーになった変数や関数を再定義し直せばよい。 エラーが発生すると、エラーモードに移行する。トップレベルに戻るには、 (restart 1) とする。
    > (define a x)
    eval : variable x is not bound.       ※ 変数未定義(aに代入すべきxの値が未定義)
    (restart 1)                           ※ トップレベルに戻る
    > (defin a 1)
    eval : variable defin is not bound.   ※ 関数名未定義(definを関数名と解釈)
    (restart 1)                           ※ トップレベルに戻る
    > (define a)
    define : syntax error.                ※ 構文エラー(手続きの引数が不整合)
    (restart 1)                           ※ トップレベルに戻る
    > (define (sqr x) (* x x))
    sqr
    > (sqr 2 3)
    sqr : requires 1 arguments、but 2 were supplied.    ※ 関数の引数が不整合
    [1] #<function sqr>                                 ※ トレース情報
    (restart 1)                                         ※ トップレベルに戻る
    > (car '(1 2)))                                     ※ 最後の括弧が余分
    1
    > read : too many closing parenthesis.              ※ 余分の括弧によるエラー
    (restart 1)                                         ※ トップレベルに戻る
    

    ● エラーポイント


    ■ プログラミング書法

    ● 要求仕様

    作成したいプログラム全体に対応する関数を考え、何を入力とし、何を出力するかを 定める。実行例を挙げてみて、具体的なイメージを把握する。

    ● 概略仕様

    部分的な処理を行う関数を適当に設定しながら、大まかな算法を作成する。 漸化式で表す。

    ● 実行過程の手動追跡

    定義に従って、幾つかの入力例について、簡約過程を追ってみる。


    ■ 数値処理

    ● 数値データ

    Scheme言語では、整数と実数だけでなく、有理分数と複素数も扱える。 有理分数は、2/3 のように書く。 複素数は、2+3.5i のように書く。 整数と有理分数は精度が無制限である。 実部と虚部が整数または有理分数の複素数も精度が無制限である。 これらを確実数[exact number]という。

    ● 算術演算

    有理分数の計算では、結果が既約分数となる。 例えば、(+ 1/3 2/5) は、11/15 となる。 複素数の場合、(+ 1+2i 3-5i) は、4-3i となる。 さらに、(/ 1+2i 3-41) は、-1/5+2/5i と正確に計算される。
    (+ n1 n2 ‥)    加法    n1 + n2 + ‥    
    ○ (- n1 n2 ‥)    減法    n1 - n2 - ‥    
    ○ (* n1 n2 ‥)    乗法    n1 * n2 * ‥    
    ○ (/ n1 n2 ‥)    除法    n1 / n2 / ‥   
    

    ● 数学関数

    三角関数の引数は、弧度法(ラジアン単位)である。 有効数字は、C言語での64ビット浮動小数点実数による計算と同程度である。
    (sqrt x)     正の平方根    √x
    ○ (abs x)      絶対値        |x|
    ○ (floor x)    切捨て整数化  [x]_
     
    ○ (sin x)      正弦関数  sin(x)
    ○ (cos x)      余弦関数  cos(x)
    ○ (tan x)      正接関数  tan(x)
    ○ (exp x)      指数関数  exp(x)
    ○ (log x)      対数関数  log(x)
    

    ● 比較演算

    数値データの大小比較を行う。真なら #t、偽なら #f を返す。
    (= 式1 式2)          式1 = 式2
    ○ (< 式1 式2)          式1 < 式2
    ○ (> 式1 式2)          式1 > 式2
    ○ (<= 式1 式2)         式1 <= 式2
    ○ (>= 式1 式2)         式1 >= 式2
    


    ■ データ処理

    ● リスト基本関数

    (car リスト)             リストのcar部(先頭要素)
    ○ (cdr リスト)             リストのcdr部(先頭要素を取り出した残り)
    ○ (cons データ リスト)     リストの先頭にデータを挿入したリスト
    

    ● リスト処理関数

    null?, car, cdr, cons で他の関数は全て定義できる。
    (list データ ‥)           データの並びをそのままリストにして返す
    ○ (length リスト)            リストの長さを返す
    ○ (append リスト ‥)         リストの並びを連結した1つのリストを返す
    ○ (reverse リスト)           リストの要素を逆順にして返す
    ○ (member データ リスト)     リスト中でデータと等しい最初の要素以降を返す
    ○ (list-ref リスト 位置)     リスト中の指定位置のデータを返す
    

    ● 等価演算

    (eq? 式1 式2)        式1と式2が全く同一のセルを指すか
    ○ (equal? 式1 式2)     式1と式2がアトムまたはリストとして同等か
    

    ● データの類別

    Scheme言語で扱う全てのデータは、以下の9種類のいずれかに該当する。 データがその類別に属すなら#t、属さないなら #f を返す。
    (null? データ)        空リスト        ※ ()   
    ○ (number? データ)      数値データ      ※ 123456789  -23.456
    ○ (symbol? データ)      記号データ      ※ 'abc  '=  
    ○ (boolean? データ)     論理値データ    ※ #t  #f
    ○ (char? データ)        文字データ      ※ #\a
    
    ○ (string? データ)      文字列データ    ※ "Good bye!"  "2*(3a-5)=7"
    ○ (vector? データ)      ベクタデータ    ※ #<1 23 3.5>
    ○ (pair? データ)        ペアデータ      ※ '(a b ab)  '(1 (2 3) ())
    ○ (function? データ)    関数データ      ※ (lambda (x) (* x x))
    

    ● 論理演算

    andとorでは、全ての式が評価されるとは限らないことに注意する。 前方の式から評価していき、値が確定した時点で、以降の評価を打ち切る。
    (not 式)              式を評価して、偽ならば#t、それ以外なら#fを返す
    ○ (and 式1 式2 ‥)      順に評価して、真でない最初の式で終了し、#fを返す
                               そうでなければ、最後まで評価して、最後の式の値を返す
    ○ (or 式1 式2 ‥)       順に評価して、偽でない最初の式で終了し、その値を返す
                               そうでなければ、最後まで評価して#fを返す
    


    ■ 制御構文

    ● 引用

    (quote 記号)       評価せず、そのまま記号データを返す
    ○ '記号              (quote 記号) の略記
    ○ (quote リスト)     評価せず、そのままリストデータを返す
    ○ 'リスト            (quote リスト) の略記
    
    〔例〕 (quote a)  →  a
    〔例〕 (quote (+ 1 3))  →  (+ 1 3)      
    

    ● 評価

    eval構文では、データをS式として評価する。 すなわち、引数を評価した値を、さらに再評価する。
    (eval データ)      
    
    〔例〕 (eval '(+ 1 3))  →  4            
    〔例〕 (eval '(car '(+ 1 3)))  →  +
    

    ● 定義

    (define 変数名 式)                   新しい変数を定義して、式の値を代入
    ○ (define (関数名 仮引数 ‥) 本体)     関数定義
    

    ● 代入

    (set! リスト位置 式)        既に定義されている変数のリスト位置への代入
    

    ● λ式

    (lambda (引数 ‥) 本体)     本体を関数抽象化して無名関数を生成
    
    〔例〕 ((lambda (x) (* x x)) 3)  →  9
    

    ● map関数

    リストは、関数の引数の個数だけ必要である。 各リストの長さは同じでなければならない。
    (map 関数名 リスト ‥)     リストの各要素に関数を適用した値を並べたリスト
    
    〔例〕 (map + '(1 2 3) '(4 5 6))  →  (5 7 9)
    〔例〕 (map sq '(1 2 3))  →  (1 4 9)
    
    map関数の第1引数としては、パラメタ付関数をλ式として与えることが多い。
    〔例〕 (map (lambda (x) (* 3 x)) '(1 2 3))  →  (3 6 9)
    〔例〕 (map (lambda (x) (map * 2 x)) '((1 2) (3 4)))  →  ((2 4) (6 8))
    

    ● apply関数

    (apply 関数名 リスト)      リストの要素をそのままの順で引数とし、関数を適用
    
    〔例〕 (apply + '(1 2 3))  →  6        
    

    ● 局所定義

    変数に式の値を束縛した局所環境の下で本体を順に評価し、最後の式の値を返す。 変数にλ式を束縛し、局所関数として用いることもできる。 ただし、再帰的定義はできない。
    (let ((変数名 式) ‥) 本体 ‥) 
    
    〔例〕 (let ( (x 2) (y 3) ) (* x y) )  →  6
    〔例〕 (let ( (f (lambda (x) (* x x))) ) (f 5) )  →  25
    


    ■ 制御構造

    ● 順次構文

    begin構文では、先頭から順に式を評価し、最後の式の値を返す。 入出力などの副作用を利用することが目的である。 {式1; 式2; ‥} と同様の処理を行う。
    (begin 式1 式2 ‥)
    
    〔例〕 (begin (display (gcd a b)) (newline) )
    

    ● 選択構文

    選択構文としては、if構文とcond構文が用意されている。 if構文では、条件を評価して、真なら式1、偽なら式2の値を返す。 先頭から順に条件を評価して真になったら、そのときの式の値を返す。 if構文は、 if (条件) {文1; ‥} else {文2; ‥} と同様の処理を行う。 cond構文では、条件と式の組を並べていく。 最後に、常に真となる条件として、else を置く。
    (if 条件 式1 式2)(cond (条件1 式1) (条件2 式2) ‥ )
    
                               
    〔例〕 (cond ( (= n 0)  0 ) 
                 ( (= n 1)  1 ) 
                 ( else     (+ (f a) (f b)) ) 
           )
    

    ● 反復構文

    関数型プログラミングでは、反復構造を再帰呼出で実現するのが普通である。 Scheme言語には、命令型プログラミング的な反復構造として、do構文も用意されている。 do構文では、変数を初期値からループ毎に変分を与えながら、 終了条件が真になるまで本体を繰り返す。
    (do ( (変数名 初期値 更新) ‥ )
           (終了条件 式)                
           本体
       )
    
    do構文は、 for (初期化; 継続条件; 更新) {本体}; {式}; と同様の処理を行っている。
    〔例〕 (do ( (k 1 (+ k 1))  (r 1) ) 
               ( (> k n) r ) 
               (set! r (* r k)) 
           )
    
    


    ■ 入出力とファイル操作

    ● 入出力

    入出力操作は副作用であり、それとは別に関数自体の値が返される。
    (read)               端末からのデータの入力を受け付ける。入力値を返却する。
    ○ (display データ)     データを端末に表示する(改行されない)。データの値を返却する。
    ○ (newline)            改行を出力する。#tを返却する。
    


    ■ その他

    ● 継続

    ● 遅延評価