Let's start Scheme

(はじめよう Scheme 3)

条件式

ある条件を満たす場合にはA、そうじゃない場合にはBということを書きたいときはいつでもあるだろう。お金が十分にあれば飛行機、そうじゃないなら徒歩といった感じである。そういった場合はif使う。
;; お金が十分にあれば飛行機で行こう
(if (has-enough-money? 100000)
    (by-aircraft)
    (by-walking))
if式が返す値は最終的に評価された式になる。上記の式で飛行機に乗れるだけのお金があった場合は(by-aircraft)が返す値となる。ifは以下の2つのパターンを取る。
  • (if <test> <consequent> <alternate<)
  • (if <test> <consequent>)
最初のパターンは例で示した場合。二つ目の式で<test>が偽の値(#f)を返した場合に返る値はR7RSでは未定義となっているが、大体未定義値という値が返ってくる。ここでは特に明示する場合を除いて未定義と書いたら未定義値と解釈してもらえばよい。
(if #f #t)
;; -> 戻り値:未定義
;; 処理系は何を返してもよいが、大体未定義値という値が返る。

設問 3.1
以下を埋めて上記の例を完成させよ。数値の比較には=<><=及び>=を用いる。それぞれ、等値、小なり、大なり、以下、以上をあらわす。
(import (scheme base))

(define money 1000)

(define (has-enough-money? cost) #| moneyと適当な飛行機代を比較する式 |#)

;; 以下は好きに実装してよい。例:displayを用いて何か表示する
(define (by-aircraft) #| 飛行機で行こう |#)
(define (by-walking) #| 歩いていこう |#)

(if (has-enough-money? 100000)
    (by-aircraft)
    (by-walking))

飛行機に乗れなかったら歩くというのはあまりにも極端である。それ以外の手段があってもいいだろう。電車はどうだろうか?
(if (has-enough-money? 100000)
    (by-aircraft)
    (if (has-enough-money? 1000)
        (by-train)
        (by-walking)))
このようにifをネストさせて書くこともできるが、条件が多くなると読みづらくなる。そんなときはcondを使う。上記のようなものはcondを使うと以下のように書ける。
(cond ((has-enough-money? 100000) (by-aircraft))
      ((has-enough-money? 1000)   (by-train))
      (else                       (by-walking)))
cond構文は以下のように定義されている。
  • (cond <clause1> <clause2> ...)
<clause>フォームは以下のいずれかを取る。
  • (<test> <expression> ...)
  • (<test> => <expression>)
  • (else <expression> ...)
最初のフォームは<test>の評価結果が真の値であれば、残りの<expression>が評価され、最後に評価された式の値が戻り値として返される。ここで気をつけたいのは<expression>は0個以上であるという点である。つまりそれが指定されなければ<test>の結果が返される。これはいろいろな場面で有用な場合があるので覚えておきたい。
(define n 100)
(cond ((= n 100) (display 'ok) (newline) n))
;; -> okを表示し変数nを返す
二つ目のフォームは<test>の評価結果が真の値であればそれを<expression>に渡して、<expression>の結果を戻り値として返す。Schemeでは#f以外は真の値として扱われるため、<test>を二度評価しなくてもよいようになる。
(define n 100)
(cond ((* n 2) => (lambda (n2) (display 'ok) (newline) n2)))
;; -> okを表示し変数n2を返す
定義では<expression>と書かれているが、実際には一つの引数を受け付ける手続きである必要がある。
これら2つのフォームはelseの前であれば何回でも現れてよい。
最後のelsecond式の最後に来なければならない。これは上記のフォームが全て偽の値を返した際に評価される。elseフォームがなく全ての条件が偽の値を返した場合の戻り値は未定義である。
condの条件式は順に評価されていくことに注意されたい。例えば、1000以下の数値の場合と100以下の数値の場合の処理を分けたい場合に、1000以下の処理を先に記述するとたとえ入力値が100以下でも評価されることはない。

設問 3.2
タクシーは電車で行くより高く、飛行機で行くより安い。by-taxiを定義し上記のcond式に入れよ。

否定


所持金が0でなければ募金するといったように条件を満たさない場合に何かをするというのは当然ある。
(if (= money 0)
    #f ;; 何もしない
    (donate money))
常にこのように書いても問題ないのではあるが、可読性が多少低い。プログラムを書く際に気をつけたいポイントの一つとして、コードから何をしたいのか読み取れるように書くというものがある。コメントに書いてもいいのではあるが、もっと直接的に所持金が0でないということを書いた方がよい。そんな場合に使えるのがnotである。
(if (not (= money 0))
    (donate money)
    #f)
notは受け取った値が真の値であれば偽の値(#f)を偽の値であれば真値(#t)返す。真の値と真値は本来同一の意味であるが、ここでは使い分けているので注意してほしい。真の値は偽の値ではない全ての値で、真値は#tである。以降は曖昧さを除くため、真値はすべて#tと書く。

whenとunless


ある条件を満たした場合もしくは満たさなかった場合にいくつかの式を実行したい場合がある。ifでは<consequent><alternate<はそれぞれ一つの式しか受け取れないので実現するには多少手を入れる必要がある(beginを使う。beginについては別の章でやることとする)。そんな場合にはwhenもしくはunlessを使う。
;; 正の数ならば
(when (positive? n)
  (display "n is ")
  (display n)
  (newline))

;; 正の数でないならば
(unless (positive? n)
  (display "n is ")
  (display n)
  (newline))

;; positive?は(scheme base)で定義されている手続きである。
when及びunlessの戻り値は未定義であるが、条件を満たした場合は最後に評価された式の戻り値である場合が多い。

andとor


複数の条件の全てもしくはいずれかを満たす場合に実行したい式というものもある。例えば、手に持っているものがボールでその色が赤いときには箱に入れるというような場合である。ifだけでも書けるのではあるが、こういう場合にはandを使うとよい。
(if (and (ball? obj) (red? obj))
    (put-it-in-the-box obj)
    (throw-it obj))
また、持っているものがボールもしくは石であれば投げるという場合にはorが使える。
(when (or (ball? obj) (stone? obj))
  (throw-it obj))
andは与えれれた式のいずれかが#fを返したらその時点で評価を終了し、#fを返す。そうでなければ最後に評価された式の戻り値を返す。orは与えられた式のいずれかが真の値を返した時点で評価を終了しその値を返す。そうでなければ#fを返す。andorのこの性質とcond=>を利用すると以下のようなことができる。
;; ものがボールかつ赤い場合には色を塗って投げる
;; paint-itは塗料の量によっては失敗するかもしれない。失敗の際は#fを返す。
(cond ((and (ball? obj) (red? obj) (paint-it obj 'blue))
       => (lambda (blue-ball) (throw-it blue-ball))))

;; 上記は以下のようにも書ける
(cond ((and (ball? obj) (red? obj) (paint-it obj 'blue))
       => throw-it))

設問 3.3
ball?red?paint-it及びthrow-itを定義して動作の確認をせよ。

No comments:

Post a Comment