読者です 読者をやめる 読者になる 読者になる

/home/by-natures/dev*

ソフトウェア開発者として働く人の技術的なメモ

cond式の書き換え

(2009年11月17日に、以前のブログで書いた記事です。)

最近は大学に行ってSchemeでプログラムを組んでるのですが、Schemeを選んだ理由が

  • 慣れている
  • 字句解析が不要なので、プログラムの変換が容易

というものでした。そんな中で今回、cond式の書き換えを間違って行っていて、数日もやもやした気分でプログラムを進めていたので(笑)備忘録も兼ねてご紹介します。

cond式はC言語Javaで言えば、if や else if、つまり

if(a==0){
    処理1
} else if(a>0){
    処理2
} else {
    処理3
}

のような感じの式で、次のような形をしています。

(cond ((eq? a 0) 処理1)
      ((> a 0) 処理2)
      (else 処理3))

これを今行っている研究のために、if式へ全て変換します。C言語Javaでは次のように、if文の入れ子になると思います:

if(a==0){
    処理1
} else {
    if(a>0){
        処理2
    } else {
        処理3
    }
}

Schemeでは次のようになるでしょう:

(if (eq? a 0)
    処理1
    (if (> a 0)
        処理2
        処理3))

Schemeに馴染みのない方は、if式の形が妙に感じられるかもしれませんが、Schemeのif式は帰結部と代替部がelseなどで挟まれない形:

(if (条件式)
    帰結部
    代替部)

を取るので、見づらいですが正しい書式です。

ここで問題となったのは、一見単なるシンタックスシュガーに見えるcond式は、「処理1」「処理2」などとなっている部分が複数の式を許す点でif式と異なることです。if式は帰結部と代替部がカッコで覆われていないため、複数式書いてしまうとどれが帰結部でどれが代替部かわからなくなってしまいます。具体的にはcond式は次の形がとれます。(意味のない例ですみません...)

(cond ((eq? a 0) (display "a は 0 です")) 
      ((> a 0) (display "a は 0 より大きいです")
               (set! a (* a a)))
      (else (display "a は 0 より小さいです")
            (set! a (- a))))

aが正のときには2乗して、負のときは正になるようにしてみました。その前にそれぞれdisplay を読んで文字列を表示させているので、2つの処理が行われている部分があります。よって、これを考慮してcond式をif式に直すと次のようになります。

(if (eq? a 0)
    (begin 処理1)
    (if (> a 0)
        (begin 処理2)
        (begin 処理3)))

beginを使って、複数式実行できるようにしました。

少しSchemeを触ったことがある方なら当たり前に思われると思うのですが、変換しているときに複数式の場合までテストしなかったために、プログラム全体が完成した後に気付くことになりました。副作用がないような、ある種きれいなプログラムだとbeginを使わない変換でもうまく実行されてしまうため、プログラムによっては変な挙動を示したりしていました。

これは、if式のthenとelseで、式を一つしか書いてはいけないということです。C言語Javaに慣れている方は「そんなことありえない!」と思われるかもしれませんが、代入や副作用のないプログラムならば複数式書いても意味がないのです。例えば2つの式を並べて書いたとして、代入や副作用のないプログラムで、1行目の結果が2行目に影響することはありえません。
とは言っても、画面に文字を出力するdisplayなどは副作用があり、関数型言語でも代入を許している言語も多いので、そのような場合にはbeginでくくって複数式実行できるようにします。