PhpStormと僕

日々周りを巻き込むことをモットーに。気まぐれでJetBrains製のIDEネタとか書いてます。

Scalaコップ本メモ

そんなわけで最近Scalaを書き始めているわけですが、1ヶ月ほど前に今更ながらScalaスケーラブルプログラミング(通称コップ本)読んだので、その過程のメモ書きをまとめてみます。

ちなみに全部じゃなくて17章まで読みました。

実際書き始めてみると全然理解しきれてないところも多いのでもう一周読みなおそう・・・

Scalaスケーラブルプログラミング第2版

Scalaスケーラブルプログラミング第2版

コップ本メモ

  • Scalaは、配列から式に至るまで、あらゆるものがメソッドを持つオブジェクトだとすることによって、概念を単純化している(P62)

  • メソッドは副作用を持っていてはならない」というのは、関数型プログラミングの考え方の大きな特徴である。メソッドの作用は、計算して値を返すことだけでなければならない。このアプローチをとると、メソッドが入り組んだものにならないため、信頼性が上がり、再利用しやすくなる。(P63)

  • (関数型スタイルに慣れる)第一歩は、コードに現れる2つのスタイルの違いを理解することである。簡単な見分け方が1つある。コードにvarが含まれていたら、それはおそらく命令形のスタイルで書かれている。コードにvarが含まれていなければ、つまりvalだけが使われていれば、それはたぶん関数型のスタイルで書かれている。だから、関数型のスタイルに近づくための1角方法は、varを使わずにプログラムを書く努力をすることだ。(P72)

  • 関数が副作用を持つかどうかを簡単に見分けるには、結果がたがUnitかどうかを見ればよい。Unitとなっていれば副作用がある。Unitという結果がたは関数が意味のある値を返さないことを意味する。(P73)

  • Scalaは、toStringやHashSetといったキャメルケースの識別子を使うというJavaの習慣に従っている。アンダースコアは識別子の中で使ってよいことになっているが、Javaとの一貫性を保つため、またScalaコードではアンダースコアが識別子以外の目的でよく使われているため、Scalaプログラムの識別子にはあまりアンダースコアを使わない。(P121)

  • Scalaでは定数という単語はvalを意味するわけではない。valは初期化された後は一定だが、それでも変数と呼ばれる。たとえば、メソッドのパラメーターはvalだが、メソッドが呼び出されるたびに、これらのvalは異なる値を保持して良い。(P121)

  • valを使うチャンスを探そう。valはコードを読みやすく、リファクタリングしやすいものにしてくれる。(P129)

  • 一般に、varを避けるのと同じように、whileループも避けることをお勧めする。実際、whileループとvarはセットになっていることが多い。コードの中のwhileループには、疑いの目を向けてみるべきだ。whileやdo-whileを使う正当な理由が説明できないときは、これらを使わない方法を探してみよう (P131)

  • files <- filesHere というような構文はジェネレーター(generator)と呼ばれ、filesHereからの要素に対する反復処理に使う (P132)

    • はじめて呼び方出てきた
  • breakやcontinueを使わずにプログラムを書く方法はいくらもある。そして、関数リテラルを利用すれば、元のコードよりも短いコードが書けることが多い。もっとも簡単な解決方法は、すべてのcontinueをifに、すべてのbreakをBoolean変数に置き換えることだ。 (P141)

  • Scalaのスコープ規則はJavaのものとほとんど同じだということが分かるだろう。JavaScalaの違いは、Scalaでは入れ子になったスコープで同じ名前の変数を定義できることである。 (P143)

  • ScalaJavaの違いとして注意すべきなのは、Javaでは、外側のスコープ変数と同じ名前を持つ変数を内側のスコープでは作れないことである。Scalaプログラムでは、外側の変数は内側のスコープでは見えなくなるので、内側の変数は同じ名前の外側の変数をシャドウイング(shadow)するということができる。(中略)通常は、外側の変数をシャドウイングするよりも、新しい意味のある変数名を選んだほうがよい (P145)

  • 部分適用された関数とは、関数が必要とするすべての引数を渡していない関数呼び出し式である。必要な引数を一部または全く渡していないのである。部分適用関数式を作るにはメソッド名のうしろにアンダースコア書く。 (P157)

  • 最後の処理として自分を呼び出す再帰関数を末尾再帰(tail recursion)と呼ぶ。Scalaコンパイラーは、末尾再帰を検知したら、パラメーターを新しい値に更新した後、再帰呼び出しを関数の冒頭にジャンプするコードに書き換える。解が末尾再帰になっていれば、実行時に余分なオーバーヘッドがかかったりはしない (P167)

  • パラメーターなしメソッドは、Scalaでは頻繁に見られるものだ。それとは対照的に、 def height(): Int のように、空括弧付きで定義されているメソッドは、空括弧メソッドと呼ぶ。パラメーターがなく、ミュータブルな状態へのアクセスがオブジェクトのフィールドの読み出しだけなら、パラメーターなしメソッドを使うべきだとされている(特にミュータブルな状態を書き換えないことが大切)。この方法は、「属性をフィールドとメソッドのどちらで実装するかによってクライアントコードが影響を受けてはならない」という統一形式アクセスの原則に従っている。 (P184)

  • 原則として、Scalaの関数呼び出しでは、全ての空括弧を省略できる。しかし、呼び出されるメソッドがレシーバーオブジェクトのプロパティ以上のものを表す場合は、空括弧を書くほうが良いとされている。たとえば、メソッドがI/Oを実行したり、再代入できる変数(var)に書き込みを行ったり、ミュータブルなオブジェクトを直接または間接的に使って、レシーバーのフィールド以外のvarを読み出したりする場合には、空括弧を付けるようにすべきである。 (P185)

  • 合成と継承は、既存の他クラスから新しいクラスを定義するための2種類の方法である。主としてコードの再利用を追求する場合には、一般に継承よりも合成を選んだほうがよい。継承は脆弱な基底クラス問題を抱えており、スーパークラスを書き換えると、サブクラスが利用できなくなることがある。 (P196)

  • 配列の ++ 操作は、2つの配列を連結する(P197)

  • case修飾子が付けられたクラスをケースクラスと呼ぶ。この修飾子を使うと、Scalaコンパイラーはこのクラスの構文に適切な変更を加えるようになる。第1にコンパイラーはクラスと同じ名前のファクトリーメソッドを追加する。そのため、Varオブジェクトを作るのに、 new Var("x") と書く代わりに、少し簡潔に Var("x") と書くこともできる。第2にコンパイラーは、ケースクラスのパラメータリスト内の全てのパラメーターに暗黙のうちにvalプレフィックスをつける。そのため、パラメーターはフィールドとして管理される。第3に、コンパイラーがクラスにtoString, hashCode, equalsメソッドの「自然な」実装を追加してくれる。最後に、コンパイラーは変更を加えたcopyを作成するために、クラスにcopyメソッドを追加する。このメソッドは、1つか2つの属性が異なるだけでほぼ同じクラスのインスタンスを新たに作成する場合に便利だ。

  • headやtailはともに一定の時間で実行されるが、initとlastはリスト全体をたどらないと結果値を計算できないので、リストの長さに比例する計算時間を要する。データ構造を設計するときには、リストの末尾ではなく、先頭にアクセスして操作できるようにするとよい。(P297)

写経しながらメモってたやつ

  • Scala Worksheetが便利。IntelliJ IDEAから立ち上げられる。RubyのPry的な感じでガンガン使ってる。

  • 再帰構造の基本形

def approximage(guess: Double): Double =
  if (isGoodEnough(guess)) guess
  else approximage(improve(guess))
  • カリー化を使うことによってクライアントプログラマーが中括弧の間に関数リテラルを書き込めるようにできる
withPrintWriter(
  new File("data.txt"),
  writer => writer.println(new java.util.Date)
)

val file = new File("date.txt")
withPrintWriter(file) { writer => writer.println(new java.util.Date)