Scalaはオブジェクト指向言語です(2)
1日空いてしまいましたが、引き続きふつーのオブジェクト指向言語
としてのScalaを説明していきます。
- 高階関数:ただのメソッド
これはジョークではなく、本当にただのメソッドなのです。とりあえず、説明は後にして、代表的な高階関数である、map
のScala実装を見てみましょう*1。def map[A, B](list: List[A], fun: A => B): List[B] = { val newList = new ArrayList[B] val it = list.iterator() while(it.hasNext()) { newList.add(fun.apply(it.next())) } newList }
利用例は以下のようになります。
val a = new ArrayList[String]{ add("A"); add("B") } println(map(a, (x: String) => "Hoge" + x)) //[HogeA, HogeB]
さて、ここからシンタックスシュガーを全部取っ払ってみましょう。すると、次のようになります。
def map[A, B](list: List[A], fun: Function1[A, B]): List[B] = { val newList = new ArrayList[B] val it = list.iterator() while(it.hasNext()) { newList.add(fun.apply(it.next())) } newList }
val a = new ArrayList[String]{ add("A"); add("B") } val b = map(a, new Function1[String, String] { def apply(x: String): String = "Hoge" + x }) println(b) //[HogeA, HogeB]
さて、ここまで来れば、高階関数と呼ばれているものは、本当にただのメソッドだという事がおわかりいただけたかと思います。
Scala界では、FunctionNantoka
クラスのオブジェクトを引数に取るメソッドを高階関数と読んでいるだけであって、実態はこんなものです。 - implicit parameter: ものぐさな人のための便利機能
ここでimplicit parameterというふつーのオブジェクト指向言語には無い機能が出てきましたが、何も難しいことはありません。まず、なんでこんな機能が必要なのか、Javaプログラムを例にして説明していきます。
まず、java.util.List<E>
を引数にとって、要素の合計値を返すsum
メソッドを定義したいとしましょう。定義したくないかもしれませんが、定義したいことにしてください。int型に特化した定義は次のようになります。import java.util.List; import java.util.ArrayList; import java.util.Arrays; public class SumListInt { public static int sum(List<Integer> list) { int total = 0; for(Integer value:list) total += value; return total; } public static void main(String[] args) { List<Integer> ints = Arrays.asList(1, 2, 3, 4, 5); System.out.println(sum(ints)); } }
しかし、これではint(正確にはInteger)のListにしかsumが使えず汎用性が全くありません。そこで、インタフェースAdditiveを用意して、それを実装したクラスに対してはsumを呼び出せるようにしてみます。import java.util.List; import java.util.ArrayList; import java.util.Arrays; public class SumListAdditive { public interface Additive<T> { T add(T other); } public static <T extends Additive<T>> T sum(List<T> list) { T total = list.get(0); for(T value:list.subList(1, list.size())) total = total.add(value); return total; } }
できました!しかし、大変残念なことに、Integerは標準ライブラリで変更しようが無いので、このsumを使うことができません。Additiveを実装したラッパークラスを作ればできないこともありませんが、無駄にコストがかさむだけでしょう。
このアプローチの何が問題だったかと言えば、後付けで変更が効かない継承関係を前提にしてsumを実装したことにあります。
この問題の解決法は簡単です。「二つのT型の値を足して、T型の値を返す」オブジェクトを別の引数として渡してあげれば良いのです。具体的には、次のようになります。import java.util.List; import java.util.ArrayList; import java.util.Arrays; public class SumListAddition { public interface Addition<T> { T plus(T a, T b); T zero(); } public static final Addition<Integer> INT_ADDITION = new Addition<Integer>() { public Integer plus(Integer a, Integer b) { return a + b; } public Integer zero() { return 0; } }; public static <T> T sum(List<T> list, Addition<T> addition) { T total = addition.zero(); for(T value:list) total = addition.plus(total, value); return total; } public static void main(String[] args) { List<Integer> ints = Arrays.asList(1, 2, 3, 4, 5); System.out.println(sum(ints, INT_ADDITION)); } }
さて、これで問題は解決…したかのように見えます。しかし、sumしたいListの型毎に適切なオブジェクトを選んで渡すのは面倒くさいです。そこで出てくるのがimplicit parameter。型に応じて、適当にうまいことINT_ADDITIONみたいなのを渡してくれる便利機能です。上記コードのScala版は次のようになります。
import java.util.List import java.util.ArrayList import java.util.Arrays object SumListAddition { trait Addition[T] { def plus(a: T, b: T): T def zero: T } implicit object INT_ADDITION extends Addition[Int] { def plus(a: Int, b: Int): Int = a + b def zero: Int = 0 } def sum[T](list: List[T])(implicit addition: Addition[T]): T = { var total = addition.zero val it = list.iterator() while(it.hasNext()) { total = addition.plus(total, it.next()) } total } def main(args: Array[String]) { val ints = Arrays.asList(1, 2, 3, 4) println(sum(ints)) } }
さて、Java版との主な違いは何かというと、sum
の引数addition
とINT_ADDITION
にimplicitという修飾子が付いたことです。
前者は、「引数addition
は、型に合うimplicit修飾子が付いたオブジェクトが見つかれば、それを勝手に渡してもらえるよ」という事を意味しています。後者がまさにそのimplicit修飾子が付いたオブジェクトです。
ここで、「じゃあ、一体どうやってimplicit修飾子が付いたオブジェクトを探してくるんだ」という話になりますが、基本的にはimplicit parameterを持ったメソッドを呼び出している箇所から見える範囲のオブジェクトを探索します。他にちょっとややこしいルールがあったりするのですが、その辺りは普通は知らなくても大体なんとかなります。
というわけで、第2回はこれまでです。implicit parameterの話は、導入の動機とかを考えるとどうしても長くならざるを得ませんでしたが、難しいと思われた場合、コメントいただければと思います。