java使いのためのScala の勉強のための資料作り scalaって素晴らしい メソッドじゃない関数でopen-closeをライブラリ化

http://d.hatena.ne.jp/nazoking/20100513/1273688373 の続き

javaでよくあるこういうの

InputStream is = new FileInputStream(new java.io.File("hoge.txt"));
try{
	// is を使ったなんか
}finally{
	is.close();
}

で、closeの付け忘れとかあるので何とかしたい。付け忘れなくても、長いし。
しかし「なんか」の部分はよく変わるので固定化できない。File.readAsString() のような関数にすることは可能だが、メモリに載らない場合とかもあるし、string型じゃないので欲しいときもあるし、最初の数バイト読めばいいだけかも知れないし、途中でシークしてなんかするのかも知れない。
あと、上の例だとcloseの部分が一行ですむからいいけど、例えばデータベース

Connection conn = createConnection();
try{
  state = conn.createStatement();
  try{
     // state を使った なんか
   } finally {
     state.close();
   }
}finally{
  conn.close();
}

とか……無駄にインデント深い! あとopen,close以外のことも前後にやりたい場合(is がクローズされている場合はクローズしないようにする処理とか、そのエラーを握りつぶす処理とか)……じゃあ、どうするか? javaでやろうとすると、

abstract class InputStreamProcessor{
	abstract void process(InputStream is) throws Exception;
	InputStreamProcessor(File file) throws Exception{
		InputStream is = new FileInputStream(file);
		try{
			this.process(is);
		}finally{
			is.close();
		}
	}
}

こんなのを定義して

new InputStreamProcessor(new File("hoge.txt")){
	@Override
	void process(InputStream is) {
		// なんか
	}
};

こんな感じ……あるいは

interface InputStreamProcessor{
	abstract void process(InputStream is);
}
static void inputStreamProcess(File file,InputStreamProcessor processor) throws IOException{
	InputStream is = new FileInputStream(file);
	try{
		processor.process(is);
	}finally{
		is.close();
	}
}

inputStreamProcess(new File("hoge.txt"), new InputStreamProcessor() { public void process(InputStream is) {
	// なんか
}});

うーん……なんかうざい。わかりにくいし。これだけ「うざい、わかりにくい」と、ついついtry-catchの方を使ってしまいますね。もちろん分かっている人はいいですが分かっていない人がやると、close を finalyでやるのを忘れたり、closeそのものを忘れたりしてリソース漏れが。
あと、「なんか」の部分で発生しうるExceptionを予想できないので、「abstract void process(InputStream is) 」には「throws Exception」をつけるか、process の中で例外をRuntimeExceptionとかにラップしてやらないといけなくなって、悲しい。さらに返値があるとやっかいだ。

final String[] val = new String[1];
new InputStreamProcessor(new File("hoge.txt")){
	@Override
	void process(InputStream is) {
		// なんか
		String[0]="xに入れたい値";
	}
};
String x = val[0]

とか、

abstract class InputStreamProcessor<T>{
	abstract T process(InputStream is) throws Exception;
	public T val;
	InputStreamProcessor(File file) throws Exception{
		InputStream is = new FileInputStream(file);
		try{
			val = this.process(is);
		}finally{
			is.close();
		}
	}
}

String x = new InputStreamProcessor<String>(new File("hoge.txt")){
	@Override
	String process(InputStream is) {
		// なんか
		return "xに入れたい値";
	}
}.val;

とか

public interface InputStreamProcessor<T>{
	abstract T process(InputStream is);
}
public static <T> T inputStreamProcess(File file,InputStreamProcessor<T> processor) throws IOException{
	InputStream is = new FileInputStream(file);
	try{
		return processor.process(is);
	}finally{
		is.close();
	}
}

String x = inputStreamProcess(new File("hoge.txt"), new InputStreamProcessor<String>() { 
	public String process(InputStream is) {
		// なんか
		return "xに返す値";
	}
});

とか……涙ぐましい。何回Stringってかけばいいねん みたいな

inputStreamProcess(new File("hoge.txt"), new InputStreamProcessor() { public void process(InputStream is) {
	// なんか
}});

で、一番無駄なのは"new InputStreamProcessor() { public "のあたり。だいたいデータ持たないんだからくらすとかいらんねん。javascriptなどで使える、関数をそのまま扱える機能を知っていると、それが出来ればいいのに、と思う。


もちろん、scalaならできる。

def inputStreamProcess[A](file:File,func:InputStream => A):A={
	var is = new FileInputStream(file);
	try{
		func(is);
	}finally{
		is.close();
	}
}

こんなのを定義して(定義する方はあんまり変わらないですね)

x:String = inputStreamProcess(new File("hoge"),(is)=>{
	// is を使ったなんか
	return "xに入れたい値";
});

こんな感じで書ける!見やすくてわかりやすい!javascriptっぽい!しかしrubyを知っていると「最後の閉じ括弧が二つってなんかかっこわるいよね……」と思うので、scalaではこんな書き方もあるようだ。

def inputStreamProcess[A](file:File)(func:InputStream => A):A={
	var is = new FileInputStream(file);
	try{
		func(is);
	}finally{
		is.close();
	}
}

x:String = inputStreamProcess(new File("hoge")){(is)=>
	// is を使ったなんか
	return "xに入れたい値";
};

定義から変更しないといけないようだけど、こういうかんじ。短いし分かりやすいし使いやすい!

まとめ

メソッドが一つしかない上にプロパティーも持たないんなら、クラス作らなければいいじゃなーい

こういうのはよくあって、ソートのプログラムもそんな感じだし

Collections.sort(list, new Comparator<Object>(){
     public int compare(Object arg0, Object arg1) {  
         return 自由な方法で比較;
     }
});

スレッドも

new Thread(new Runnable(){
	public void run() {
		// なんかする
	}
}).start();

と、これら(Comparator,Runnableなど)は「引数や戻り値に代わりはあれどメソッドを一つだけ持っていればいいインタフェース」としてまとめることが出来る。
テンプレートパターンの、一番小さい形(埋めるべきメソッドが一つしかない)とみることもできる。
javaでは、クラスの定義が必要なのでどうしても「クラス宣言」「メソッド宣言」の二つが必要になって、冗長になってしまう。

scalaでは簡単な記法で関数をそのまま渡せるので、記述は簡潔になります。

scalaってすばらしい!

続き書いた → http://d.hatena.ne.jp/nazoking/20100516/1273940563