読者です 読者をやめる 読者になる 読者になる

さんぽみち

なにか思いついたときとかに気まぐれに更新されます。

Java 推論型を利用したい

Java

はじめに

 今後Javaを使うことがほぼ確定となったため勉強します。
 でもいい題材がないので思いついたものやJavaで不便に感じることを解消する形のものを実装しつつ、Javaをつかんでいきたいと思います。

実装内容

 Javaでは推論型が基本的に使えません。
 しかしこれには例外があり、"ラムダ式の引数と処理を区切る -> 演算子の左側" では型が推論できる状況であれば明示しなくても問題ありません。
 ラムダ式を与えて処理し続ければ型宣言は不要となります。
 これは常に同じようなクラスを返し続ければ実現でき、これを満たすのはジェネリクス型のメソッドチェーンですね。
 格納した値に直接触れずに、処理だけ渡して影響させ続けるというのはどこかで聞いたことがあります。
 Haskellでいうと Functorクラス の fmap関数 がこのような仕事をしていました。
 C#から考えると、Linq での IEnumerable<T>型 に対する Select拡張メソッド も同じですね。
 それでは、実際に実装してみたいと思います。

実装

import java.util.function.*;

public class Chain<V>
{
    public static <V> Chain<V> create(V value)
    {
        return new Chain<V>(value);
    }
    
    private V value;
    
    private Chain(V value)
    {
        this.value = value;
    }
    
    public V get()
    {
        return this.value;
    }
    
    public Chain<V> accept(Consumer<V> f)
    {
        f.accept(this.value);
        return this;
    }
    public Chain<V> accept(BiConsumer<Chain<V>, V> f)
    {
        f.accept(this, this.value);
        return this;
    }
    
    public <R> Chain<R> apply(Function<V, R> f)
    {
        return Chain.create(f.apply(this.value));
    }
    public <R> Chain<R> apply(BiFunction<Chain<V>, V, R> f)
    {
        return Chain.create(f.apply(this, this.value));
    }

    public boolean test(Predicate<V> f)
    {
        return f.test(this.value);
    }

    public <R> Chain<R> bind(Function<V, Chain<R>> f)
    {
        return f.apply(this.value);
    }
    public <R> Chain<R> bind(BiFunction<Chain<V>, V, Chain<R>> f)
    {
        return f.apply(this, this.value);
    }
}

 実装してみると、綺麗に何もしないモナドが作れそうだったのでついでに bind メソッドも作っておきました、が、このbindは実際に使うことはなさそうです。
 モナドを作るのがメインではなかったので、関数の対応のみ軽く書いておきます。
  return = create
  bind = bind
  (fmap = apply)
 それでは、実際に使ってみます。

public class Test
{
    public static void main(String[] args)
    {
        System.out.println(
            Chain
                .create(20)                                 // 初期値、型の宣言が不要
                .apply( num -> "num = " + num.toString() )  // ラムダ式を渡すことによる間接的な操作
                .accept( str -> System.out.println(str) )   // アクションの実行
                .apply( str -> str + " test" )              // もう一度操作
                .get()                                      // 値を取り出す
        );
    }
}

結果

num = 20
num = 20 test
続行するには何かキーを押してください . . .

 メソッドチェーンを続けている間は型宣言なしで操作し続けることができましたね。
 これで入れ子になったジェネリクス型を扱うときなどに楽そうです!

おわりに

 C#やってるからJavaもすぐできると思っていましたが、細かい違いが意外と目に付いて頭になじむまでは少しかかりそうです。
 特に大きな違いとして、ジェネリクス型を扱うとき、型引数の数のみが違うクラスやメソッドが扱えない点が挙げられます。
 次は、この違いが大きく出る Tuple の実装を行ってみたいと思います。
 またこの Tuple は型が非常にややこしくなる為、今回実装した Chainクラス に本領を発揮してもらいたいと思います。

2016/7/27追記

 Tuple のエントリでは使いませんでした!!
 でもここでの推論させ方がそのまま利用されており、こちらの Chainクラス と組み合わせて使う場合も想像できるのではないかと思います。