SICP と Coursera でプログラミングの独習しはじめて6ヶ月くらい経った感想

独習し始めた動機

大学ではC言語でソートを実装する程度の実習だけしかやらず、コンピュータサイエンスをしっかりと勉強していなかった。 しかし、仕事でウェブサイトのテストをやることになり、JavaJavaScriptでテストコードを書く頻度も増えてきたので、 まじめに勉強しなおしてみようということで、定番の教科書 SICP を読むことにした。

計算機プログラムの構造と解釈

SICP Web Site for the Japanese Edition, Structure and Interpretation of Computer Programs 略して SICP

選んだ理由は、読み始めた当初はあまり意識していなかったが、振り返ってみると、以下の3つ。

  • Lisp をやっていたこと
  • 本が手元にあったが、積ん読状態にあったこと
  • 本の名前で検索してみて、多くのブログエントリがあって、本の内容がぼんやりと把握できていたこと。
独習する上で気をつけておくこと

必ず言葉の定義を写経する

最初はテキストだけを追っていっていたが、わからない単語についてメモしていく。 たとえば 遅延評価 という単語が出てくる。 「評価って何?」「何が遅れるの?」のように自問して、調べて、まとめたら、以降で詰まることは少なくなった。 ただ今でも、演習問題で「これどういう定義だったっけ」と読み返すことは多い。

関係ないけど、 オブジェクト志向の「オブジェクト」と、SICP で使われている「オブジェクト」の違いはなんだろうとも思った。

1ステップずつ進めていく

3章にある遅延評価の問題 3.52 を自分が REPL になって進めた。 紙に書くと、受験勉強のときみたいに手が汚れたので、途中でメモ書きにはタブレットでノートアプリを使うようにした。 編集が手軽にできるので、まとめやすい。

f:id:noorbeh:20171227172558p:plain:w180

こうみてみると、数学の問題を解いているようだ。時間はかかるが、言葉の定義と1行1行展開していく「作業」をすると、わからない部分がどこかわかるようになってきた。

演習問題については、後ろの章で問題に出たことが参照されるので、第3章までの問題については、 少なくとも、目を通しておいたほうがよい。あと、上にあげた日本語公式ページには、模範解答への隠しリンクが貼ってある。

SICPに関するエントリ

4章の途中まででも、抽象的に考えることを、徹底的に叩き込まれるので、 ITに関わる人なら、読んでみるといいです。

古い本なんで、多くの方がまとめていて、もっと探せばあると思いますが、 進めるにあたって見つけた記事の中で気に入ったものをいくつか紹介します。

「たしなみ」ではなく「たのしみ」としてのSICP - 思っているよりもずっとずっと人生は短い。 にある次の文にはハッと気づかされたし、賛同できます。

あらゆるプログラムは、本質として言語処理系なのです。

『計算機プログラムの構造と解釈』について はWebアーカイブですが、まさにこういう感想です。

IQの高い人間じゃないと理解できないに違いない、そもそも実用性がないんじゃないか、と思っていた。 が、やり始めてみると、すぐに病みつきになった。

最後は、弱LisperがMITでSICP(シクピー)を受講した結果 - Qiita です。 講義 6.037 - Structure and Interpretation of Computer Programs のサマリーで、ライブ感が伝わってきます。 YouTube の講義動画と一緒にどうぞ。

www.youtube.com

Coursera

SICP のほうもある程度、軌道にのってきたので、並行しつつ、 無料で受けられる Programming Languages というコースを取ることにした。 単位をもらうには、お金を支払う必要があるようだ。

www.coursera.org

Programming Language の構成について

Part. A, B, C に分かれていて、それぞれ ML, Scheme, Ruby でのプログラミング実習を行う。 そのため、各パートの初めの Week は環境設定について行う。 Part A は Week 5, Part B は Week 3まであって、各 Week ごと以下の流れで進めていく。

  1. 講義動画を見る
  2. レジュメを読む
  3. 課題を解き、提出する
  4. 課題が合格点なら、他の人の解答をレビューする

期限は1週間くらいあるけど、早く進めてもいいし、課題の提出が遅れても、採点してもらえる。 課題の採点は自動採点と他の受講者によるレビューの2段階で採点される。

レビューについては、こういう回答は5ポイントで、これができなかったら -1 ポイントのような 基準が課題ごとにあらかじめ決められているので、コードレビューの仕方についても学べるのは嬉しい。

内容について

まだ Ruby のパートは受けてないので詳細はわからないが、メタプログラミングについてやるようだ。 ML パートでは、関数型言語でのプログラミング作法を学び、Scheme パートでは、Scheme で ML でやったこと復習し、Lisp を実装する。 環境設定についてもしっかりレジュメがあり、ML は Emacs + SML/NJ で、Scheme パートは Racket を使用した。

内容的には、ML パートはリスト操作とパターンマッチが印象的だった。Java でも使えるようになると嬉しい。 一般に使われているプログラミング言語(JavaとかCとか)とMLの機能の比較についても触れているので、知っている人はより深く勉強できる。

いま振り返ると、先に Coursera でこのコースを試してみてから、SICP を進めたほうが進度は早かったかもしれない。

今後の予定

今後は、SICP もまだ4章と5章が残っているし、Coursera で残している Ruby のコースも進めたい。 あとは、せっかく Scheme をやったので、Scheme 手習い をやって、 Littile Prover でテストに役立ちそうなことを学びたい。

最後に

まだ独習は続きますが、仕事でコードを書いているときに Java の Stream を抵抗なく 利用できるようになったのは、良いです。

  • 「プログラミングやってみたいけど、どこから始めればいいんだろう」
  • 「〇〇というプログラミング言語を一通り学んでみたけど、次は何にとりかかろう」

というような状態なら、「定番」と世間で呼ばれているものに、飛びついてみてもいいかなと思います。

AtCoder Beginner Contest 062

テストケース実行マクロを導入して、テストも書きやすくなったのはよかった。
でも、A問題の提出のときに、なんども関数呼出をしないコードを提出してしまい、時間をロスしてしまった。
結局、C問題は解けなかったので、過去問を解いてなれていくしかないかな。

テストケース実行マクロについて

いちいち標準入力に入力するのはもったいないので、 動作確認用のユーティリティを定義した。前の defsolver とあわせて使う。

(defsolver solution-a (n)
  (dotimes (i n)
    (format t "~a~%" (1+ (read)))))

(run-testcases
 solution-a
 ((3 1 2 3) . (2 3 4))
 ((2 4 5) . (5 6)))

問題ページから例をコピペして、リストに整形してあげればよくなったので、楽になったと思う。

(defun %run-test (solver input)
  (with-output-to-string (*standard-output*)
    (with-input-from-string (*standard-input* input)
      (funcall solver))))

(defun run-test (solver input expected)
  (let* ((actual (%run-test solver input))
    (samep (equal actual expected)))
    (format t "~:[FAILS~;pass~]~%" samep)
    samep))

(defmacro run-testcases (solver &rest cases)
  `(progn
     ,@(mapcar
    #'(lambda (c)
        `(run-test
          #',solver
          ,(format nil "~{~a~%~}" (car c))
          ,(format nil "~{~a~%~}" (cdr c))))
    cases)))

意識はしていなかったけれど、つくり終えてみれば、 実践Common Lisp のテストフレームワークのような出来になった。 http://www.gigamonkeys.com/book/practical-building-a-unit-test-framework.html

記事をかいてておもったことは、testng の data provider みたいに

(bind-testcases solultion-a
  ...)

(run-testcases solution-a)

とできると、かっこいいかも。

AtCoder Beginner Contest 061

C問題に時間をかけて、B問題 (https://abc061.contest.atcoder.jp/submissions/1283256) しか解けなかった。素直にA問題から順番に解いていくべきだった。

  • HyperSpec を参照する回数が多かったので、その時間ももったいなかった。
  • slime のミニバッファに出る関数のシグネチャをみて、使い方がわかる程度には慣れておこう。
  • 標準入力の部分は似たようなコードパターンがなので、こんなマクロを用意してもいいかもしれない。
(defmacro defsolver (name vars &body body)
  `(defun ,name ()
     (let (,@(mapcar #'list
            vars
            (mapcar (constantly '(read))
                vars)))
       ,@body)))

次回は、今週末 http://abc062.contest.atcoder.jp/ で。