Haskellerが勘違いして『Scheme初心者へのアドバイス』を書いてみた

Scheme初心者へのアドバイスを書いてみる - ヤドカリデンキ商会(第一倉庫)にインスパイアされて書いてみた.
ネタだからね真に受けないでね.
お題はServer error

トップダウンに考える

部品からいきなり作ってはいけません.その部品にひきずられてしまいます.トップダウンに考えます.
トップレベルの駆動手続 run を考えましょう.run は情報をもらって,結果を出力というのでよいでしょう.入力情報は対戦回数,結果は対戦結果の集計になるかな.

(define (run n)
  'undefined)

とりあえずインタフェースはこれでよし.で,n 回で対戦 game を繰り返すから,くりかえし回数,対戦結果の集計の初期値,そして対戦手続があればよさそう.

(define (run n)
  (iter/count game '(0 0 0) n)))

(define (iter/count f a c)
  'undefined)

(define (game a c)
  'undefined)

繰り返しのための高階手続 iter/count

まず繰り返し iter/count の引数は,

  • f : 直前まで集計結果と回数カウントをもらって1回分を集計結果に蓄積する手続
  • a : 集計初期値
  • c : 残りの繰り返し回数

の3つ.もし,

  • 残りの繰り返し回数が0なら,直前までの集計結果をそのまま返す.
  • そうでなければ,(f a c)の結果によって,集計結果を更新し,残り回数を減らして繰り返す.
(define (iter/count f a c)
  (if (= c 0)
      a
      (iter/count f (f a c) (- c 1))))

これは汎用的で十分再利用性があると思います.

対戦 game

対戦は手のリストで表現,ランダムに置く対戦は,*moves* すなわち '(0 1 2 3 4 5 6 7 8) の置換で表現できそう.というわけで対戦の勝敗を判断 judge する手続があれば以下のように書けます.

(define (game a _)
  (match a
    ((w l d) 
     (match (judge (shuffle *moves*))
       ('Win  (list (+ w 1) l d))
       ('Lose (list w (+ l 1) d))
       (_     (list w l (+ d 1)))))))

game は実質1引数ですが,iter/count で使うので2引数にしてある.shuffle を使うには (use gauche.sequence)を指定します.アドバイザはHaskellerだからパターンマッチを多用する(^o^).(use util.match) を指定しよう.

手の番号は 3×3 のマスめの上の行左のマスめから 0,1,2,... のように付番してあるものと仮定しておこう.直線にならぶパターンは'(0 1 2),'(3 4 5) など全部で8種類ある.直線にならぶパターン *win-patterns* はわかっているものとして,判定を行う手続を定義しよう.

(define (judge ms)
  (define (iter p q w l vvs)
    'undefined)
  (iter *win-patterns* *win-patterns* 'Win 'Lose ms))

直線になるパターンから置いたところを削除していく,先にパターンのどれかが空になった方が勝ち.2人の間で手が交互になることを引数の入れ替りで表現します.

(define (judge ms)
  (define (iter p q w l vvs)
    (match vvs
      (() 'Draw)
      ((v . vs) (let1 p- (map (cut delete v <>) p)
                  (if (any null? p-)
		      w
		      (iter q p- l w vs))))))
  (iter *win-patterns* *win-patterns* 'Win 'Lose ms))

あとは *moves* と *win-patterns* を定義すればよい.この程度ならハードコーディンで十分.(とかいって手抜きの言い訳)

(define *moves* '(0 1 2 3 4 5 6 7 8))
(define *win-patterns* 
  '((0 1 2) (3 4 5) (6 7 8)
    (0 3 6) (1 4 7) (2 5 8)
    (0 4 8) (2 4 6)))

全体のコード

結果表示は手抜きです.また,REPL内で run を走らせたときいつでも同じ乱数源だと結果に変化がない(これはこれで良い性質ではある)と実験には適さない.そこで run の実行ごとに乱数源を乱すようにしてある.

(use srfi-1)
(use srfi-27)
(use gauche.sequence)
(use util.match)

(define (run n)
  (random-source-randomize! default-random-source)
  (iter/count game '(0 0 0) n))

(define (iter/count f a c)
  (if (= c 0)
      a
      (iter/count f (f a c) (- c 1))))

(define (game a _)
  (match a
    ((w l d) 
     (match (judge (shuffle *moves*))
       ('Win  (list (+ w 1) l d))
       ('Lose (list w (+ l 1) d))
       (_     (list w l (+ d 1)))))))

(define (judge ms)
  (define (iter p q w l vvs)
    (match vvs
      (() 'Draw)
      ((v . vs) (let1 p- (map (cut delete v <>) p)
                  (if (any null? p-)
		      w
		      (iter q p- l w vs))))))
  (iter *win-patterns* *win-patterns* 'Win 'Lose ms))

(define *moves* '(0 1 2 3 4 5 6 7 8))
(define *win-patterns*
  '((0 1 2) (3 4 5) (6 7 8)
    (0 3 6) (1 4 7) (2 5 8)
    (0 4 8) (2 4 6)))