6.4 トレイトは実装コードを書けるインタフェ ース println(greeting. getMessage) def main(args : Array [String] ) { args match { case Array(1ang, user) = > lang mat ch { case = > pri Ⅱ tl Ⅱ ( " 指定されたあいさつは実行できません。 " ) case "EN" = > say (new GreetingEng1ish(user) ) case "JP't = > say (new GreetingJapanese (user) ) リスト 32 複数のトレイトを使ったミックスインの例 = > println ( " 引数の数が不正です。 " ) } ななか十 inside press ◆ 28 / / 読み込み専用リポジトリの宣言 entities + = (employee . id ー > employee) def save (employee : Emp10yee) : Unit this: Emp10yeeRepositoryBase = > / / this 型の指定 trait Writab1eEmp10yeeRepository { / / 書き込み専用のリポジトリ機能 def load(key : lnt) : Emp10yee entities(key) this: Emp10yeeRepositoryBase = > / / this 型の指定 trait Readab1eEmp10yeeRepository { / / 読み込み専用のリポジトリ機能 def toList entities . toList def toMap = entities Map. empty [lnt , Emp10yee] private [repository] var entities abstract class Emp10yeeRepositoryBase { 〃従業員情報を保持するリポジトリの骨格実装 case class Emp10yee(id: lnt, name: String) / / 従業員 package repository
3.2 クラスが簡潔に書ける class Money(var amount : BigDecima1 , def this (amount : BigDecima1) def plus (other : Money) currency Currency) { / / コンパニオンオプジェクト ( 必ず同一ファイルに定義する ) object Money { val USD = Currency. getlnstance ( "USD") val JPY = Currency. getlnstance ( " JPY") val money = new Money(100 , Money. JPY) リスト 17 第二引数にコンパニオンオプジェクトのフィールドを指定 / / コンパニオンクラス amount と DEFAULT-CURRENCY は共に private であることに注目 リスト 18 コンパニオンオプジェクトを使えばお互いの非公開メンバーにアクセスできる。 val JPY = Currency. getInstance("JPY") / / デフォルトの C 社 e れ c val USD = Currency. getInstance("USD") object Money { / / コンパニオンオプジェクト 〃以下、省略 def this (amount : BigDecima1) this (amount , Money. DEFAULT—CURRENCY) class Money(private va1 amount : BigDecima1 , val currency : Currency) { private val DEFAULT—CURRENCY def sum(monies : List [Money] ) var result : BigDecima1 for(money く一 monies){ result 十 = money . amount new Money(resu1t) JPY 〃すべてのれ e の合計を返す リスト 19 コンパニオンオプジェクト内部にファクトリメソッド (apply メソッド ) を定義する package money ななか十 inside press ◆ 18
3.2 クラスが簡潔に書ける Scala ではプログラミングスタイルを関数型と命令型で使い分けできる 本文中の plus メソッド ( リスト 11 ) では this ( 既存のインスタンス ) の amount を変更せずに、新しい Money インスタンスを生成しています。実はこのメソッドは関数型を意識したプログラミングスタイ ルを採用しています。素直に Money のフィールドを val ではなく var にして、 this の amount に加算 してもよいはずです。 class Money(var amount: BigDecimal, val currency: Currency) { def plus(other : Money): Unit currency) require (Other. currency amount 十 = Other. amount Java プログラマならこちらのメソッドの方がなじみ深いでしよう。このメソッドは従来からの命令型 のプログラミングスタイルに沿っています。 ScaIa は関数型の特徴を持っ言語ですが、このような命令 型のプログラミングスタイルも許容しています。命令型の plus メソッドでは、 this の amount の値を 更新するので this の状態を変更することを意味します。 2 度目に plus メソッドが呼ばれた場合も、 1 度目の plus メソッドによる加算結果を保持する am 。 unt が利用されます。つまり、状態の変更はその 後の処理に影響を与えます。例えば、状態の変更を起こす plus メソッドでテストコードを書いた場合 はどのようになるのでしようか。以下のテストコードを見てください。 val currency = Currency. getlnstance( ” JPY ” ) val money = new Money(lOO, currency) mo れ e 既ん s ( れ ew MO れ e リ ( 20 , c 社ロ、 e れ c の丿 ( 丿 money. plus(new Money(lO, currency)) new Money(110, currency) ) { if ( money println (money ・ amount) / / ノブ 0 を表示 この例では、 money を 100 円で初期化し、 plus メソッドで 10 円を追加し、次の if 文で 110 円のとき に金額を表示しています。期待通りに処理できますが、プログラムの仕様変更で ( A ) の部分に 20 円を 追加した場合はどうでしようか。 130 円となるので、当然 if 文は true になりません。こは条件式を 変更する必要があるでしよう。この例が示しているように、状態の変化を引き起こす処理は複雑で理解 しにくいことがわかります。複雑であればプログラムに対して間違った理解をしてしまい、思わぬ不具 合を作り込んでしまう可能性があります。このように、ある処理が状態の変更を引き起こし、その後の 処理に影響を与えることを「副作用」と呼びます。代表的な副作用のある処理とは、変数の値の変更と、 標準出力 / 標準入力やファイルなどの入出力です。関数型言語では、「メソッド ( 関数 ) 内の処理は副作 用を持つべきでない」という考え方に基づくプログラミングスタイルを採用しています。また、最初に 説明した副作用を起こさない M 。 ney クラスの plus メソッドは、どのような処理を実行しても副作用を 起こしません。 m 。 ney に対してどのような操作を行っても、処理結果は新しいインスタンスが生成さ れるだけです。つまり、 p ⅲ s メソッドの処理結果は引数の other だけに依存し、その呼出しによって他 の動作に影響を与えないという特徴があります。このような特徴を「参照透明性」といいます。また、 amount は val で宣言されているので、そもそも amount に再代入ができません。再代入するならば、 var で宣言しますが、関数型では val を使うのが基本です。 val は、一度初期化したら二度と値を変更す ることができません。このような特徴を「単一代入」と呼びます。クラスのフィールドに val を利用す ることで自然と副作用を回避できるため、バグを作り込みにくいプログラミングスタイルとなっていま す。このように関数型は副作用に対して大きなメリットがありますが、普段命令型に慣れていると新た な言語を学習するコストは高くなりがちです。しかし、 ScaIa ではプログラマに関数型と命令型のそれ ぞれのプログラミングスタイルを適材適所で選択できる自由を与えています。特に、これから ScaIa を 学習するプログラマには、慣れている命令型のプログラミングスタイルから始めることができるのは大 きなメリットでしよう。 ◆ 20 ななか十 inside press
3.2 クラスが簡潔に書ける ます。 リスト 13 補助コンストラクタの例 package money import java. uti1. Currency import Java. util . Loca1e class Money (val amount : BigDecima1 , val currency : 〃↓補助コンストラクタ this (amount , def this (amount : BigDecimal) Currency ・ getlnstance (Loca1e. getDefau1t) ) Currency) { 〃以下、省略 リスト 14 val a = new Money(100) / / プ 00 円 println(). amount) val b = new Money(50) / / 50 円 println(). amount) val result = a. plus(b) / / プ 00 円 println(result . amount) リスト 13 用のテストコード + 50 円 new Money()5 の ) / / プ 50 円かどうか検証 assert (result 3.2.5 オプジェクトでインスタンスを一つに限定 Java では、よく使う値を staticfinal なクラスフィールドで定義します。 Scala で書いた Money クラスでも、 Currency を円にするなど定数のようにできないものでしようか。 通常のクラスだけでは以下のようにインスタンス化しないで定数フィールドを参照することは できません。 val usdCurrency = Money. USD scala では static final の代わりにオプジェクトが使えます。 He110,World の節で紹介した HelloWorId がオプジェクトでした。このオプジェクトは一見すると、 object キーワードで宣言 するクラスのようなものですが、複数のインスタンスが作成できるクラスとは違い、一つのイ ンスタンスしか生成できません。デザインパターンのシングルトン ( クラスのインスタンスが一 つしか生成されない ) を言語としてサポートしているわけです。使用する場合の注意点は、オプ 合に暗黙的に指定する引数のことです。 class Money(var amount: BigDecimal,var currency: Currency Currency. getInstance(Locale. getDefault) ) . ななか十 inside press ◆ 16
3.2 クラスが簡潔に書ける import java. util . Currency import java. util . Loca1e class Money(var amount : BigDecimaI , def this(amount : BigDecima1) def plus (other : Money) 〃必ず同一ファイルに定義する object Money { currency Currency) { def apply(amount : BigDecima1 , currency : Currency) new Money (amount , currency) / / 補助コンストラクタを廃止し、コンパニオンオプジェクトで複雑な生成を担うことも可能 def apply(amount : BigDecima1) new Money (amount , Currency. getlnstance (LocaIe. getDefauIt) ) 3.2.7 コンパニオンオプジェクトでファクトリを実装 コンパニオンオプジェクトにはファクトリ ( インスタンスの生成 ) としての役割もあります。リ スト 13 のように apply メソッドを定義することで、 new 演算子を利用せずにインスタンスを作 ることができるようになります。 apply メソッドは明示的に呼び出すのではなく、「コンパニオ ンオプジェクト名 ( 引数 ) 」のように記述することで apply メソッドを呼び出します。例えば、リ スト 20 では、 Money(100, Money. JPY) は Money. apply(100, Money. JPY) と、 Money(150) は Money. apply(150) と同じ意味になります。これは糖衣構文 ( シンタックスシュガー ) といって、 コードの読み書きのしやすさのために導入される構文です。 リスト 20 叩 ply メソッドの呼び出し例。糖衣構文により簡単に呼び出せる / / れ e に 0 のれ e JP りは、れ e 叩 2 に 0 の Money.JPY) が呼び出されて Money(150)) assert(result れる / / れ e に 5 のは、れ e . 叩 2 切に 5 のが呼び出され、れ e 田 Mo れ e ( プ 50 , れ e JPY) が実行さ val a = Money( 100 , Money. JPY) / / れ e 田れ e ( 10 のれ e 咽りが実行される ななか十 inside press ◆ 19
6.4 トレイトは実装コードを書けるインタフェース トで実装したサンプルです。 Greeting トレイトは Java のインタフェースのように実装を持ち ません。抽象クラスである AbstractGreet ⅲ g クラスが継承しています、 13 。次はトレイトを使っ た「ミックスイン」の例です。複数のトレイトを合成することをミックスインと呼びます。リ スト 32 を見てください。今度はトレイトに処理が実装されています。トレイトにはコンスト ラクタが記述できない以外は、クラスと同様に実装コードも記述できます。 こでは、従業員 (Employee) をメモリー上で読み書きできるリポジトリを例に挙げます。リポジトリの抽象クラ スである EmployeeRepositoryBase は内部に Map を持ち、 List や Map への変換メソッドを提 供します。そして、リポジトリとして書き込みや読み込みの機能を提供する WritableEmploy- eeRepository トレイトと ReadableEmpIoyeeRepository トレイトがあります。先ほどの例と違 い実装コードを含みます。また、これらのトレイトでは this 型で EmployeeRepositoryBase クラ スを指定しており、 ミックスイン対象の EmployeeRepositoryBase の機能にアクセスできます。 EmployeeReadOnlyRepository クラスは読み込み専用で、 EmployeeRepository クラスは読み書 きができ、 toReadabIe メソッドで EmployeeReadOnIyRepository クラスに変換することができ ます。トレイトでミックスインを使うとクラスに実装する処理を小さな断片として定義し、必要な ときに組み合わせてクラスを実装することが可能です。 Java にはない Scala の魅力の一つです。 リスト 31 トレイトの利用例。 Scala には Java の interface に相当する機能はなく、トレイトを使用する / / あいさつのトレイト trait Greeting { def getMessage : String / / あいさつの抽象クラス abstract class AbstractGreeting (target : String) extends Greeting { def getMessageFormat : String override def getMessage = getMessageFormat . format (target) / / 日本語のあいさつ class GreetingJapanese (target : String) extends AbstractGreeting(target) { " %s さん、こんにちは " override def getMessageFormat / / 英語のあいさつ class GreetingEng1ish(target : String) extends AbstractGreeting(target) { "%s, He110 " override def getMessageFormat / / あいさっするアプリケーション object GreetingApp1ication { / / ポリモーフィズムを使ってあいさつを表示 private def say(greeting: Greeting) { 、 13 二つ以上のトレイトを継承する場合は「… extends Greetingwith A with B 」などと with でつなげて指名しま ななか十 inside press ◆ 27