Java で Either モナド

functional java ってライブラリ使えば、Javaでも関数型プログラミングできるよ!っていう誰得な記事。あと、途中にScalaの話しもちょっと混ざってます。


ライブラリ使うために、sbt使います。まず以下のようなbuild.sbtを用意

libraryDependencies ++= Seq(
  "org.codehaus.jackson" % "jackson-mapper-asl" % "1.9.7"
 ,"org.functionaljava" % "functionaljava" % "3.1"
)

実行する処理としては、

  • url を渡す => それに対してhttp getした結果を返す
  • string を受け取る => それを json として parse した結果を返す

という2つの処理があって、1を行った後その結果を2に渡して、最終的それらの処理を繋げて全体としては



あるurlがある => それをparseした後のjsonのオブジェクトを生成


という流れです。*1まず Java でそのまま例外を投げるようにした、Javaらしい普通のversion

https://gist.github.com/2721739/6269452adfaa93fb42233c685acfb999578261da#file_java_main.java

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.InputStream;
import java.io.IOException;
import java.net.URL;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;

public class MainJava{

  public static void main(String[] args){
    final String url = "http://search.twitter.com/search.json?q=scala";
    try{
      final String jsonString = get(url);
      final JsonNode json = parse(jsonString);
      System.out.println(json);
    }catch(IOException e){
      e.printStackTrace();
    }
 }

  static final JsonNode parse(final String jsonString)throws IOException{
    return new ObjectMapper().readValue(jsonString,JsonNode.class);
  };

  static final String get(final String urlString)throws IOException{
    final URL url = new URL(urlString);
    final InputStream content = (InputStream)url.getContent();
    final BufferedReader bf = new BufferedReader(new InputStreamReader(content));
    String line;
    final StringBuilder sb = new StringBuilder();
    while ((line = bf.readLine()) != null){
      sb.append(line);
    }
    return sb.toString();
  }
}

1のgetは

  • URLの文字列がそもそも不正かもしれない
  • 取得先のURLに繋がらないかもしれない

というエラーが起きる可能性がありますし、

2のparseは

  • json として parse できない不正な文字列かもしれない

ということで、両方IOExceptionを投げます。そして、mainの中で例外catchしてprintStackTraceします。Javaでは、例外という仕組みによって、


1のgetというメソッドで成功した場合のみ、2のparseを実行


という処理の流れを実現します。で、それを例外使わずに実現するにはどうすればいいか?ということで、まず両方とも以下のようにEitherを使って書き換えます。

  static final Either<Exception,JsonNode> parse(final String jsonString){
    try{
      return Either.right(new ObjectMapper().readValue(jsonString,JsonNode.class));
    }catch(Exception e){
      return Either.left(e);
    }
  }

  static final Either<Exception,String> get(final String urlString){
    try{
      final URL url = new URL(urlString);
      InputStream content = (InputStream)url.getContent();
      final BufferedReader bf = new BufferedReader(new InputStreamReader(content));
      String line;
      final StringBuilder sb = new StringBuilder();
      while ((line = bf.readLine()) != null){
        sb.append(line);
      }
      return Either.right(sb.toString());
    }catch(Exception e){
      return Either.left(e);
    }
  }

例外を投げる代わりに、

  • 成功したらRight
  • 失敗したらLeft

で包んで返します。

問題は、この2つを組み合わせる処理です。Java風に書くと*2、以下のようになります

https://gist.github.com/2721739/89d19f87f959988163ab2831a26fc8d9570b01b1#file_java_main.java

  public static void main(String[] args){
    final String url = "http://search.twitter.com/search.json?q=scala";
    final Either<Exception,String> str = get(url);
    if(str.isRight()){
      final Either<Exception,JsonNode> json = parse(str.right().value());
      if(json.isRight()){
        System.out.println(json.right().value());
      }else{
        json.left().value().printStackTrace();
      }
    }else{
       str.left().value().printStackTrace();
    }
  }

ネストが増えて激しくダサいですね。関数型にこだわるなら value()っていうメソッド(ScalaのEitherの場合だとget)使ったら負けです、駄目です、死んでください。

ここで、浅海さんの一連の記事では、だいたいScala風のパータンマッチがでてきたりします。しかし、Javaにパターンマッチなんてありません

そして、twitterでのこのやりとり↓

つまり、functional javaだと、中級者のパターンマッチの方法ができません!

Eitherに対するパターンマッチも、たまーに使いどころはあるかもしれませんが、ほとんどの場合、Scalaならば既存のメソッドの合成で、Scalazならばなんらかの型クラスとして扱えば、パターンマッチ使わなくても書けます。


そしてfunctional javaにおいてこれを綺麗に合成するには、無駄に努力が必要になります。Scalaをやっていると意識しない部分が、functional java関数型プログラミングすると、明確に意識して自らが行わないといけないので、(パターンマッチを多用してるような?)中級者くらいのScalaプログラマが、あえてfunctional javaでプログラミングすると、おもしろい気づきがあるかもしれません。


コードに戻りますが、まず綺麗に合成するための下準備として、単なるメソッドではなく、以下のように関数オブジェクトとして定義しないといけません。*3

  static final F<String,Either<Exception,JsonNode>> parse = new F<String,Either<Exception,JsonNode>>(){
    public Either<Exception,JsonNode> f(final String jsonString){
      try{
        return Either.right(new ObjectMapper().readValue(jsonString,JsonNode.class));
      }catch(Exception e){
        return Either.left(e);
      }
    }
  };

  static final F<String,Either<Exception,String>> get = new F<String,Either<Exception,String>>(){
    public Either<Exception,String> f(final String urlString){
      try{
        final URL url = new URL(urlString);
        InputStream content = (InputStream)url.getContent();
        final BufferedReader bf = new BufferedReader(new InputStreamReader(content));
        String line;
        final StringBuilder sb = new StringBuilder();
        while ((line = bf.readLine()) != null){
          sb.append(line);
        }
        return Either.right(sb.toString());
      }catch(Exception e){
        return Either.left(e);
      }
    }
  };


Fというのは、functional javaに用意されてる、関数オブジェクトのためのclassです。

戻り値と引数の型をそれぞれ3〜4回ずつ書かないといけなくなって死にたくなりますねー、しかし関数型プログラミングするとしたら、ラムダの記法がない現状のJava*4ではこうするしかありません。

こうすると最終的に main が以下のように書けます。

https://gist.github.com/2721739/df8c6103de85a7ba44a0a9741daa4dd50265884a

  public static void main(String[] args){
    final String url = "http://search.twitter.com/search.json?q=scala";
    get.f(url).right().bind(parse).either(
      new Effect<Exception>(){
        public void e(final Exception ex){
          ex.printStackTrace();
        }
      }.e()
      ,
      new Effect<JsonNode>(){
        public void e(final JsonNode node){
          System.out.println(node);
        }
      }.e()
    );
  }

functional java

  • bind が、ScalaだとflatMapです。
  • eitherというメソッドが、Scalaでのfoldで、leftの場合の処理、rightの場合の処理を、それぞれ関数オブジェクトとして渡します*5
  • Effect というのは、副作用専用の処理を記述するもののようです。F という型で書いてもいいが、そうすると Fの無名クラスの fのメソッドの中で、return Unit.unit(); とかやらないといけなくなるので、これがあるようです。Effect<A> から F<A,Unit> にeというメソッドで変換できるようになってます

水島さんが、よく以下のように言ってますが



Scalaでも、メソッドとFunctionのオブジェクトは、実際は異なるものですが、ほとんどシームレスに勝手に変換されるので、普段Scalaだけ書いてるとほとんど意識しない部分です。がしかし、functional javaでやると、無名クラスのインスタンスを自分で生成しないといけないので、ものすごくめんどくさい・・・ですが、つまりこれは、Scalaコンパイラがやっていることです。Scalaでは、関数オブジェクトは、実際にはFunctionN tarit の無名クラスのインスタンスとして*6表現されています。


結果的に、この程度のものを書き換えても、コード量明らかに増えるので、実用的かどうかは知りません。しかし、

という境遇の人がもしいれば、勝手にfunctional javaを導入して、普通のJavaプログラマには理解できない、上記のような感じの無名クラスだらけのコードを書いてしまえばいいと思います(ぉ


あと最後に、全体のコード(わざと履歴残してある)と、それをScalaで書いたversionが含まれてるgistのリンクを貼っておきます

https://gist.github.com/2721739/

*1:もちろんjacksonには、URL渡すだけで直接jsonのオブジェクト生成するメソッドもありますが、わざとです

*2:浅海さんの書き方のパクリ・・・?(・ω・`)

*3:この例のためだけならば、get のほうは関数オブジェクトにする必要ないけど・・・

*4:java6のこと言ってます。java7だとちょっとだけ短く書ける?場合もあるらしいですが、どちらにしろclosureがある大半の言語に比べてめんどくさいことに変わりはないですね・・・

*5:foldは結果返すけど、上記のコードの場合使ってない

*6:もっと細かいこというと、versionによっては、runtime.AbstractFunctionN というclassの無名クラスのインスタンスだったり