Javaにvariant型を実装した

最近Hadoopを使うために5〜6年ぶりにJavaをいじっています。

そんで、ちょっとばかり耐えがたいわけです。

例えばありがちな例題を挙げると、


["google", "検索","開発"]

のようなタグ(文字列)のリスト(でも配列でもなんでもいい)を受け取って

[
 {tag=>"google", enc=>'google'},
 {tag=>"検索", enc=>'%E6%A4%9C%E7%B4%A2'},
 {tag=>"開発", enc=>'%E9%96%8B%E7%99%BA'}
]

のようなハッシュテーブルのリストに変換しなさい。

ただしパーセントエンコーディングを行う関数(でもメソッドでもなんでもいい) uri_encodingは与えられているものとする。

こんなこと、Webアプリケーションではよくやるじゃないですか。

んで、実際Perlなんかでは結構簡単に書けて

#my @tags = ("google", "検索","開発"); はモデルとかから取得
my @ret = map{ { tag=>$_,enc=>uri_encoding($_) } } @tags;

まあこんな感じじゃないですか。

これをJavaで書くとこんなんなります。

//List<String> tags; の中身はモデルとかから取得
List<Map<String,String>> result = new LinkedList<Map<String,String>>();
Iterator<String> i = tags.iterator();
while(i.hasNext()){
    String tag = i.next();
    Map<String,String> m = new HashMap<String,String>();
    m.put("tag", tag);
    m.put("enc", uri_encode(tag));
    result.add(m);
}

スキンのまま生まれたクソバカか、デブ? それとも努力してこうなったのか? 僕が6年ぶりにJavaを書くせいでうまい書き方をしらないからこんな冗長なんでしょうか。


でも、

こんなにも・・!!
こんなにも苦しいのならば型などいらぬ!!

とか暗黒面に落ちそうになってしまいました。



これ以外にもWebアプリケーションなんかだとクエリーが全部文字列で来るけど、妥当性検証のためにintに変換してintと比較とかするのもJavaだとクソ面倒です。(例えば今表示している0件目ページの次の100件を表示するためにoffset=100とかをGETパラメタで受け取るとかそういう場合。こういうのはフレームワークが吸収してくれるんでしょうか?)



そんで、Javaのリハビリとしてバリアント型を作ってみました。

どうせWebアプリケーションのコントローラとかHadoopでやるようなテキスト処理だと、使う型はせいぜい文字列、整数、日付、リスト、ハッシュおよびそれらの組み合わせと相互変換なわけです。

こういう簡単なことは簡単に書けるように、これらを全部つっこめるvarクラスを作って、例えばさっきの問題ならこんな風に書けるようにするのがゴールです。(lambdaはファンクタのための抽象クラス、$はmapとかの関数的なクラスメソッドを入れておくためのクラス。)

var tags = $.list("Google", "検索エンジン", "開発");
lambda tag2hash = new lambda(){var f(var tag){
  return $.hash( "tag", tag, "enc", uri_encode(tag) );
}};
var result = $.map(tag2hash, tags);

んで、書いたのがコレ(var.java)

1ファイルにまとめるために無理やりpublicじゃない内部クラスを大量に使ってますが気にしないでください。

varクラス(クラス名が大文字で始まらないけど勘弁してねw)にmainついてますが、もうJavaソースには見えませんw

JavaでhackするというよりJavaをFxxkしてる気分になりました。

public class var {
    public static void main(String[] args){
        lambda square = new lambda(){var f(var v){
            return new $(v.i() * v.i());
        }};
        lambda say = new lambda(){var f(var v){
            System.out.println(v.display());
            return var.NIL;
        }};

        //文字列
        var name = new $("James");
        say.apply(name);

        //文字列と数値の混合リスト
        var ls = $.list(1, "2", 3, 4, 5);
        //リストの各要素を2乗したリストを返す(数値は自動変換)
        say.apply($.map(square, ls));
        //cdr(笑)
        say.apply(ls.cdr());

        //入れ子のハッシュテーブル
        var hash = $.hash(
          "John", $.hash("sex", "male", "age", 20),
          "Eva",  $.hash("sex", "female", "age", "unknown"),
          "Abe",  $.hash("sex", "unknown", "age", "25")
          );
        say.apply(hash.get("John").get("sex"));
        say.apply(hash.get("Nobita").get("sex"));
        say.apply(hash.keys());
        say.apply(hash);
    }
}

このmainを実行するとこんな感じ。


$ java variant/var
"James"
(1 4 9 16 25)
("2" 3 4 5)
"male"
nil
("John" "Abe" "Eva")
(("John" => (("age" => 20)("sex" => "male")))("Abe" => (("age" => "25")("sex" => "unknown")))("Eva" => (("age" => "unknown")("sex" => "female"))))