クフでダローバルな日記

タフでもグローバルもない

『コーディングを支える技術』読書メモとか

大学四年生になって自分の勉強をする時間がようやく取れるようになったので、体系的な知識をつけるために技術書を読むことにしている。 ただ漫然と読んでいるとサラーッと読んでしまうので、人に説明できる程度には自分のものにしたかったので、自分用のwikiに読書メモをまとめている。 しかし、圧力がないと飽きてしまうのでとりあえずブログに公開してみることにした。

まとめ方は基本的に一つの節を一つの疑問文に直し、その答えを極力簡潔にその節の中からまとめる形。

以下自前wikiより転載。


コーディングを支える技術

読むことにしたわけ

  1. プログラミングについての知識を深める以前に、全体の俯瞰をしたかった

  2. 例外とかよく知らないなと思った

  3. 勉強法についても乗ってるっぽくて良さそうだった

読後の感想

☆5

もっと早く読むべきだったとも思うが、今読むことが出来て本当に良かった。 知ってはいるし使ってもいるが何となくも理解しきれていない技術がどのような歴史的経緯で発明されて、今後どこに向かっていきそうなのかがかなりよくまとまっていた。 オブジェクト指向の章は今まで読んだ中で一番納得感のある内容だった。

ただ、この本を読んだからと言って実装ができるようになるとは限らず、また細かいところも省略されているのであとは自分で調べる必要がありそう。

読書メモ

第1章 言語を深く効率的に学ぶには

タイトルの通り

  • 1.1 比較から学ぶ -why?

    • 言語ごとに共通な知識と違いを知ることが出来るから

    • RubyとCでtrueになるものの違い

  • 1.2 歴史から学ぶ -why?

    • なぜその機能が必要なのかを、言語設計者の視点から知ることが出来るから
  • 1.3 まとめ

第2章 プログラミング言語を俯瞰する

どうしてプログラミング言語が生まれたか?

  • 2.1 プログラミング言語誕生の歴史 -what?

    • ケーブルをつなぎ替える→テープでのプログラミング(機械語)→FORTRAN等で人間が読み書きしやすく、という歴史
  • 2.2 プログラミング言語の生まれた目的 -what?

    • 特定の目的を「楽」にするため

      • C++ならCと同等の速さのコードをきれいに書く

      • Schemeは言語仕様を最小限 仕様書全体で50ページ!

      • Pythonは読むのが楽

      • MLは言語処理が楽

      • etc

  • 2.3 まとめ

第3章 文法の誕生

なぜ文法が必要か?

  • 3.1 文法って何だろう?

    • 演算子の優先順位など、言語設計者が決めたルール

    • この章では演算子の様々な処理方法を比較する

  • 3.2 スタックマシンとFORTH -what

    • スタックに値を入れていって、演算子/関数が来たらスタックから値を取り出すもの

    • PythonとかJavaVMもスタックマシン型

  • 3.3 構文木LISP -what

    • 関数ノードの下に値が葉として生えてる木とその再帰的な組み合わせが構文木

    • 実は、LISPとFORTHは構文木を辿る順番が違うだけ

  • 3.4 中置記法 -what

    • 1+2 みたいな+の使い方

    • 同じ構文木を作るためにいろんな文法があるが、それぞれを実現するために適切な構文解析器(パーサ)が必要

  • 3.5 まとめ

第4章 処理の流れのコントロール

なぜ制御構造が必要か?

  • 4.1 構造化プログラミングの誕生 -what

    • ifとかwhileとかが生まれた → なぜ?
  • 4.2 ifが生まれる前 -what

    • アセンブリレベルではジャンプ(=Cでのgoto)を使っている。直感に反しており読みづらい。

    • elseも同様。楽で読みやすい方が良い!

  • 4.3 while ──繰り返しのifを読みやすく表現 -what

    • ifgotoだけで実現できるが、読みやすさ書きやすさが良い
  • 4.4 for ──数値を増やしながらのwhileを読みやすく表現 -what

    • forを使うとループの初期化と週条件とステップがまとまってるので意図が分かりやすい。foreachも同様の目的。
  • 4.5 まとめ

    • 使わなくても出来るけど、制御できるおかげで分かりやすい

第5章 関数

何故関数が必要か?

  • 5.1 関数の役割 - what?

    • 理解のため - いくつかの行を一塊にして名前をつけることで 組織 のようになる

    • 再利用のため - 部品としてコンパクトに再利用できる。

  • 5.2 戻る命令 - how?

    • 従来の goto 文だと戻ることができなかった。

    • 戻り先を書き換える方式だと上書きされたら戻れなくなる。

    • 戻り先をスタックに保存しておくことでネストした関数呼び出しにも対応できる。

  • 5.3 再帰呼び出し - why?

    • 入れ子構造のデータを扱うために必要。
  • 5.4 まとめ

第6章 エラー処理

なぜ例外処理が返り値を使う方法よりもよいのか?

  • 6.1 プログラムも失敗をする - what?

    • ファイルの書き込みの失敗などは起きうるが、それを伝える必要がある。
  • 6.2 失敗をどうやって伝える? - how?

    1. 返り値で失敗を伝える - 失敗を見落とす、エラー処理がifの中にあるのでコードが読みづらくなるなどの問題がある

    2. 失敗したらジャンプする - 失敗したときにジャンプする場所を決めておくことでコードの可読性は向上する。

  • 6.3 失敗しそうなコードを囲む構文 - why?

    • 例外の可能性を忘れる/不適切な例外処理を書くというもんだいを解決するためには、失敗しそうな処理を囲ってエラーをあとに書くという try ~ catch が必要。
  • 6.4 出口を1つにしたい - why?

    • try, catch だけでなく finally も生まれた。

    • 対になる処理(file の lock , unlock など)を絶対に行う必要があるから。

    • D言語では scope(exit) というスコープガードを発明。

  • 6.5 どういうときに例外を投げるか

    • 何が例外的状況なのかは一通りではない

    • 筆者としては、すぐ例外を投げてくれたほうが異常事態に気づきやすいので良い(フェイルファースト)

  • 6.6 例外の伝搬

    • 関数fの中で呼ばれた関数gで例外が発生したとき、gで処理されない例外はfで処理されるか、fの上の関数に伝搬していく。

      • ある関数が発生させうる例外を特定するためにはその関数が呼び出しているすべての関数をチェックする必要がある。
    • Javaの検査例外では、例外を投げる可能性のあるメソッドを呼ぶときにその例外を処理するか自分も投げるのかを明示する。

      • かなり良いが、Javaの実装だと苦しい。

      • 型システムでどうにかならないか?

  • 6.7 まとめ

第7章 名前とスコープ

なぜ名前の有効範囲を制限する必要があったのか?

  • 7.1 名前はなぜ必要だったか

    • メモリの番地よりも人にとってわかりやすい

    • 名前は衝突しうるので、長い変数名をつけることとスコープを利用することが対策としてうまれた。

  • 7.2 スコープの進化 - what?

    1. 動的スコープ - 入り口で元の値をとっておき、出口で戻す。呼び出された関数のスコープが呼び出し元と同じになってしまう。

    2. 静的スコープ - 関数ごとの名前と値の対応表を専用に持っているので、影響範囲を関数内だけに限定できる。←これが普通

  • 7.3 静的スコープは完成形?

    • ネストした関数でのスコープや、外の関数への束縛など問題は残っており、各言語が頑張って解決している
  • 7.4 まとめ

第8章 型

  • 8.1 型とは何か

    • ビット列をどのように解釈するのかを定めた追加の情報
  • 8.2 数値をオンとオフで表現する方法

    • 0~9をランプで表現するためには、位取りや7セグメント、そろばん形式など多くの形式がある
  • 8.3 1つの位に必要なランプはいくつか?

    • 4つで良いが、2新方で表せばもっと節約できる。
  • 8.4 実数はどうやって表現しよう

    • 固定小数点数ではどこまでを小数部に決めたかを覚えておく必要があり大変。

    • どこからか少数部かの情報自体を値に含めれば良い→浮動小数点数

  • 8.5 型は何のため?

    • コンピュータにとってはビット列が何を表しているかわからない→言語処理系で宣言しておけばどのように処理すればよいかわかる。
  • 8.6 型のいろいろな展開

    • 既存の型を組み合わせて作るユーザー定義型や、さらにデータの処理の仕方もまとめたクラスへと発展した。

    • 方があっているかはコンパイラがチェックできるので、型を仕様として用いることができるようになった。

    • 構成要素の型の一部が変わる型が生まれた。ex. Javaジェネリクス, Haskellの型コンストラクタ。

    • 動的型付け: 情報の種類と値をセットにした上でメモリに保存することで、言語処理系で宣言する必要がなくなった。

      • メモリ上で同じように扱えるように設計されている。

      • 柔軟な処理ができるものの、コンパイル時に型の整合性チェックができなくなった。

    • 型推論

      • 型チェックかつ型宣言なし

      • より強力な型推論によってバグがないことを証明しようとする研究もある

  • 8.7 まとめ

第9章 コンテナと文字列

「いくつものモノを入れるためのモノ」について、色々な種類がある理由とその長所と短所について。後半は特に文字列について。

  • 9.1 いろいろな種類のコンテナがある - what?

    • e.g. Cの配列、 Lispのリスト、 Pythonのタプル、Rubyのアレイ……

    • これらをまとめてコンテナと呼ぶ

  • 9.2 なぜいろいろな種類のコンテナがあるのか

    • それぞれのコンテナに長所と短所があるから。

    • ex. 配列と連結リストでは、操作によって効率の優劣が違う。

  • 9.3 辞書,ハッシュ,連想配列 - what?

    • 「キー: 値」という組み合わせ。

    • 万能のコンテナはない

  • 9.4 文字とは何か

    • これを文字と呼びましょうとし決めた記号の集合。「文字集合

    • 文字集合をデジタルに表現するための符号化の方法が文字符号化方式であり、結局UTF-8が標準になりそう。

      • 混じってると困る。
  • 9.5 文字列とは何か

  • 9.6 まとめ

    • 文字列の実現へのアプローチにはいくつかの方法があるが、実装で解決するだけでなく標準化での解決も進んでいる。

第10章 並行処理

同時に複数の処理が実行される時何が起こっているのか。どんな問題が起こりうるのか。

  • 10.1 並行処理とは何か

    • 複数の処理を時間軸上でオーバラップして実行すること。
  • 10.2 細かく区切って実行する -what

    • 実際には、 人間が気付かないくらい短い間隔で複数の処理を切り替えながら実行している
  • 10.3 処理を切り替える2通りの方法 -what

  • 10.4 競合状態を防ぐには -how

    • 競合状態が起こるには3つの条件がある。

      1. 2つの処理が変数を共有している。

      2. 少なくとも1つの処理がその変数を書き換える。

      3. 一方の処理が一段落付く前にもう一方の処理が割り込む可能性がある。

    • 3条件のどれかを解消すれば良い

      1. 共有しない: メモリを共有しない方法・メッセージを送り合う方法(アクターモデル)

      2. 書き換えない: Immutable パターン, const, val などなど

      3. 割り込まない: 協調的なスレッドを作る(RubyのFiberなど)、割り込まれたくないときは印をつける(ロック、mutex、セマフォ)

  • 10.5 ロックの問題点と解決策 -what

    • デッドロック・合成できない(アトミックに実行されて欲しい一連の処理を扱えない)

    • DBのトランザクション理論を転用して、トランザクショナルメモリというものができた。→これからどうなる?

  • 10.6 まとめ

第11章 オブジェクトとクラス

オブジェクト指向という言葉がさすものは言語によって異なります。」

  • 11.1 オブジェクト指向とは何か

  • 11.2 変数と関数を束ねて模型を作る方法

    • クラス以外の3つをまず以下で説明
  • 11.3 方法1 モジュール,パッケージ

    • 関連性の強い関数や変数のまとまりを表現するもの。

    • Perlでの例。最初はデータもモジュール(パッケージ)に含めていたが、データを外部のハッシュにし、blessでモジュールと紐付けて実現。

  • 11.4 方法2 関数もハッシュに入れる

    • JSでは関数もハッシュ(Object)に入れる。

    • ただObjectに入れるだけだと各Objectに関数があってメモリの無駄なので、prototypeに関数を入れておくことでオブジェクト指向を実現。

  • 11.5 方法3 クロージャ

  • 11.6 方法4 クラス

    • 言語によってクラスの主要な意味は異なるが、主に3つの意味がある。

      • まとまったものを作る生成器(上述の例)

      • どういう操作が可能かという仕様としてのユーザー定義型(C++での主目的)

      • 継承しコードを再利用する単位

  • 11.7 まとめ

第12章 継承によるコードの再利用

いろいろな継承の仕組みの長所と短所

  • 12.1 継承とは

    • 実装を自動で引き継ぐ考え方。主に3つの考え方がある。

      • 一般化/特殊化

      • 共通部分の抽出 - 複数のクラスの共通部分を親クラスとして抽出する

      • 差分実装 - 変更分だけ実装していく → コードの場所がわかりづらくなるという批判あり。

    • リスコフの置換原則(継承はis-aの関係でなければならない)

  • 12.2 多重継承 -what

    • 現実世界では、1つのモノが複数の分類に属することがほとんど。→多重継承
  • 12.3 多重継承の問題点──またしても衝突!

    • 多重継承した結果複数のクラスで同じメソッドを持っていたらどうするか?

      • 多重継承の禁止(Javaなど) - 委譲を用いてオブジェクトを持つにとどめる(依存性の注入)。インターフェースを用いる。

      • メソッド解決順序を工夫する(Pythonなど) - 深さ優先探索(Python2.3まで)、C3線形化(Python2.3以降)

      • 処理を混ぜ込む:Mix-in(Rubyなど) - 再利用したい機能だけを持った小さなクラスを混ぜ込む。(衝突は解決できない)

      • トレイト - 再利用の単位としての役割をインスタンス生成機としての役割から分離する。Rubyのmodule同様メソッドをまとめる束だが、mix-inでの衝突時に明示的にエラーにする。トレイトを利用するクラスに必要とするメソッドを明示しておく。

  • 12.4 まとめ