if 式
if式は次の文法に沿って記述します.
(if test consequence alternate) syntax
(if test consequence) syntax
if 式は次のように評価されます.まず判定式(test)が評価されます.
その結果が真値(#f 以外の値)だったとき,
帰結式(consequence)が評価され,
その結果が if 式の評価結果として返されます.
また,このとき,代替式(alternate)は無視されます.
一方,判定式(test)の結果が #f だった場合,代替式(alternate)が評価され,
その結果が if 式の評価結果として返されます.
このとき,帰結式(consequence)は無視されます.
判定式(test)の結果が #f で代替式(alternate)がない場合,if 式の結果は unspecified です.
(if test consequence) syntax
test ::= expression consequence ::= expression alternate ::= expression expression ::= 式
cond 式
cond式は判定式(下記のtest)の結果に基づいて条件分岐(場合分け)を行うための構文です.cond 式は次の文法に沿って記述します.
(cond cond-clause+) syntax
(cond cond-clause* else-clause) syntax
cond 式は,おおまかに見ると
(cond cond-clause* else-clause) syntax
cond-clause ::= (test expression+) | (test) | (test => recipient) | (test guard => recipient) else-clause ::= (else expression+) test ::= expression expression ::= 式 recipient ::= 1引数の手続き guard ::= 手続き
(cond
(test$_1$ ...)
......
(test$_n$ ...)
または
(test$_1$ ...)
......
(test$_n$ ...)
(cond
(test$_1$ ...)
......
(else ...)
といった形式をしています.Guile はこのcond式を次のように処理します.
(test$_1$ ...)
......
(else ...)
- それぞれの判定式(test$_i$)を先頭から順に評価していって, 最初に真値(#f 以外の値)となるcond節(cond-clause)を見つけます.真なるcond節が見つかったら,そのcond節の帰結部( ... の部分)を評価して, その評価結果をcond式全体の返り値として返します.cond節の評価の仕方は,帰結部のパターンに応じて大きく異なるので,のちほど詳しく説明します.
- 真となる判定式(test$_i$)が1つもなく,cond式の最後にelse節(else-clause)が配置されているときには,else節の帰結部を評価して,その評価結果をcond式全体の返り値として返します.
- 真となる判定式(test$_i$)が1つもなく,else節もないときには,cond式の返り値は unspecified です.
- なお,cond節やelse節を選ぶために評価した部分と,cond式全体の結果を返すために評価した部分を除く他の部分は評価されません.
(test$_k$ ...)
または
(else ...)
とおきます.
(test$_k$ expression+) または (else expression+)
このパターンのcond節またはelse節を選んだとき, 帰結部(expression+)の式を先頭から順に評価していって, 最後の式の結果をcond式全体の評価結果として返します.(test$_k$)
このパターンのcond節を選んだとき,test$_k$ の結果をcond式の評価結果として返します.(test$_k$ => recipient)
このパターンのcond節を選んだとき,test$_k$ の結果を実引数として recipient を実行し,その返り値をcond式全体の評価結果として返します.次の点に注意して下さい.- test$_k$ は多値を返す式でもかまわないようです.多値を返す場合,Guileは recipient に1番目の値だけを渡してきます.2番目以降の値は捨てるようです.
- recipient は1引数の手続きでなければいけません. 厳密に言うと,可変的な引数を持っていてもかまわないのですが,いずれにしても Guile はrecipientに値を1つだけ渡すので,それを受け取れるものでなければいけません.
(test$_k$ guard => recipient)
このパターンはGuile固有の機能なのですが,上の3つのcond節とは動作が大きく異なります.なによりもまず,判定式(test$_k$)の真偽は無視され,test$_k$ の値を実引数として guard を実行してしまいます.そのため, このcond節を選ぶか否かは判定式(test$_k$)だけでは決まらず,guard の結果によって決まります. guard の返り値が #f の場合, このcond節をあきらめて,cond節を見つける処理に戻って, 後続するcond節($k+1$番目以降のcond節)の中から真値となるものを見つけに行きます. 一方,guard の返り値が真値(#f 以外の値)だった場合, 今度は test$_k$ の値を実引数として recipient を実行し,その返り値をcond式全体の評価結果として返します. 次の点に注意して下さい.- test$_k$ は多値を返す式でもかまいません.多値を返す場合,3番目のパターンとは違って,Guileは guard や recipient に多値そのものを渡します.
- guard や recipient は多値を受け取る手続きでもかまいません.
補足
ガードを使ったcond節がどういった場面で役に立つのかよく分からなかったのですが, 判定式(test$_k$)を2度以上評価したくないときに役立ちそうだということが分かりました.以下にそのような具体例を示します.;; use-guard.scm (define (check-ab-native n a b) (let loop ((n n)) (format #t "n=~A\n" n) (cond ((= n 1) #t) ((= (remainder n a) 0) (loop (/ n a))) ((= (remainder n b) 0) (loop (/ n b))) (else #f)))) (define (check-ab-guard n a b) (let loop ((n n)) (format #t "n=~A\n" n) (cond ((= n 1) #t) ((/ n a) (lambda (x) (integer? x)) => (lambda (x) (loop x))) ((/ n b) (lambda (x) (integer? x)) => (lambda (x) (loop x))) (else #f))))上記の2つの手続きは,いずれも,正整数 n と2以上の整数 a,b を受け取って, n が a$^i$b$^j$($i,j \geq 0$)の形をしていたら #t を返し,そうでなければ #f を返します.check-ab-naive はガードを使わずに素直に実現していて,check-ab-guard はガードを使って実現しています.check-ab-naive では,2番目と3番目のcond節は整数除算を2度行っています(余りと商を求めるため).一方,ガードを使ったcond節は整数除算を1度しか行いません.これがガードを使っている理由です.以下に実行例を示します.
guile> (load "use-guard.scm") ...... コンパイルメッセージ ...... guile> (check-ab-native 36 3 4) n=36 n=12 n=4 n=1 $1 = #t guile> (check-ab-guard 36 3 4) n=36 n=12 n=4 n=1 $2 = #t guile> (check-ab-guard 60 3 4) n=60 n=20 n=5 $3 = #f
具体例
FizzBuzz問題のプログラムを作ります.FizzBuzz問題とは,$k=1,2,...$ に対して次のように動作するプログラムを作成せよ,というプログラミングの問題です.- $k$が15の倍数のときには "FizzBuzz" を表示し,
- そうではなく,$k$が3の倍数のときには "Fizz" を表示し,
- そうではなく,$k$が5の倍数のときには "Buzz" を表示し,
- 以上のいずれでもないときには $k$ 自身を表示する.
;; fizzbuzz1.scm (define (fizz-buzz k) (define (divisible-by? m) (= (remainder k m) 0)) (cond ((divisible-by? 15) "FizzBuzz") ((divisible-by? 3) "Fizz") ((divisible-by? 5) "Buzz") (else k))) (define (fizz-buzz-main a b) (let loop ((k a)) (when (<= k b) (display (fizz-buzz k)) (newline) (loop (1+ k)))))
guile> (load "fizzbuzz1.scm") ...... コンパイルメッセージ ...... guile> (fizz-buzz-main 10 20) Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz
具体例
FizzBuzz問題のプログラムを, 判定式だけからなるcond節を使って無理やり実現してみます.;; fizzbuzz2.scm (define (fizz-buzz k) (define (divisible-by? m str) (if (= (remainder k m) 0) str #f)) (cond ((divisible-by? 15 "FizzBuzz")) ((divisible-by? 3 "Fizz")) ((divisible-by? 5 "Buzz")) (k))) (define (fizz-buzz-main a b) (let loop ((k a)) (when (<= k b) (display (fizz-buzz k)) (newline) (loop (1+ k)))))実行例は省略します.
具体例
FizzBuzz問題のプログラムを, 矢印(=>) を含むcond節を使って無理やり実現してみます. 下記の divisible-by? は k が m で割り切れるときには '("FizzBuzz" "Fizz" "Buzz") というリストを返し,そうでないときには #f を返します. 各cond節は,リストが返ってきたとき,car,cadr,caddr を使ってリストから文字列を取り出してcond式の返り値として返しています.;; fizzbuzz3.scm (define (fizz-buzz k) (define (divisible-by? m) (if (= (remainder k m) 0) '("FizzBuzz" "Fizz" "Buzz") #f)) (cond ((divisible-by? 15) => car) ((divisible-by? 3) => cadr) ((divisible-by? 5) => caddr) (else k))) (define (fizz-buzz-main a b) (let loop ((k a)) (when (<= k b) (display (fizz-buzz k)) (newline) (loop (1+ k)))))実行例は省略します.
case 式
case 式はキー式(以下のkey)の結果とデータム(以下のdatum)の比較に基づいて条件分岐(場合分け)を行うための構文です.case 式は次の文法に沿って記述します.
(case key case-clause+) syntax
(case key case-clause* else-clause) syntax
case 式は,おおまかに見ると次のような形式をしています.
(case key case-clause* else-clause) syntax
case-clause ::= ((datum*) expression+) | ((datum*) => recipient) else-clause ::= (else expression+) | (else => recipient) key ::= expression recipient ::= 1引数の手続き expression ::= 式 datum ::= what the read procedure successfully parses.補足:
- すべてのdatumは互いに異なっていなければなりません,
- 上記の datum の説明文は R7RS[7.1.2. External representations] からの引用です.
(case key
(($d_{1,1}$ $d_{1.2}$ ... ) case-clause$_1$)
......
(($d_{n,1}$ $d_{n.2}$ ... ) case-clause$_n$)
または
(($d_{1,1}$ $d_{1.2}$ ... ) case-clause$_1$)
......
(($d_{n,1}$ $d_{n.2}$ ... ) case-clause$_n$)
(case key
(($d_{1,1}$ $d_{1.2}$ ... ) case-clause$_1$)
......
(else ...)
ここで,$d_{i,j}$は datum を表しています.Guile はこのcase式を次のように処理します.
(($d_{1,1}$ $d_{1.2}$ ... ) case-clause$_1$)
......
(else ...)
- まず key を評価します.以下,その値を $val$ とおきます.
- 次に,$d_{i,j}$ を先頭から順に調べていって,eqv? の意味で $val$ と等しいものを探索します.
- $val$ と等しい $d_{i,j}$ が見つかったら,その $d_{i,j}$ を含むcase節(case-clause$_i$)を評価し,その評価結果を case 式の結果として返します.なお,case 節の評価の仕方については後述します.
- $val$ と等しい $d_{i,j}$ がなく,else節(else-clause)があるときには else 節を評価して,その評価結果を case 式の結果として返します.else 節の評価の仕方も後述します.
- $val$ と等しい $d_{i,j}$ がなく,else 節もないときには case 式の返り値は unspecified です.
((datum*) expression+) または (else expression+) の評価
これらの場合,expression+ の中の式を先頭から順に評価していって,最後の式の結果を case 式の結果として返します.((datum*) => recipient) または (else => recipient) の評価
これらの場合,keyの値を実引数として recipient を実行し,その返り値を case 式の結果として返します.次の点に注意して下さい.- keyは多値を返す式でもかまわないようです.多値の場合,Guileは,1番目の値だけを利用して2番目以降の値は捨てるようです.
- recipientは可変的な引数を持っていてもかまわないのですが, いずれにしても Guile はrecipientに値を1つだけ渡すので, それを受け取れるものでなければいけません.
記録(case 式の動作について)
数,文字,シンボルを datum とする case 式は機能するようですが,文字列やリストを datum とする case 式は機能しないようです. 以下に実行例を示します.;; Guile 3.0.5 guile> (case 30 ((10 20) 'first) ((30 40) 'second) (else 'third)) $1 = second guile> (case 'C ((A B) 'first) ((C D) 'second) (else 'third)) $2 = second guile> (case #\c ((#\a #\b) 'first) ((#\c #\d) 'second) (else 'third)) $3 = second guile> (case "c" (("a" "b") 'first) (("c" "d") 'second) (else 'third)) ;;; <stdin>:4:12: warning: datum "a" cannot be meaningfully compared using `eqv?' in clause (("a" "b") (quote first)) of case expression (case "c" (("a" "b") (quote first)) (("c" "d") (quote second)) (else (quote third))) ...... 同様のwarningメッセージが続きます(省略) ...... $4 = third guile> (case '(3 4) (((1 2)) 'first) (((3 4)) 'second) (else 'third)) ...... 上と同様のwarningメッセージが続きます(省略) ...... $5 = thirdwarningメッセージ(赤字)はナゾです. 素直に解釈するならば,case 式の datum として "a" や '(1 2) を指定しても無意味であると読めます.でも,R7RS[7.1.2. External representations]によれば, 文字列表記やリスト表記も指定できるはずです. おそらく,文字列やリストに限らず,eqv? の判定に場所(location)が関係する datum は使えないと思われます.もっとも,そういった case 式を使うことはないだろうと思うので,実用上困ることはないでしょう.
記録
datum に関して筆者が把握している実践上の注意点として, シンボルを datum とするとき, クォートを付けてはいけません.R7RS[7.3. Derived expression types] に示されている case 式のマクロ定義によれば,case 式を評価するとき datum のリストは
'(datum ... )
といったようにクォートを付けて処理されます.
そのため,シンボルにクォートを付けていると,そのクォートも datum の一部として処理されることになります.
具体例
datum としてシンボルを使った具体例を示します. 上で述べたように,シンボルにクォートを付けてはいけません. 以下の3番目の case 節ではわざとクォートを付けています.;; case-example.scm (define (case-test x) (case x ((Alice Carol Elena) "Hello") ((Bob David) "Hi") (('Kajya) "Hey") (else "who-are-you")))上でも述べたように,case 式を評価するとき,3番目の case 節にある ('Kajya) は
'('Kajya)
といったリストに置き換えて処理されると考えることができます.さらに,
シングルクォートは qoute 式の糖衣構文であることから,結局 ('Kajya) は
'((quote Kajya))
といった,(quote Kajay) を datum とするリストとして処理されることになります.
そのため,上記の手続きをコンパイルしてみると,ナゾのwarningメッセージ(リストを datum にしても無意味といった趣旨のメッセージ)が出てきます.
ただし,コンパイルは成功します.
guile> (load "case-example.scm") ...... コンパイルメッセージ ...... ;;; ...... : warning: datum (quote Kajya) cannot be meaningfully compared using `eqv?' in clause (((quote Kajya)) "Hey") of case expression (case x ((Alice Carol Elena) "Hello") ((Bob David) "Hi") (((quote Kajya)) "Hey") (else (quote who-are-you))) ...... コンパイルメッセージ(続) ...... guile> (case-test 'Elena) $1 = "Hello" guile> (case-test 'David) $2 = "Hi" guile> (case-test 'Kajya) $3 = "who-are-you"最後の手続き呼び出しは,Kajya を datum とする case 節はないことになるので,else 節を評価しています.
when 式 と unless 式
when 式と unlesse 式は次の文法に沿って記述します.
(when test expression+) syntax
(unless test expression+) syntax
(unless test expression+) syntax
test ::= expression expression ::= 式
when 式
Guile[6.11.2 Simple Conditional Evaluation] は,上記の when 式は
(if test (begin expression+))
といった if 式に等価である(とだけ),と述べています.
従って,when 式は,test の結果が真値(#f 以外の値)のとき,expression+ を先頭から順に評価していって,
最後の式の評価結果を返します.
test の結果が #f のとき,expression+ を評価せずに終了します.そのときの返り値は unspecified です.
unless 式
Guile[6.11.2 Simple Conditional Evaluation] は,上記の unless 式は
(if (not test) (begin expression+))
といった if 式に等価である(とだけ),と述べています.
従って,unless 式は,test の結果が #f のとき,expression+ を先頭から順に評価していって,
最後の式の評価結果を返します.test の結果が真値(#f 以外の値)のとき,expression+ を評価せずに終了します.
そのときの返り値は unspecified です.
記録
R7RS[4.2.1. Conditionals] は,when 式と unless 式の返り値は,test の結果に関わりなく unspecified であると述べています.一方,R7RS[7.3. Derived expression types] に示しているマクロ定義は,上で述べた if 式に翻訳できるとしています. 両者の間に齟齬があります.ちなみに,上で述べた返り値の説明は REPL によって確認した結果に基づいています.and 式,or 式,and=> 式,他
and 式,or 式,and=> 式は次の文法に沿って記述します.
(and expression*) syntax
(or expression*) syntax
(and=> expression procedure) procedure
(or expression*) syntax
(and=> expression procedure) procedure
expression ::= 式 procedure ::= 1引数の手続き
and 式
Guile は and 式を次のように評価します. expression を先頭から順に評価していきます. その結果が真値(#f 以外の値)である限り評価を続けます. ある式の結果が #f になったらその時点で直ちに評価を終えて,#f を and 式の結果として返します.一方,最後の式まで #f にならなかったら,最後の式の結果を返します. なお,expression を1つも指定しない (and) については #t を返します. expression は多値を返す式でもかまわないようです. 多値を返す式を途中に指定したときには,その結果の1番目の値だけが使われるようです. 一方,多値を返す式を最後に指定していて,かつ,その最後の式まで評価が到達したときには,そのすべての値が and 式の結果として返されるようです. これらの事項は REPL を使って確認しています.or 式
Guile は or 式を次のように評価します. expression を先頭から順に評価していきます. その結果が #f である限り評価を続けます. ある式の結果が真値(#f 以外の値)になったらその時点で直ちに評価を終えて, その真値を or 式の結果として返します. 一方,最後の式まで #f だったら or 式の結果として #f を返します. なお,expression を1つも指定しない (or) については #f を返します. expression は多値を返す式でもかまわないようです. 多値を返す式を途中に指定したときには,その結果の1番目の値だけが使われるようです. 一方,多値を返す式を最後に指定していて,かつ,その最後の式まで評価が到達したときには,そのすべての値が and 式の結果として返されるようです. これらの事項は REPL を使って確認しています.and=> 式
and=> は (ice-9 boot-9) モジュールの中で次のような手続きとして定義されています. 下記の value は上記の文法規則における expression のことです.(define (and=> value procedure) (and value (procedure value)))これより,and=> は,まずvalue(expression)を評価し, その結果が #f だったら #f を返します. 一方,value(expression)の結果が真値(#f 以外の値)たっだら, その真値に procedure を適用した結果を返します. 次の点に注意して下さい.
- value(expression)は多値を返す式でもかまわないようです. ただし,1番目の値だけが利用されて,2番目以降の値は捨てられるようです.
- procedure は可変的な引数を持つものでもかまわないようです. ただし,ちょうど1つの実引数を受け取ることのできる手続きでなければいけません.
具体例
fizzbuzz3.scm で示したプログラムは and,or,and=> を使って次のように作り変えることができます.;; and-or.scm (define (fizz-buzz k) (define (divisible-by? m) (and (= (remainder k m) 0) '("FizzBuzz" "Fizz" "Buzz"))) (or (and=> (divisible-by? 15) car) (and=> (divisible-by? 3) cadr) (and=> (divisible-by? 5) caddr) k)) (define (fizz-buzz-main a b) (let loop ((k a)) (when (<= k b) (display (fizz-buzz k)) (newline) (loop (1+ k)))))
guile> (load "and-or.scm") ...... コンパイルメッセージ ...... guile> (fizz-buzz-main 10 20) Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzzand や or を使うとプログラムがすっきりして見える気がします(効率的にどうなのかは分かりませんが).ただ,他言語(例えば,C言語)から来た人は最初は戸惑うだろうと思います.いまは慣れましたが,筆者も最初は戸惑いました.