Top > ScriptFuClass > Step4

四限目 よく使われる構文 Edit

最初に Scheme の最も基本的なところだけを説明しました。しかし変数やリスト以外にも重要な構文は沢山あります。全てをカバーすることはできないので、Script-Fu を書く上でその次に重要であろうと思われるものの説明をします。

4.1 比較 Edit

この後で出てくる繰り返し文や条件分岐では、特定の条件を満たしているかどうかを判定する必要が出てきます。この判定は両者を比較して成立条件を満たしているかどうかを判別することで行います。例えば二つの数値を比較してみます。

4.1.1 比較式 Edit

数値の比較では <、>、= の等号/不等号記号を使います。この場合も記号を前に置いて、比較するものをその後に置きます。2 と 3 を比較してみましょう。Script-Fu コンソールに (< 2 3) と (> 2 3) を入力して下さい。

> (< 2 3)

#t

> (> 2 3)

#f

それぞれ #t と #f が返ってきましたね。これは評価式が真 (TRUE) であるならば #t が返ってきて、評価式が偽 (FALSE) であるなら #f が返ってきているのです。#t と #f は真偽値を現します。

上に式での (< 2 3) は 2 よりも 3 が大きいのでこの式は成り立ち、#t と返されます。一方 (> 2 3) では 2 が 3 よりも大きいというのは間違っているので #f と返ってきます。

2 以上の大きさであるかどうかを判定する方法は、<= を使います。下の例では #t と返ってきますが、等号 = を無くすと #f と返ってきます。2 が含まれるか含まれないかの違いです。

> (<= 2 2)

#f

> (< 2 2)

#f

4.1.2 等価の判定 Edit

では、数字の比較ではなく文字列の比較の場合ではどうでしょうか? 通常文字列の比較では両者の大小関係というよりも、両者が同一のものであるかどうかを判定するために使います。同一であるかどうかは、equal? を使います。例えば、このようにしてみます。

> (let* ((name1 "wilber") (name2 "wilma")) (equal? name1 "wilber"))

#t

> (let* ((name1 "wilber") (name2 "wilma")) (equal? name2 "wilber"))

#f

ここでは name1 と name2 にそれぞれ文字列 wilber と wilma を入れて、name1 と name2 が文字列 wilber と等しいかどうかを判定しています。name1 には wilber が入っているので #t が返ってきて、name2 には wilma が入っているため間違いなので #f が返ってきます。

比較には equal? の他にも eq? や eqv? があります。何の一致を確かめたいのかによって使い分けると良いでしょう。ちなみに、Gimp において eqv? は = と等価に扱われます。

上の判定文を段落をつけて記述すると次のようになります。

(let* ((name1 "wilber")
       (name2 "wilma"))
  (equal? name1 "wilber"))

4.2 繰り返し構文 Edit

4.2.1 while Edit

Script-Fu で用いられている繰り返し文は、while だけです。将来の Gimp のバージョンで Guile を実装することになれば他の繰り返し文を使えるようになりますが、いまのところは while しかありません。while 構文はこのように書きます。

(while (成立条件) (評価式) (評価式) ... )

成立条件が成り立っている間は、それ以降に書かれている評価式を順番に評価していきます。while 構文の返り値は、最後の評価式を評価した結果です。例えば 1 から 10 までの数字を足してみます。

> (let* ((i 1) (j 0)) (while (<= i 10) (set! j (+ j i)) (set! i (+ i 1))) i)
 
11

> (let* ((i 1) (j 0)) (while (<= i 10) (set! j (+ j i)) (set! i (+ i 1))) j)

55

> (+ 1 2 3 4 5 6 7 8 9 10)

55

ここでは最初に i と j に 0 を入れてあります。そして while の条件判定 (<= i 10) では i が 10 以内であれば繰り返し、11 以上であれば終了するようにしています。

繰り返されている式は二つあります。最初の (set! j (+ j i )) では、j に j と i を加算したものを入れています。次の (set! i (+ i 1)) では i を 1 ずつ増やしています。i が 11 になったところで先程の条件式は成り立たなくなるので while の繰り返しは終了します。

i は 11 になったところで終了したので 11 です。j には i を加算していった値が入っており、55 となっています。この加算の結果が本当に正しいのかどうかを確かめるため、その次に (+ 1 2 3 4 5 6 7 8 9 10) として直接 1 から 10 までの数字を並べて足し算をして合計を出しました。両方の結果が一致していますね。

Script-Fu コンソールではどんなに長い式でもコマンドラインの一行に収めなければならないので、式がどのような構造になっているのか把握が難しいと思います。コンソールに入力ではなく、実際にスクリプトに書く時は複数行に分けてインデントをつけながら書くと見やすくなるでしょう。今の加算の場合では、このようになります。

(let* ((i 1)
       (j 0))
  (while (<= i 10)
    (set! j (+ j i))
    (set! i (+ i 1)))
 i)

このように "ある一定回数だけ処理を繰り返す" というのは、while 文の前にカウント用の変数に 0 (または 1 など) を入れておいて、while 文の中の最後でカウント用の変数の値を 1 ずつ加算してループさせるという手がよく使われます。

4.3 条件分岐 Edit

4.3.1 if Edit

条件分岐にはいくつかの方法がありますが、ここでは if と cond を説明します。if 構文はこのようになっています。

(if (成立条件) (成立したときの評価式) (成立しないときの評価式))

もし条件式が成立したら、成立した時の評価式を評価を行い、成立しなかったら成立しない時の評価式を評価します。成立条件式は真偽値をもたらすものであればどのようになっていても構いません。if 構文の返り値は、成立した時/成立しなかった時の評価式を評価した結果です。例えば数字を比較して大小を判定するにはこうします。

> (let* ((atai 5) (kekka "")) (if (> atai 6) (set! kekka "High") (set! kekka "Low")))

"Low"

> (let* ((atai 5) (kekka "")) (if (> atai 4) (set! kekka "High") (set! kekka "Low")))

"High"

これは atai に 5 を入れ、atai が比較する数字よりも大きければ kekka に文字列 "High" を入れ、そうでなければ "Low" を入れています。if 構文を実行した時に返ってくるのは、条件を分岐させた時に行った評価式の結果が返ってきます。set! を行ったときには代入したものが返ってくるので "High" と "Low" という文字列が返ってきます。

そのため (> atai 6) の時には条件が成り立たないので後者を評価した結果の"Low" が返ってきて、(> atai 4) の時は成り立つので前者を評価した結果の "High" が返ってきています。これをインデントをつけて書くとこうなります。

(let* ((atai   5)
       (kekka ""))
  (if (> atai 4)            ; 条件判定
      (set! kekka "High")   ; 条件が成立した時
      (set! kekka "Low")))  ; 条件が成立しなかった時

4.3.2 cond Edit

cond では複数の条件式を並べておき、最初に見付かった条件を満たす式が選ばれ、その式を評価した結果が返ってきます。

(cond (条件1 評価式1) (条件2 評価式2) (条件3 評価式3) ... (評価式))

その条件を満たした場合に行う式を続けて書きます。どの条件も満たさなかった場合のために、最後に受け皿となる評価式 (いわゆる else 節) を書くこともできます (最後の評価式はなくてもよい)。例えばこのように書きます。

(let* ((seiseki 50))
  (cond ((= seiseki 100) "Perfect!")
        ((>= seiseki 70) "Very good!")
        ((>= seiseki 50) "Good!")
        ((>= seiseki 30) "Bad!")
        ('else "No comment...")))

この場合上から順番に評価していき、seiseki が 100 だった場合には "Perfact!" を、70 以上だった場合には "Very good!" を、50 以上だった場合には ”Good!" を、30 以上だった場合には Bad!" を出力します。どの条件にも当てはまらなかった場合 (seiseki が30 より下) には "No comment..." を出力しています。詳しい説明は省きますが、'else はアトムです。また、'else の代わりに "#t" を使ってもいいでしょう。#t は真を意味するので、必ず条件が成り立っていることになります。

4.3.3 and と or Edit

and はそれに続く評価式に一つでも偽があればそこで評価を止めて偽を返します。全てが真であれば最後に評価した式の結果を返します。一方 or はそれに続く評価式のどれか一つでも真であればそこで止めて真を返します。全てが偽であれば偽を返します。複数の条件が一度に成り立っているかどうかを判定する場合に使うとよいでしょう。

(and (評価式1) (評価式2) (評価式3) ... )

(or (評価式1) (評価式2) (評価式3) ... )

and と or の場合、評価式を書く順番が意味を持ちます。and では偽となる式が、or では真となる式が見付かった場所以降の評価式は評価されません。

4.3.4 begin Edit

これは条件分岐の式ではありません。しかし、複数の式を書かなければならないことが多い条件分岐の場面で begin がよく使われているのでここで取り上げました。begin は複数の式を順番に評価します。最後の式を評価した結果が返ってきます。

(begin (評価式1) (評価式2) (評価式3) ... )

評価式を順番に全て評価していきます。if 文や cond 文ではその条件が成立した時に取り得る評価式は一つですが、begin を使ってその一つの式の中に複数の式を書くことができるようになるのです。

4.4 局所変数 Edit

4.4.1 let* と let Edit

最初の Scheme の説明のところで、set! による変数に値を入れる方法を紹介しました。set! で定義された変数は、大域変数となります。大域変数とは、変数の適用範囲が全体にわたって有効であるという意味です。

このような大域変数に対して、変数の有効範囲がある範囲内に限定されているものを局所変数と呼びます。局所変数の定義は let* 構文などを用いて行います。

(let* ((変数1 値1) (変数2 値2) ... ) (評価式1) (評価式2) ... )

let* 構文では最初に変数に値を入れたものを括弧で囲み、その後に評価式が並びます。ここで定義した変数は let* 文の括弧の中だけで有効で、括弧の外に出てしまうと無効 (通常はエラー) になります。let* 構文を使った時の返り値は、一番最後の評価式を評価した結果が返ってきます。

> (let* ((x 5) (y 7) (z 9)) (+ x y z))

21

上の式はコンソールに入力するため一行に収めましたが、インデントをつけて複数行で書くとこのようになります。どの括弧がどこの括弧に対応しているのかに注意して下さい。

(let* ((x 5)
       (y 7)
       (z 9))
  (+ x y z))

let* 構文と同じものとして let 構文があります。両者はほとんど同じなのですが、let 構文では宣言した局所変数を用いて次の局所変数を宣言できないところに違いがあります。

(let ((x 6)
      (y 7)
      (z (+ x y)))
  (+ x y z))

ここでは x に 6 を、y に 7 を入れて、x と y を足し算したものを z にしたいのですが、let では宣言したばかりの変数を使うことができないのでエラーになります。let* では x と y を z の宣言のために使うことができることを確認して下さい。

4.5 新しい関数の定義 Edit

4.5.1 define Edit

新しい関数の定義は define 文を使います。最初に作ったスクリプトのところで何気なく出ていますが、あれは新しい関数を作ったのです。define の式はこうなります。

(define ((関数の名前) (仮引数1) (仮引数2) ... ) (評価式1) (評価式2) ... )

関数の名前には新しい関数の名前を付けます。任意の名前をつけることができますが、ユニークな (重複しない) 名前にする必要があります。その後にはこの関数の仮引数を並べます。仮変数には関数を呼び出した時の引数 (実引数) が順番に入ります。その後には関数の処理を書きます。define の返り値は、最後の評価式を評価した結果が返ってきます。

define 文を実際にやってみると分かりやすいでしょう。数値の自乗を返す関数を作ってみます。

> (define (jijyou x) (* x x))

jiyou

> (jijyou 4)

16

> (jijyou 9)

81

ここでは jijyou という名前の関数を作りました。jijyou という関数は引数を一つだけ必要とします。そして引数を自乗したものを返します。jijyou 関数の呼び出しは、jijyou に続けて引数となる数値を与えることでできます。

4.5.2 lambda Edit

lambda (ラムダ) 式は名前のない関数を定義します。それ以外は define と同じで、最後に評価した式の結果を返します。

(lambda ((仮引数1) (仮引数2) (仮引数3) ... ) (評価式1) (評価式2) (評価式3) ... )

lambda 式に仮引数の数だけ引数を与えることで利用します。例えば与えた引数に 1 を加算する手続きはこのように書きます。

> ((lambda (x) (+ x 1)) 0)

1

> ((lambda (x) (+ x 1)) 5)

6

4.6 再帰呼び出し Edit

再帰呼び出しとは、関数がその関数の中で自分自身を呼び出すことです。Scheme では再帰呼び出しが認められており、Script-Fu でも使用することができます。これによってさらに高度なスクリプト処理が可能になります。

再帰呼び出しは、プログラミングの本などではハノイの塔を例としてよく取り上げれているようです。ここでは再帰呼び出しによって計算をさせてみます。この関数に数値 n を与えると、n から 1 まで階乗した結果を返します。

> (define (kaijyou n) (if (<= n 0) 1 (* n (kaijyou (- n 1)))))

kaijyou

> (kaijyou 5)

120

この kaijyou という関数は n が 0 以下の場合には 1 を返し、0 より大きかった場合には 1 だけ値を小さくして自分自身を呼び出します。この繰り返しによって n! を、つまり 1*2*3* [...] *(n-2)*(n-1)*n を計算させているのです。最初の引数として 5 を与えた場合の計算過程はこのようになります。

(kaijyou 5)
=> (* 5 (kaijyou 4))
=> (* 5 (* 4 (kaijyou 3)))
=> (* 5 (* 4 (* 3 (kaijyou 2))))
=> (* 5 (* 4 (* 3 (* 2 (kaijyou 1)))))
=> (* 5 (* 4 (* 3 (* 2 (* 1 (kaijyou 0))))))
=> (* 5 (* 4 (* 3 (* 2 (* 1 1)))))
=> (* 5 (* 4 (* 3 (* 2 1))))
=> (* 5 (* 4 (* 3 2)))
=> (* 5 (* 4 6))
=> (* 5 24)
=> 120

引数を 5 にしてこの関数を呼び出した場合、引数の 5 が最初に n に入って if 文で判定されます。0 よりも大きいので n から 1 を引いた値、つまり 4 を引数として自分自身を呼び出します。次の引数は 4 で、これも 0 よりも大きいので 3 を引数として自分自身を呼び出します。

これを繰り返し、引数を 1 として自分を呼び出した時の次には 引数を 0 として自分自身を呼び出すはずです。この時の引数は 0 なので、ここでようやくはじめて 1 という値が返ってきます。これは引数を 0 として呼び出した時の返り値ですから、これが求まれば引数を 1 とした時の引数が求まり、(* 1 1) の計算ができます。この結果は引数を 1 として呼んだ時の返り値ですから、引数を 2 とした時の引数が求まり、(* 2 1) の計算ができます。それ以降は順番に最初の引数を 5 とした時まで求めることができます。

上の再帰呼び出しをインデントを付けて書き直したものがこれです。非常にシンプルですが、結構高度なことをやっています。再帰呼び出しを使用する場合は、ループを抜け出す条件に注意するようにしましょう。

(define (kaijyou n)
  (if (<= n 0)
      1
      (* n (kaijyou (- n 1)))))


Diff Freeze Rename Backup   RSS of recent changes
Last-modified: (847d)