Scheme:ある日のプログラミング風景

Scheme:ある日のプログラミング風景

前フリ

WiKi風パーサを作っていたある日のこと、自分のプログラミング風景が画家っぽく見えたので我ながら感心してしまった。普段からやっていることだけど、ポールグラハムが言っていることと自分の作業がリンクしてると実感したのは初めて。せっかくだから記録しておくことにした。--hira

Step 1

行をパースして、同じ種類の行は一つのブロックにまとめたい。 でも「一つのブロックにまとめる」という部分がピンとこない。 行とかパースとかは置いといて、こんなリストで下書きしてみよう。 carが種類でcadrが行の内容とみなす。

(define s
  '((1 1)
    (2 2)
    (2 3)
    (3 4)
    (3 5)
    (1 6)
    (2 7)))

Step 2

行のパースはどうしよう。代わりにcadrを10倍することにする。 関数の仕様としてはこんな感じ。

  1. cadrを10倍する
  2. carが同じなら一つにまとめる

とりあえず、出力イメージを書き出してみる。 どんな関数になるかはまだ分からんのでテストケースは作れない。 関数名をうだうだ考えるにはまだ早すぎるし。

(define e
  '((1 10)
    (2 20 30)
    (3 40 50)
    (1 60)
    (2 70)))

Step 3

「2. carが同じなら一つにまとめる」がやりたくてしょうがない。 手っ取り早く吐き出しておく。 nの値をいじりながら、行がくっついたり離れたりする様を確認する。

(let ((c '(1 1))
      (n '(1 2)))
  (if (= (car c) (car n))
      (append c (cdr n))
      (values c n)))

Step 4

「2. carが同じなら一つにまとめる」その2。 リストをループしてみる。 普段は上から降ってくるlistのみを扱っていたが、今回は下から突っ返されたリストも扱う。 末端の関数では味わえない、中間管理職的苦悩&妙な罪悪感を感じる。

(let loop ((s s))
  (if (null? s)
      '()
      (let ((c (car s))
            (n (loop (cdr s))))
        (if (null? n)
            (cons c '())
            (if (= (car c) (caar n))
                (cons (append c (cdar n)) (cdr n))
                (cons c n ))))))

Step 5

caarとかcdarとかうるさいので、主要な処理を関数にする。 ついでに「1. cadrを10倍する」も実装する。 偽行パースの関数名はfunc1に落ち着く。保留ってこと。

(define (func1 c)
  (list (car c) (* 10 (cadr c))))

(define (same-block? a b)
  (= (car a) (car b)))

(define (marge-block a b rest)
  (cons (append a (cdr b)) rest))

(let loop ((s s))
  (if (null? s)
      '()
      (let ((c (func1 (car s)))
            (n (loop  (cdr s))))
        (if (null? n)
            (cons c '())
            (if (same-block? c (car n))
                (marge-block c (car n) (cdr n))
                (cons c n))))))

Step 6

関数名を決めて、高階関数にしてひと段落。 lines->blocksか。たぶん長くは持たないな、この名前。

(define (lines->blocks lines line-proc same-block? marge-block)
  (let loop ((s lines))
    (if (null? s)
        '()
        (let ((c (line-proc (car s)))
              (n (loop      (cdr s))))
          (if (null? n)
              (cons c '())
              (if (same-block? c (car n))
                  (marge-block c (car n) (cdr n))
                  (cons c n)))))))

Step 7

テストしてみる

(equal? e (lines->blocks s func1 same-block? marge-block))

とまあ、ここまで見てきたのだけれども、Javaと比べて何が違うのだろうか? やはり、なんの設計もしてないってことに尽きると思う。

要求→いきなり実装。ときどきリファクタリング。

「ときどきリファクタリング」てのは結局設計のことなんだけど、 そのコストをlazyに少しずつ支払っているのから気にならないし、 必要があってやってることなのでストレスも感じない。 高階関数が使えるおかげで抽象化のコストなど水や空気みたいなものだし 「インターフェースを定義してそれを実装するクラスを書かなきゃならんああめんどくさ」 みたいな気持ちにならなくて済む。ここまでやって、余計な出費は何一つない。 なんの準備もしなくて良いというこの「手ぶら感」が心地いい。 だから私はLispが好きなんだな。

おしまい。

議論など

hira: こんなん書いてみました。ホントはもっと試行錯誤してるけど、キリがないのではしょってます。


Last modified : 2004/06/09 23:28:08 UTC