レバレッジメモ: Scalaスケーラブルプログラミング
言語も伽藍とバザールのように成長する--Steele "Growing a language" 1999
トレイト=インターフェイスに似ているがメソッド実装やフィールドを持つ、ミックスイン合成出来る
JavaのStringにtoIntがないことに気づいたときに、暗黙の型変換を参照してRichStringに変換し、そのtoIntを呼ぶ
アラン・ケイ「私は型に反対しているわけではない。『完全な苦痛』にはならないような型システムを見たことがないので動的な片付けを支持しているのである」2003 Dr. Alan Kay on the Meaning of "Object-Oriented Programming"
ダイクストラ「テストが証明できるのはエラーの存在であって、エラーの不在ではない」 1970 http://www.cs.utexas.edu/users/EWD/ewd02xx/EWD249.PDF
「ことなる応用分野への拡張性を持った言語」というアイデア1966 The next 700 Programming Languages http://www.thecorememory.com/Next_700.pdf
(arg: String) => println(arg)をprintlnって書けるのを「関数の部分適用」と呼ぶのは気持が悪いなぁ。 P.51
コンパニオンオブジェクト 4.3
タプルとリストの両方があって両方イミュータブル。Haskellのタプルとリストだと思えばだいたいあってる
フィールドとメソッドを総称してメンバーと呼ぶ。メンバーはデフォルトでpublic
varとvalがある。valは再代入不可。
Scalaのクラスは静的メンバ(static)を持てない。そのかわりにシングルトンオブジェクトがある。classキーワードの代わりにobjectキーワードを使う。シングルトンオブジェクトがクラスと同じ名前の時にコンパニオンオブジェクト、コンパニオンクラスと呼ぶ。同じ名前のクラスがないシングルトンオブジェクトはスタンドアロンオブジェクト。
import文で任意のオブジェクトからメンバーをインポート出来る。
すべてのメソッドが演算子になれる。s.indexOf('o')をs indexOf 'o'と書ける。
論理演算子もメソッド→じゃあショートサーキットはどうやってる?→実は全てのメソッドで名前渡しが可能(9.5)→なんだって!!
===各引用ごとにページ数を書くのは面倒なのでここでP.100===
イミュータブルなオブジェクトのトレードオフ P.105
長所:複雑な状態変化をしないので動作を推定しやすい、他のコードに渡す際に破壊を避けてコピーをとる必要がない、スレッドが同時アクセスしても壊れない、安全にハッシュのキーにできる
短所:大規模なオブジェクトのコピーが必要になることがある
P108 クラスパラメータはコンストラクタ内からしかアクセス出来ない?→クラスパラメータをフィールドに代入して解決→え、なんのためのクラスパラメータ… 10.6のパラメータフィールドで解決される??
def this(n: Int) = this(n, 1)で「2番目の引数に1を入れてコンストラクタを呼ぶ」っていう補助コンストラクタができる。基本コンストラクタだけがスーパークラスのコンストラクタを呼び出せる。これはJavaより若干制限が強い。see sec.10
演算子メソッドを使えばコードは簡潔になる、しかし読みやすくなるかどうかはクライアントプログラマが個々の演算子の意味を理解して覚えられるかどうかによる P.120
Scalaでは関数リテラルとの相性が悪いbreakとcontinueを取り除いている。末尾再帰や、whileの条件にtoExit的変数を入れたりループの後半をifで囲ったりすることでbreakやcontinueがなくても同等なコードが書けるという説明がされている。まあ、そりゃそうだけどね。どう相性がわるいのか知りたいけどどこに書いてあるのかわからない。
Scalaはネストしたスコープの内側で外側の変数をシャドーするような変数を定義できる。
「一人前の存在としての関数」(first-class functions)!
「(x: Int) => x > 0」は「_ > 0」とも書ける。「_ + _」は1引数の2倍になる関数ではなく2引数の足し算をする関数
Javaの内部クラスは外側のスコープで書き換えられる可能性のある変数にはアクセス出来ない。Scalaのクロージャは直感的に言えば変数事態を掴んで「閉じた」ものである。
Scalaは末尾再帰をジャンプに変換するので、末尾再帰の関数が失敗した時のスタックトレースにはいくつもの関数が出てこない。紛らわしい時は-g:notailcallsオプションでOFFにする。末尾再帰の最適化は文字通り自分自身を呼ぶ再帰の時にしか使えない。相互再帰や他の関数の呼び出しは最適化されない。
def foo(f: () => Boolean) = ...で定義される関数は foo(() => x > 0)と呼べる。これをdef foo(f: => Boolean)にするとfoo(x > 0)と呼べるようになる。foo(v: Boolean)との違いは評価のタイミング。これが名前渡しパラメータ。
単純なオブジェクトとは何か、単純なオブジェクトからより面白いオブジェクトを作るにはどうすればいいか、コンビネータ(合成演算)はどう組み合わされるか、最も一般的なコンビネータは何か?これらに答えられるならライブラリ設計は軌道にのっていると言えるだろう。P.173
サブクラスとサブ型の違い: 型引数を取らないクラス・トレイトに関してはサブクラスとサブ型は同じ。型引数を持つ場合それが共変(たとえばListはList[+A]なのでList[Cat] <: List[Animal])ならサブ型 パラメータフィールド:要するにclass Foo(val arg: Type)って書き方でフィールドもつくれる。class Foo(override val x)とかprivateなどの修飾も付けられる。 親クラスのコンストラクタは基本コンストラクタからしか呼べない。class Foo(...args...) extends SuperClass(...args for superclass...) 具象メンバのオーバライドにはoverride修飾が必須 Scalaスケーラブルプログラミングのレバレッジメモ
呼び出されるメソッドの定義は変数の型ではなく実行時のオブジェクトのクラスによって決まる。動的束縛。
継承を使う前にis-a関係かどうかを考えろ(Mayers, "Effective C++" 1991)、また、スーパークラス型としてサブクラス型を使いたいかどうかを考えろ(Eckel, "Thinking in Java" 1998)
最下位(bottom)の型: Null, Nothing。Nullは全ての参照クラス(AnyRefクラスを継承するクラス)の小クラス。Nothingは全ての小クラス。たとえばerrorの返り値など。失敗しうるIntを返す関数の型をMaybe Intとかにしない。Intを返して、エラー時には例外機構でNothingをIntとして使う前に別の処理に飛ばされる。
P.204 トレイト トレイトはクラスみたいなもの。違いはsuperの指すものが何か。traitのsuper呼出はそれがクラスにミックスインされたあとで線形化によって決まる。そのためtraitのabstractメソッドの中ではsuper.a_methodにアクセスすることができる。これはa_methodの具体的な定義を提供するクラスのあとでmixinしなければならないことを表明している。
このことによりtraitという形で表現された「変更の差分」を積み重ねることができる(僕は差分を積み重ねるコーディングスタイルがよいものかどうかは疑問だが)
Scalaのimportはどこにでも書ける。オブジェクトもインポートできる。インポートされたメンバの名前を変えられる。
クラスとコンパニオンオブジェクトは相互にprivate無視。これを使ってstaticメンバの代わりにする。
ケースクラス。
abstract class Expr case class Var(name: String) extends Expr case class Number(...) extends Expr case ...
パターンマッチに使う。
ScalaもJavaも消去モデル(erasure model)のジェネリックプログラミングなので与えられたMapオブジェクトがMap
sealedクラスにすると同じファイルで定義されているサブクラス以外にはサブクラスを作ることができなくなる。
型パラメータとサブ型の組み合わせは面白い問題をはらんでいる。S <: T (SがTが期待されている箇所で型安全に使用できる)の時、Foo[S] <: Foo[T]ならFooは共変(covariant)といいFoo[+T]と表現する。Foo[-T]もありこれは反変(contravariant)。T <: Sの時にFoo[S] <: Foo[T]という意味。非変(nonvariant)はそれらの関係が成り立たないことで、デフォルトではこれ。Javaの配列は共変である。Objectには任意のFooBarが入る。これはStringにObjectを介してIntegerを入れられてしまうということで、実行時エラーを引き起こす。ゴスリン曰く配列をジェネリックに扱う方法が欲しかった(P.358)しかし今ではJavaにもジェネリクスの機能が入っているので配列の共変は必要ではない。互換性のために残されている。
下限境界。def append[U >: T](x: U)などという書き方ができる。TかTのスーパクラスであるものを引数に取るという意味。上限境界もある。[T <: Ordered[T]] lazy val x = ...;で遅延評価 暗黙の型変化。x + yがないときにconvert(x) + yが可能ではないか調べる。に通りの方法があるときはコンパイルエラー。型変換は1回しかしない。書かれているとおりで動く場合には変換しない。 むう、駄目だ、さすがに一気に読むのは脳が疲れた。まだ共変・反変の話と暗黙の型変換が残ってるんだけど、続きは今度。(追記した)