関数型言語の勉強にSICPを読もう - (36) 3章 - 小休止 port-foreach

Ruiさんよりコメントを頂きました。
Schemeに慣れた人が書くとこんなにもきれいなのか。ありがとうございます。
このようにコードを見せていただくことはとても勉強になります。

sum は (apply + list) と書けますね。Gaucheだとport-for-eachという便利な高階手続きがあって、それをつかうと全体は次のようになります。(一時ファイルを使わないで済むよう、call-with-input-fileの代わりにcall-with-input-stringを使いました。)

たしかに、sum は (apply + list)ですね。何で気づかなかったんだろう。

 (port-for-each (lambda (list)
                  (print (apply + list)))
                (call-with-input-string ”(1 2 3) (4 5 6)”
                  (lambda (in)
                    (lambda () (read in)))))

port-for-eachは、2番目の引数の手続きがeofオブジェクトを返すまで繰り返し呼び出して、1番目の引数の手続きにその値を渡す手続きです。こういった高階手続きをうまく使うと自分でループを回す必要が減ってプログラムが簡潔になります。


ぱっと見ると分からないので分解してみましょう。

(port-foreach 手続きA 手続きB)

という構造です。
手続きAは手続きBが eof まで返しつづける値を引数として受け取り何らかの処理を行います。
実は手続きBは port に絡まなくてもいつか eof を返せばよいらしいです。


次は、手続きBにあたる call-with-input-stringですが、これは文字列を第一引数とてして受け取ります。
これが input の port となって 第2引数の手続きが呼ばれます。
さらに第2引数が「 read をして eof を返す」手続きを返します。(うわこれSchemeっぽい)


というわけで一応理解できたつもりですが、自分で一から書けるかというとかなり不安なので何かしらの練習問題をやったほうがよさそう。



※「SICPを読もう」の目次はこちら


計算機プログラムの構造と解釈
Gerald Jay Sussman Julie Sussman Harold Abelson 和田 英一
ピアソンエデュケーション (2000/02)
売り上げランキング: 56,404

dot.gaucherc

http://www.fobj.com/hisa/diary/20060514.html#p02
これは便利。頂きました!

;; 結果表示
(define (displayln v) (display v) (newline))
(define (displayln-kv k v) 
  (display k) (display ": ") (displayln v))

追記

g:sicp:id:hyukiさんからトラックバックを頂きました。
define-syntaxを利用した debug という手続きを紹介されています。
define-syntaxって面白いですね。
http://sicp.g.hatena.ne.jp/hyuki/20060515/debug

さらに追記

Gauche release 0.7.3から、`.gaucherc'はgoshがスクリプトモードで 起動された時は読まれなくなりました。


とのことなので、スクリプトモードでデバッグ関数をあらかじめ有効にしておくお手軽な方法は

colinux% gosh -l /home/taro/sicp/lib/debug.scm 3.22.scm

こんな感じ?かな。
GAUCHE_DYNLOAD_PATHというのもやってみたんだけどうまくいかなかった違う用途かな?

関数型言語の勉強にSICPを読もう - (34) 3章 - 標準部品化力、オブジェクトおよび状態 (156ページ)

問題3.22

え?手続きで出来るの?と思って一瞬でも疑った自分を恥じます。
Scheme楽しいよ。楽しすぎるよ。

(define (make-queue)
  (let ((front-ptr '())
        (rear-ptr '()))
    ;; public interface
    (define (empty-queue?)
      (null? front-ptr))
    (define (front-queue)
      (if (empty-queue?)
          (error "empty queue")
          (car front-ptr)))
    (define (insert-queue! item)
      (let ((new-pair (cons item '())))
        (cond ((empty-queue?)
               (set! front-ptr new-pair)
               (set! rear-ptr new-pair))
              (else
               (set-cdr! rear-ptr new-pair)
               (set! rear-ptr new-pair)))))
    (define (delete-queue!)
      (cond ((empty-queue?)
             (error "empty queue"))
            (else
             (set! front-ptr (cdr front-ptr)))))
    (define (display-queue)
      (define (display-queue-internal q)
        (cond ((eq? q rear-ptr)
               (display " ")
               (display (car q)))
              (else
               (begin (display " ")
                      (display (car q))
                      (display-queue-internal (cdr q))))))
      (if (empty-queue?)
          (display "empty queue\n")
          (begin
            (display "(")
            (display-queue-internal front-ptr)
            (display ")\n"))))
    (define (dispatch m . args)
      (cond ((eq? m 'insert-queue!) (insert-queue! (car args)))
            ((eq? m 'delete-queue!) (delete-queue!))
            ((eq? m 'display-queue) (display-queue))
            (else (erro "hoge"))))
    dispatch))


;; make-queue test
(define q1 (make-queue))
(q1 'display-queue)
;; empty queue

(q1 'insert-queue! 'a)
(q1 'display-queue)
;;( a)


(q1 'insert-queue! 'b)
(q1 'display-queue)
;;( a b)


(q1 'delete-queue!)
(q1 'display-queue)
;;( b)


(q1 'delete-queue!)
(q1 'display-queue)
;; empty queue

問題3.23

まだ解いている途中なのですが、大変なことが発覚!
いつも答えあわせでお世話になっている、『計算機プログラムの構造と解釈 第二版』解答集(未完)を見ていたところ、なんと3.20以降は未完です。
つまり解答がないのです。
これからはまったくの未知領域だ。がんばります。


※「SICPを読もう」の目次はこちら


計算機プログラムの構造と解釈
Gerald Jay Sussman Julie Sussman Harold Abelson 和田 英一
ピアソンエデュケーション (2000/02)
売り上げランキング: 56,404

関数型言語の勉強にSICPを読もう - (35) 3章 - 小休止 外の世界とつながろうファイルを読む

#scheme-jp(wide系 IRCチャンネル)でのネタふりで、ファイルの読み込みを学んでみました。
Schemeは port を介して入出力するようです。
面白いのが read の戻り値がS式だということです。(これはひらっちさんから教えてもらいました。)
なのでファイルの中身がS式だといろいろと面白いことが出来そうなので作ってみました。


まず data.scm を用意します。

(list 1 2 3 4 5 6 7 8 9)
(list 1 1 1 1 1 1 1 1 1)


次はプログラム本体です。
data.scmを読み込んで、read した S式のlistの要素を sum して表示します。

(define (sum l)
  (if (null? l)
      0
      (+ (car l) (sum (cdr l)))))

(call-with-input-file "data.scm"
  (lambda (i)
    (define (f)
      (let ((l (read i)))
        (if (eof-object? l) #f
            (begin (display (sum (eval l '()))) (f))))) (f)))

これをやると 45 と 9 が表示されます。
read で読み込んだデータを eval してsumに渡しています。


で、思ったのですが

(1 2 3 4 5 6 7 8 9)
(1 1 1 1 1 1 1 1 1)

として、evalしないほうが自然ですね。
ひらっちさんの説明で、S式ってのはJSONみたいなもんですよってのが分かりやすくて面白かったです。


※「SICPを読もう」の目次はこちら


計算機プログラムの構造と解釈
Gerald Jay Sussman Julie Sussman Harold Abelson 和田 英一
ピアソンエデュケーション (2000/02)
売り上げランキング: 56,404