Schemeのifを関数として書いてみるぜ

shi3zさんが書いた記事「Kahuaメモ またはなぜヤングにLispが向かないのか」のブコメで「ifは関数じゃないyo」とツッコミを受けてるのを見て「lambdaを使えば関数として書けるだろ」と思ったのがそもそもの発端。Wikipediaラムダ計算の項を見ると

TRUE := λx y. x
FALSE := λx y. y
IFTHENELSE := λp x y. p x y

と書いてある。楽勝。Gaucheで書き下してみる

;;オレオレ真
(define true (lambda (x y) x))

;;オレオレ偽
(define false (lambda (x y) y))

;;オレオレif
(define (my-if p x y)
  ((pred-conv p) x y))

;;真偽値コンバータ(汚なくてごめん)
(use srfi-13)
(define (pred-conv boolian)
  (eval
   (read-from-string
    (regexp-replace #/#t/
    (regexp-replace #/#f/
                    (x->string boolian)
                    "false")
    "true"))
   (interaction-environment)))

動作を確かめてみる

(define (odd-sample n)
  (my-if (odd? n)
         'kisuu
         'guusuu))

(odd-sample 1)
 kisuu

(odd-sample 2)
 guusuu

やった。ifを関数として書けたよ!



と思った人は
甘いです



実は問題大あり

(define msg "I am great!")

(my-if (odd? 2)
       (set! msg "I am fool!")
       'alternative-is-evaluated)
 alternative-is-evaluated

msg
 I am fool!

orz
(odd? 2)は偽だから本来は評価されちゃいけないthen節がしっかり評価(実行)されちゃってるよ!

何が起こっているのか

まあ、わかってる人には当たり前の話なんだけどね。

Schemeの関数は引数をもらった時点で中身を実行してしまう。つまり上の例では実行済みのthen節とelse節を振り分けてるだけで、どうしたってthen節とelse節の中身はどちらも実行される。これってifじゃないよね。

これを防ぐには、then節とelse節を引数として渡す前に保護してやるしかない。あらかじめlambdaでくるんでtrueかfalseの中でほどく。いわゆる「遅延評価」の超素朴版実装

;;xとyに()を付ける
(define true (lambda (x y) (x)))
(define false (lambda (x y) (y)))

;;my-ifを使う人が気を付ける
(define (odd-sample n)
  (my-if (odd? n)
         (lambda () 'kisuu)
         (lambda () 'guusuu)))

さっきのやつの実行結果はこんな感じになる

(define msg "I am great!")

(my-if (odd? 2)
       (lambda () (set! msg "I am fool!"))
       (lambda () 'alternative-is-evaluated))
 alternative-is-evaluated

msg
 I am great!

なんか美しくないなあ。やっぱりifは関数にしない方がいいね、という一周回ってなんだそれ、というオチ


【追記】
「if」みたいなやつを一般に「特殊形式(special form)」と呼ぶ。ほかの特殊形式としては「and」とか「or」がある。これらも受け取った式を必ずしも全部は評価しない。

【追記その2】
“trueやfalseの実体が手続きになっていることに違和感がある人がいるかもしれません。Schemerの手続きへの認識は、実はJavaScripterに近いんじゃないかなと思っています。”

とか書くとまたartonさんに喜んでもらえたかも、とふと思った。サービス不足だな、オレ
http://www.artonx.org/diary/20090621.html