式言語の基本的なしくみについて解説します。 以下の文では、式言語の例および書式において次の規約を用います。
- 書式の説明において[]で囲まれた部分は、適当な文字列によって置き換えられます。
- <例>において「→(右向き矢印)」が示すのは、各式を評価した結果の値です。
(a) 式言語の基礎
<1> 記法
式言語は、式および他のデータのすべてをかっこでくくった前置記法を用います。
処理手続きは全て関数の形式をとります。
関数の呼び出しは
( 関数名引数1 引数2 ・・・)
という形が基本となります。
<例> ( + 1 2 ) → 3 ( max 1 3 2 ) → 3
各引数は、数値のようにそれ自体を値とするものだけでなく、再びSchemeの関数の形になっていても構いません。その場合には、まず引数がすべて評価されたあと、結果が関数に渡され、関数での処理の結果が返されます。
<例> ( * 3 ( + 2 1 ) ) → 9 ( max 2 ( − 5 2 ) ( min 1 3 5 ) ) → 3
<2> 変数の定義
変数の定義にはdefine文を用います。書式は次のようになります。
( define 変数名代入する値 )
<例> ( define x 3 ) ;xに3を代入
<3> 関数の定義
関数の定義にはdefineを用います。書式は次のようになります。
( define ( 関数名引数1 引数2 ・・・) ( 関数本体 ) )
<例> ( define ( plus x y ) ( + x y ) ) ;関数plusを定義 ( plus 2 4 ) → 6
名前のない関数の定義にはlambda を用います。書式は次のようになります。
( lambda ( 引数1 引数2 ・・・) ( 関数本体 ) )
この定義文はそのまま関数として使えます。即ち、次の例のように直接引数をとることができます。
<例> ( ( lambda (x) ( + x x ) ) 4 ) → 8
lambdaをつかって関数の定義をすることもできます。
<例> ( define plus ( lambda ( x y ) ( + x y ) ) ) ;関数plusをlambdaを使って定義 (plus 2 4) → 6
<注> ;から行末まではコメントとみなされます。
(b) データ形式
式言語におけるデータ形式は、大きく分けると2種類に分類できます。即ち
- 単純データ(アトム)
- 対(ペア)およびリスト
の2種類です。
式言語においてはこの2種類のデータ形式の違いに特に注意する必要があります。
以下にそれぞれについて説明します。
<1> 単純データ(アトム)
式言語の最も基本となるデータ形式です。
対(ペア)およびリストも単純データ(アトム)が組み合わされたものと考えることができます。
単純データ(アトム)は、次の2種類に分類できます。
- 記号:関数名、変数名など、その名前で識別されるデータ。
- 即値:入力そのものを値とするデータ。#f、#t、数値定数、文字定数など。
<2> 対(ペア)およびリスト
対(ペア)およびリストとは、「car(カー)部」と「cdr(クダー)部」という2つの部分を持つデータ構造です。
2つのデータaとb に対して
( a b )
と書いたもので表現します。
データ「a」がcar部、データ「b」がcdr部を表します。
データとして許されるものは、単純データ(アトム)または対(ペア)およびリストです。
<例> ( ( a b) (1 2 ) ) ( 1 ( 2 3 ) ) ( 1 ( 2 ( 3 ( ) ) ) )
特に
( 1 ( 2 ( 3 ( ) ) ) )
のような形のものをリストといいます。省略形として
( 1 2 3 )
と書くことができます。
ここでの「1」と「2」と「3」を「リストの要素」といいます。
形式的には、”(“に対応する”)”があるとき、それらを省略することができます。
また、( ) は要素が一つもない「空リスト」です。
リストの要素にはリストや対(ペア)または単純データ(アトム)が許されます。
リストの要素が2つしかないものを特に対(ペア)と呼びます。
(c) quote
記号は通常、その記号名に対応する変数または関数として扱われます。
システムに定義されていない記号の値を評価するとエラーになります。
記号そのものを定数として扱いたい場合は quoteを用います。
( quote 記号 )
という形のリストで表すと定数としての記号が定義できます。
例えば、定数としての記号xは
( quote x )
となります。
quoteには省略形が用意されていて
( quote x ) は ’x
と書いても全く同じ意味となります。
リストに quote を付けて定数とすることもできます。
<例> ( quote ( a b c ) ) →( a b c )
(d) 条件式
ある条件にしたがって処理を分岐させたい場合に用いられるのが条件式です。
式言語での条件式において、#f以外は全て真として扱われます。
以下に主な条件式を示します。
<1> if式
構造は次のようになります。
( if 条件条件が真のときに実行される式条件が偽のときに実行される式 )
まず、[条件]が評価され、残りの式はそれぞれ条件が真(#t)あるいは偽(#f)のときにのみそれぞれ実行されます。
<例> ( if ( > 3 2) ’yes ’no ) → yes
<2> cond式
構造は次のようになります。
( cond ( 条件1 式1 ) ( 条件2 式2 ) : ( 条件n 式n ) ( else 式n+1 ) )
[条件1][条件2]・・・と順番に評価していき、真になる[条件i]を見つけたときに、対応する[式i]を評価し、式の値を返します。
どの条件にもあてはまらない場合には、else に対応した[式n+1]を評価します。
<例> ( cond ( ( > 3 2 ) ’greater ) ( ( < 3 2 ) ’less ) ( else ’equal ) ) → greater
<3> case式
構造は次のようになります。
( case key 式 ( 要素リスト1 式1 ) ( 要素リスト2 式2 ) : ( 要素リストn 式n ) ( else 式n+1 ) )
まず、[key式]を評価し、その結果を各要素リストと比較します。
[key式]を評価した結果が含まれるリストに対応した式が評価され、式の値を返します。
どの要素リストにもあてはまるものがない場合には、else の[式n+1]を評価します。
<例> ( case ( * 2 3 ) ( ( 2 3 5 7 ) ’prime ) ( ( 1 4 6 8 9 ) ’composite ) ) → composite
<4> and/or
andの書式は以下のようになります。
( and 条件1 条件2・・・条件n )
条件式を左から右に評価します。
偽になるものがあれば#fを返し、残りの式は評価されません。
すべての式が真となる場合には、最後の式の値が返されます。
<例> ( and ( = 1 1 ) ( > 1 2 ) ) → #f ( and ( = 1 1 ) ( < 1 2 ) ) → #t ( and ( = 1 1 ) ( * 1 2 ) ) → 2
orの書式は以下のようになります。
( or 条件1 条件2・・・条件n )
条件式を左から右に評価します。
真になる最初の式の値を返します。
残りの式は評価されません。
すべての式が偽となる場合には、#fを返します。
<例> ( or ( = 1 1 ) ( > 1 2 ) ) → #t ( or ( = 1 2 ) ( > 1 2 ) ) → #f ( or ( = 1 2 ) ( * 1 2 ) ) → 2
(e) 局所変数の定義
let、let*、名前つきletは、局所変数を定義するための式です。
letを用いた場合は定義した変数のスコープ(適用範囲)をそのlet式の内部に限定することができます。
<1> let
let式の構造は次のようになります。
( let ( ( 変数1 初期値1 ) ( 変数2 初期値2 ) : ( 変数n 初期値n ) ) 式 1 式 2 : 式 m )
まず、[初期値1]から[初期値n]が順番に評価されます。
その後、それぞれの評価値が[変数1]から[変数n]に代入されます。
そして、これらの変数を用いて、[式1]から[式m]が順に評価されます。
この時、[変数1]から[変数n]のスコープ(適用範囲)は、let式内部の[式1]から[式m]に限定されます。
<例> ( let ( ( x 2 ) ( y 3 ) ( z 4 ) ) ( + x y z ) ) → 9
let式の中で再びlet式を用いることもできます。
<例> ( let ( ( a 2 ) ( b 3 ) ) (let ( ( c ( + a b ) ) ( d ( * a b ) ) ) ( * c d ) ) ) → 30
<2> let*
let*式は、let式と同様の構造を持ちます。
しかし、let*の場合には逐次的に代入が行われます。
即ち、[変数i]は、直後の代入式 ( [変数(i+1)][初期値(i+1)] )以降のlet*式の内部で参照可能となります。
<例> ( let* ( ( x 2 ) ( y ( + x 3 ) ) ) (* x y ) ) → 10
この辺(letとlet*の違い)は少しややこしいのですが気持ちを落ち着けて使い方の違いをゆっくり考えてみてください。
<3> 名前付きlet
名前付きletは、次のような構文になります。
(let 名前 ( ( 変数1 初期値1) ( 変数2 初期値2) : ( 変数n 初期値n)) 式 1 : 式 n)
構文としてはletと同じです。しかし、名前を付けることによってletで使う変数を束縛し、関数における定義された変数のように扱うことができます。
<例> (let loop ( (i 0) (max 10)) (if (>= i max) ’() (cons i (loop (+ i 1) max)))) → (0 1 2 3 4 5 6 7 8 9)
let(レット)とlet*(レットアスタ)に加え、名前付きletまで登場し頭の中がパニックになってしまった方へ!あなたは、ごく普通の初心者です!!
DSSSLの式言語を学ぶ際に初心者の心にできる傷は概ね次の2つです。
一つ、丸かっこ()がたくさんありどこからどこまでを括っているか読みづらい。
一つ、変数がいきなり式の中で出てくるので面喰らう。
心に傷を負ってしまったかもしれない初心者の皆さん、安心してください。
今は難しく思われますが、自分が書く立場になれば、全く気にならなくなります。
しかし、ご自分で書けるようになった際は、読みやすく書くことを心がけてください。