コップ本メモ
コップ本の新版を読みながら学んだことをメモする
要素数が0個〜4個のイミュータブルな Set や Map は特別視されていて、Set の場合ならたとえば
- 0: EmptySet
- 1: Set1
- 2: Set2
- 3: Set3
- 4: Set4
- 5個以上: HashSet
に変わる。これは結構効率がいい。生成時に個数がある程度確定的で推論可能なイミュータブルコレクションだからできる芸当。
AnyRef は(JVM 上で言う)ヒープメモリにアロケートされる、と巻末に書いてあったので、では AnyVal はスタックメモリにアロケートされるのかな?と思ったら、やっぱりそうみたい?なので、よく値クラスを作る際にやる extends AnyVal
は効率よくメモリを使うためには結構重要だとわかった。
nominal subtyping と structural subtyping をそれぞれ「公称的サブ型」「構造的サブ型」と訳出されるのはやはり違和感があるし、巻末の用語集の方を直すべきだったのでは、と思いました……
ミュータブルなコレクションとイミュータブルなコレクションのどちらを使うべきか、という話だけど
- イミュータブル版は要素数が4までだと特別な実装が用意されていて、それのおかげもあって要素をコンパクトに格納できる傾向にある。
- ミュータブルで書いていた箇所が複雑になってきた場合に、イミュータブルなコレクションに書き換えると見通しがよくなる可能性もある。
- 一度イミュータブルで書いてみて、そこからミュータブルが必要であればミュータブルに直すという手順がよい。
共変の例。コンパイルは通るが、実行時例外になる。Scala では改善されている。
String[] a1 = { "abc" };
Object[] a2 = a1;
a2[0] = new Integer(17); // a1[0] = a2[0] = 17; ArrayStoreException; String が保存されていてほしい箇所に Integer を入れようとしたので。
String s = a1[0];
以下コップ本より。へーって思った。
Javaはなぜ危険でコストもかかるこのような設計を採用したのだろうか。Java言語の中心的な開発者であるジェームズ・ゴスリンは、このような質問を受けたときに、配列をジェネリックに扱う単純な手段が欲しかったのだと答えている。たとえば、次のようにObjectの配列を取るシグネチャーを使って、配列のすべての要素をソートするメソッドを書けるようにしたかったということだ。
void sort(Object[] a, Comparator cmp) { ... }
配列の共変は、任意の参照型の配列をこのsortメソッドに渡せるようにするために必要だったのである。もちろん、ジェネリクス(総称型)の登場により、Javaでもこのようなsortメソッドは型パラメータを使って書けるようになったので、配列の共変はもう必要ではない。しかし、互換性を維持するために、共変は今日のJavaにも残っている。
私の個人的な思想になってはしまうが、このような単純な代入ミスなどは、実行時に気づきたくはなく、コンパイルタイムで気づけると嬉しい。結局実行時に気づくエラーというものは、実行するまでわからないのだから、動作確認やテストの記述(当該箇所にカバレッジが確保されているか)を必要とする。が、人間はそういうことを往々にして忘れる。急いでいる際や、実務経験の少ないプログラマがとくにそういうミスをしてしまう傾向にある。
Scala や Rust、Haskell などで見られるコンパイル時の厳格な型チェックは、一見するとプログラムを速く記述できないから邪魔に思えるかもしれないが、そうした軽いミスが実行時に検出されて失う時間や費用と比較すると、非常に費用対効果の高いものだと思う。
書籍の中に登場する関数型キューと呼ばれる実装については、たしかに速度面ではこれで改善し、時間効率はよさそうかも知れないのだけど、メモリを倍使うことになるから普通に空間効率悪そうだなあと思ってしまった。
要素数がかなり多くなってきたときに通用しなさそうなのだけど、このあたりは何か別のワークアラウンドや手法があったりするのかは気になった。
ビューという機能をはじめて知った。IndexedSeqView
のような型を生成することができる。
これはいわゆる非正格なコレクションで、中間結果を生成させず、変換処理を先に定義していくことができる。
結構使いたいと思う場面は多く思い出せるが、あまり使ってこなかったような気がする。Stream
くらいしかこうした遅延評価コレクションを知らず、無理して使っていたかも。
ただ、なんでもかんでもビューにすればよいというわけではなくて、
- 結構要素数が多くないとビューを作ってクロージャーを適用するオーバーヘッドが、中間データ構造を作らなくても済むというビューの効果を上回ってしまう。
- 遅延評価される演算に副作用があると、ビューの評価はとたんに難しくなる(e.g. リストの中に HTTP を通じた API アクセスを含むとか)