LL脳がscalaの勉強を始めたよ その12
Scalaコップ本の第6章にすすみますよー、第6章では実際に関数型のオブジェクトを構築してみますよー
immutableオブジェクト
関数型ということでこの章ではimmutableなオブジェクトを具体例を利用して実際に作成していきますー
immutableオブジェクトのメリット
コップ本によればimmutableなオブジェクトはmutableオブジェクトに比べて
- 時間とともに変化するような状態空間を持たないので、動作を推定しやすい
- コピーを作らずに他のオブジェクトに渡しても安全
- 複数のスレッドがアクセスしても状態を壊す恐れが無い
- ハッシュテーブルのキーを安全に作れる
という4つのメリットを挙げられるらしいんだけど、要は「immutableなオブジェクトは外侮から勝手に変更されないから状態が保証できるよね、mutableは無理」って話だと勝手に解釈。
それではクラスを作ってみましょー
今回例として作成するのはRationalクラスです。Rationalは有理数(整数の分数で表現できる数のことデス)のことで、作成するクラスは有理数(分数:ただし分母は0以外)の加減乗除を実装しますよー
またRationalオブジェクトはimmutableなものとするので、例えば異なるRationalオブジェクト同士の和は新しいRational オブジェクトを作成するような動作になりマス
まずは目標を明確化
さて、破綻気味の自然言語でズラズラ書いたところで目標のイメージがつくはずも無いので、Rationalクラスの動作がどういうものになればいいのか!の例をちょろっと
// インスタンスの生成:halfには1/2をもつRationalオブジェクトが格納される val half = new Rational(1, 2) // インスタンスの生成:quarterには1/4をもつRationalオブジェクトが格納される val quarter = new Rational(1, 4) // 四則演算が可能:13/8をもつRationalオブジェクトが生成される ( half * 3 ) + ( 1 - quarter / 2 )
とりあえずこんな感じのモノを作っていきますよー
Rationalの構築
まずはクラスの基本定義をやりますよー、整数による分数で表現できるのが有理数なので引数として分子、分母の2つの整数をとります
// nが分子でdが分母です class Rational(n:Int, d:Int)
このコードで生成されるのは2個のパラメータをとる基本コンストラクタ(コンパイラが自動生成するものかしら?)が生成されるそうです。それと上では引数と適当に呼んだものですが、正確にはクラスパラメータと呼ぶらしいデス。まあ、クラスの引数だから意味的には一緒かと。
Javaなんかだとクラスパラメータは別途記述したコンストラクタの中で受け付けたりする(PHPもそうだった気がする)んですが、Scalaだとそこら辺はすっ飛ばして簡潔に書けるのでかなり楽ちんですねぇ
それじゃ、こんな感じの基本クラスを実際に動かして試してみますよー
// Rationalクラスの作成 scala> class Rational(n:Int, d:Int) { | println("Created " + n + "/" +d) | } defined class Rational // インスタンスの生成 scala> new Rational(1, 2) Created 1/2 res0: Rational = Rational@4d40df
toStringメソッドをオーバーライド
上でインスタンス生成した際に出力された「res0: Rational = Rational@4d40df」という行はRationalオブジェクトのtoStringメソッドが呼び出しているらしく、標準ではjava.lang.Objectで定義されたtoStingが呼び出されて「クラス名@16進数(内部的なインスタンス識別子かしら?)」の形式になるみたい。
でも、こんなのが出力されてもイマイチ活用方法が思い浮かばないので、Rationalインスタンス中身が確認できるように出力内容を変更してみますー
// toStringメソッドをオーバーライドしたクラスを定義しますよー scala> class Rational(n:Int, d:Int) { | override def toString = n + "/" + d | } defined class Rational // インスタンス生成したら値がきちんと出力されましたよー scala> new Rational(1, 2) res1: Rational = 1/2
オブジェクトのtoStringメソッドはデバッグ出力文やログメッセージによく使うそうなので、欲しい情報を詰め込んでおくと素敵な感じ
事前条件のチェック
とりあえず基本的なクラスを作成できるようになったものの、有理数の分母は0になってはいけないので事前チェックの処理を追加しますー
具体的にはクラスパラメータdは0をとってはいけないので、dに0が渡された場合はエラーとして処理してオブジェクトの構築をしないようにします。
// requireを定義して条件に満たない場合は // IllegalArgumentExceptionを投げるようにします scala> class Rational(n:Int, d:Int) { | require(d != 0) | override def toString = n + "/" + d | } defined class Rational // 分母に0を指定したらエラーが発生しますよー scala> new Rational(1, 0) java.lang.IllegalArgumentException: requirement failed at scala.Predef$.require(Predef.scala:107) at Rational.<init>(<console>:5) at .<init>(<console>:6) at .<clinit>(<console>) at RequestResult$.<init>(<console>:3) at RequestResult$.<clinit>(<console>) at RequestResult$result(<console>) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMetho... // 0以外なら普通に処理されますよー scala> new Rational(1, 2) res3: Rational = 1/2