Scala勉強中です。
Javaのようなオブジェクト指向言語ではよくメソッドチェーンを使いますが、関数型言語を勉強していると出てくるパイプラインとの違いは何ぞや?と思ったため、その違いについて調べてみました。
チェーン
チェーン内のそれぞれのメソッドが同じ自身のオブジェクトの参照を返すことで、メソッドを連結していきます。
public class Person {
private String firstName = "";
private String lastName = "";
public Person setFirstName(final String firstName) {
this.firstName = firstName;
return this;
}
public Person setLastName(final String lastName) {
this.lastName = lastName;
return this;
}
public String toString() {
return firstName + " " + lastName;
}
public static void main(final String[] args) {
Person person = new Person();
System.out.println(person.setFirstName("山田").setLastName("太郎"));
}
}
出力結果は下記です。
山田 太郎
チェーンを使うととても流暢な感じでプログラミングできます。
これをデータ指向っぽい使い方に適用してみます。3, 1, 2というリストの末尾に0を足してソートしてみます。(今度はScalaで)
import java.util.ArrayList
import java.util.Collections
class ChainSample {
val list = new ArrayList[Integer]()
list.add(3)
list.add(1)
list.add(2)
def add(num: Int): ChainSample = {
list.add(num)
this
}
def sort(): ChainSample = {
Collections.sort(list)
this
}
}
object ChainSample {
def main(args: Array[String]) {
val chain = new ChainSample()
println(chain.add(0).sort().list)
}
}
※わざとScalaのListは使っていません。
結果は下記になります。
[0, 1, 2, 3]
チェーンでも全く問題がないように思えますが、この場合、メソッド呼び出しのたびに参照オブジェクト自身の内容が変化しています。
関数型プログラミングでは関数に渡されたオリジナルのデータは実行後も変更されず、オリジナルのまま残るべきです。
パイプライン
ということで、パイプラインとチェーンの違いが見えてきました。
パイプラインとはおそらく、
- パイプラインは入力値を壊さず、新しいデータを作って進む
- 入力値にはオブジェクトへの参照ではなくて値(コレクション等も含む)を扱う
上記を踏まえて作成したパイプラインが下記になります。
※これもわざとScalaのListは使っていません。
import java.util.ArrayList
import java.util.Collections
object PipelineSample {
// 第一引数を順番を元に第二引数以降の関数リストを再帰的に実行していく
def pipeline(seed: ArrayList[Integer], functions:Any*): ArrayList[Integer] = {
val func = functions(0).asInstanceOf[(ArrayList[Integer])=>ArrayList[Integer]]
val result:ArrayList[Integer] = func(seed)
functions.length match {
case i:Int if i >= 2 => pipeline(result, functions.takeRight(functions.length - 1): _*)
case _ => result
}
}
def add(num: Int, list: ArrayList[Integer]): ArrayList[Integer] = {
list.add(num)
new ArrayList(list) // 新しいListを返す
}
def sort(list: ArrayList[Integer]): ArrayList[Integer] = {
Collections.sort(list)
new ArrayList(list) // 新しいListを返す
}
def main(args: Array[String]):Unit = {
val list = new ArrayList[Integer]
list.add(3)
list.add(1)
list.add(2)
val curriedAdd = (add _).curried
println(pipeline(
list, // 入力データ
curriedAdd(0), // 関数その1
sort _)) // 関数その2
}
}
実行結果は
[0, 1, 2, 3]
どうやらパイプラインのコードを作ることができた(?)模様です。
ちなみに、、、いろいろやりましたが、ScalaのListにはパイプライン用のメソッドがちゃんと用意されているので、下記だけで上記のパイプライン処理ができます(^_^;)
(List(3,1,2) :+ 0).sorted