第9章 制御の抽象化
重複するコードの削減
Page 170
共通部分は関数本体であり、非共通部分は引数という形で与える必要がある
高階関数(higher order functions)すなわちパラメーターとして関数をとる関数は、コードを凝縮して単純化するチャンスを増やしてくれる。
<高階関数と関数合成の違い>
引数に関数をとって関数内部で評価するのと、
例えば、関数の引数自体はf・g(x)のように関数ではない場合
<高階関数>
f(g, h, x)
合成できない場合でもいい
(a->b)-> (b->c)
=>a->c
合成できなければいけない
Page 171
一部の動的言語はともかく、Scalaではこのように実行時にコードをペーストすることはできない。ではどうすればよいのか
値としてメソッド名を渡せないのなら、メソッドを呼び出してくれる関数値を渡せば同じ効果が得られる
Page 172
これは関数なので、型の中に=〉が含まれている
filesEndingメソッドで使われている.endsWith()という関数リテラルは、次のものと同じ意味である。
(fileName: String, query: String) => fileName.endsWith(query)
これは
(fileName, query) => fileName.endsWith(query)
とも書けるfilesMatchingと queryの両方からqueryパラメーターを取り除くことができる。すると、リスト 91に示すようにコードは単純化される。
コストに比べてメリットが少なく、そんなことなら最初の重複を認めた方がましだ。
Page 173
.endsWith()や .contains()などの関数リテラルは、自由変数をつかんでいないので、実行時にはクロージャーではない関数値を作る
どういうことか説明。
9.2 クライアントコードの単純化
高階関数のもう 1つの重要な用途は、 API自体に高階関数を使ってクライアントコードを簡潔にすることである。
そのよい例が、 Scalaのコレクション型が定義している特殊な用途のループメソッドである※1
これらの特殊なループメソッドは、List、 Set、 Mapで拡張されている Traversableトレイトで定義されている。第17章を参照
Page 174
existsメソッドは、ある特殊な制御構造を表現している。 whileや forのようにScala言語に組み込まれている制御構文ではなく、Scalaライブラリーが提供している特殊用途のループ構文である
組み込みではなくライブラリー
カリー化
Page 175
新しい制御構造を言語機能の拡張のように感じさせる方法を理解するには、まずカリー化(currying)と呼ばれる関数型プログラミングのテクニックを理解して使えるようになる必要がある。
カリー化と部分適用の違いを説明する
Page 176
curriedSumの「第 2関数」に当たる参照を手に入れる方法はある。次のように、部分適用関数式の中でプレースホルダー記法を使ってcurriedSumを使うのである。
_の使い方も実際に説明するprintln_ のように通常のメソッドでプレースホルダーを使っていた前章では、れなければならなかったが、ここではスペースを入れる必要はない。println_ curriedSum(1)_ はそうではないからである。
9.4 新しい制御構造を作る
Page 177
このメソッドを使うメリットは、操作終了時にフアイルをクローズするのがユーザーコードではなく、 withPrintWriterになるということだ。そのため、ファイルをクローズし忘れるということがなくなる。withPrintWriterのような制御構造を実装する関数力劇リソースをオープンして、関数にリソースを「貸し出す」ので、このようなテクニックを回―ンパターン(loan pattern)と呼ぶ。
ローンパターンという名前と思想を覚えておくこと。
Page 178
クライアントコードでのこうした関数の呼び出しを、組み込み制御構造の場合の見た日に近づけるには、引数リストを括弧(丸括弧)ではなく中括弧で囲むとよい。引数を1個だけ渡すメソッド呼び出しでは、引数を囲む括弧を中括弧に変えてもよいことになっている。
中括弧の使い方引数を1個渡すときに括弧ではなく中括弧を使いたいのは、クライアントプログラマーが中括弧の間に関数リテラルを書き込めるようにしたいからである。こうすると、実質はメソッド呼び出しなのだが、制御構造を使っている感じが強まる
第 1引数のFileはカリー化によつて別の引数リストに押し込むことができる
カリー化の効果
Page 179
この新バージョンは、前のものと基本的に同じである。異なるのは、 2個のパラメーターから構成された1個のパラメーターリストではなく、それぞれ 1個のパラメーターから構成された2個のパラメーターリストをとるようになったことだ
ここがカリー化の本質
9.5 名前渡しパラメーター
中括弧の中のコードが引数をとるという点で、 ifやwhileなどの組み込み制御構造とは異なる
このような場合に対応するために名前渡しパラメーターが提供されている
Page 180
myAssert(5 > 3) // () =>がないので動作しない
() => Booleanではなく、 => Booleanにすれば名前渡しパラメーターになる
Page 181
それに対し、 byNameAssertの predicateパラメーターの型は => Booleanなので、byNameAssert(5 > 3)の括弧の中の式は、 byNameAssert呼び出しの前には評価されない。代わりに、 5>3を評価するapplyメソッドを持つ関数値が作られ、その関数値がbyNameAssertに渡される。
第10章 合成と継承 Composition and lnheritance
10.1 2Dレイアウトライブラリ
Page 182
この章を通じてElementという2Dレイアウトの例を元に説明されていく
Page 183
抽象クラス
合成演算子は、同じドメインの要素を結合して新しい要素を作るので、コンビネーター(combinator)と呼ばれることも多い。
自己準同型の射になっている構成(モノイド)abstract class Element
キーワードの付け方はJavaと同じ
Page 184
Javaとは異なり、 abstract修飾子はメソッド宣言では不要である(付けてもよい)。
注意
contentsという抽象メソッドを宣言(declare)しているが、現在のところは具象メソッドを定義(define)していない
宣言と定義の違い
10.3 パラメーターなしメソッドの定義
このようなパラメーターなしメソッド(parameterless methods)は、 Scalaでは頻繁に見られるものだ。
それとは対照的に、 def height(): Intのように、空括弧イ寸きで定義されているメソッドは、空括弧メソッド(empty paren methods)と呼ぶ
パラメータなしメソッドと空括弧メソッドのキーワードを覚えるパラメーターがなく、ミュータブルな状態へのアクセスがオブジェクトのフイールドの読み出しだけなら、パラメーターなしメソッドを使うべきだとされている
真似したい原則
Page 185
この方法は、「属性をフィールドとメソッドのどちらで実装するかによってクライアントコードが影響を受けてはならない」という統一形式アクセスの原則(uniform access principle)※
OOPの重鎮の一人、メイヤーが提唱。
若干説明する。違いは、フィールドアクセスの方がメソッド呼び出しよりもわずかながら速いことだけだ
その反面、フィールドに変更すると、 1つ 1つのElementオブジェクトが必要とするメモリ容量が増えるJavaのふるまいに対応するという問題がまだ残っている。 Javaは統一形式アクセスの原則を実装していないのである。 Javaでは、 String.length()であって、 string. lengthではない(一方、 array.lengthであって、 array.length()ではない)。言うまでもなく、これは非常に紛らわしい。
パラメーターなしメソッドと空括弧メソッドは、相互にもう一方をオーバーライドできるのである
引数をとらないあらゆる関数呼び出しでは、空括弧は省略可能になっている
Javaとは違うので注意呼び出されるメソッドがレシーバーオブジェクトのプロパティ以上のものを表す場合は、空括弧を書く方がよいとされている
前段と同じ、真似したい原則
Page 186
クラスの拡張
クラス名の後ろにextends節を付ける
Javaと同じextends節を省略すると、 Scalaコンパイラーは、暗黙のうちにscala.AnyRefを拡張しているものと見なす。これは、」 avaプラットフォームにおけるjava.lang.Obj ectクラスと同じものである。つまり、 Elementクラスは、暗黙のうちにAnyRefクラスを拡張している
AnyRefは覚えておこう。
Page 187
サブ型という言葉には、「スーパークラスの値が必要な場所では、サブクラスの値を使える」という意味が含まれている
サブ型は覚えようArrayElementが実際のプロジェクトで使われるものなら、防衛的に配列そのものではなく配タリのコピーを返すことを考えるとよい。
ちょっとしたTips
Page 188
図 10.1は、 ArrayElementと ArrayIString]が合成(composition)関係にあることも示している
10.5 メソッドとフイールドのオーバーライド
Elementクラスでのオ由象メソッドというcontentsの定義を変更せずに、ArrayElementクラスではcontentsの実装をメソッドからフイールドに切り替えることができる
このバージョンのArrayElementにおける(valとして定義されている)contentsフイールドは、 Elementクラスにおける(defで定義されている)ノドラメーターなしメソッドの contentsの実装として、まったく問題のないものである。
一方、 Scalaでは、Javaとは異なり、同じクラスの中で同じ名前のフイールドとメソッドを定義することは禁止されている。たとえば、次のJavaクラスは問題なくコンパイラーを通る。
統一形式アクセスの原則の実装
Page 189
一般に、 Scalaは定義のための名前空間を2個しか持っていない(Javaは 4個ある)。
●値(フイールド、メソッド、パッケージ、シングルトンオブジェクト)
●型(クラス、トレイト)パラメーターなしメソッドをvalでオーバーライドできるようにするためであ
10.6 パラメーターフィールドの定義
contsという名前は、 contentsというフィールド名と似ているが衝突しないものということで選ばれている。この部分は「臭いコード Jであり、どこかに不要な冗長性や繰り返しが含まれている兆候である
単一のパラメーターフィールド(parametric fields)定義でパラメーターとフィールドを結合すれば取り除ける。
Page 190
クラスパラメーターのプレフイックスとしては、 varも使える。この場合、対応するフイールドは再代入可能になる。そして、他のクラスメンバーと同じように、これらのパラメーターフイールドには、 private、 protected※ 5、 overrideの修飾子を追加することもできる
10.7 スーパークラスコンストラクターの呼び出し
Page 191
スーパークラスコンストラクターを呼び出すには、渡したい引数をスーパークラス名の後ろに括弧で囲んで書けばよい。たとえば、 LineElementは、スーパークラスであるArrayElementの名前の後ろに括弧で囲んでArray(s)と書けば、 ArrayElementの基本コンストラクターにArray(s)を渡すことができる。
10.8 override修飾子の使い方
Scalaでは、親クラスの具象メンバーをオーバーライドするすべてのメンバーにこの修飾子を付けなければならない。また、同じ名前の抽象メンバーを実装する場合、この修飾子はオプションとなるため、付けても付けなくてもよい
Page 192
「不測のオーバーライド」(accidental overrides)は、いわゆる「脆弱な基底クラス問題」(fragile base class)が姿を現す典型例であるこの問題は、クラス階層の基底クラス(本書では通常、スーパークラスと呼んでいる)に新しいメンバーを追加すると、クライアントコードが使えなくなるリスクが生じるというものである。
Page 193
10.9 多相性と動的東縛
Page 194
メソッド呼び出しや式が動的東縛(ダイナミックバインディングとも言う : dynamic binding)されることを見落とすわけにはいかない。つまり、実際に呼び出されるメソッド実装は、変数や式の型ではなく、実行時のオブジェクトのクラスによって決まるのである
Page 195
10.10 ファイナルメンバーの宣言
メンバーにfinall多飾子を付けてファイナルメツッド(final methods)
クラス宣言に final修飾子を付けて、ファイナリレクラス(final class)を宣言すればよい