二番煎じ：JavaでIf式をしてみる

「[JavaでIf式をしてみる](https://qiita.com/ishibashi-futoshi/items/85aa192d2ec0a97e0342)」の二番煎じ。

## 実現できること

例えば FizzBuzz を次のように書ける。

```Java
    public static void main(String... args) {
        for (int i = 1; i <= 100; ++i) {
            int count = i;
            String fizzBuzzed = 
                    If.<String>test(() -> count % 15 == 0).then(() -> "FizzBuzz")
                    .elif(() -> count % 3 == 0).then(() -> "Fizz")
                    .elif(() -> count % 5 == 0).then(() -> "Buzz")
                    .el(() -> Integer.toString(count));
            System.out.println(fizzBuzzed);
        }
    }
```

## 準備

if 式の値として nullable な値を扱うため、
次の汎用クラスを作成する。

```Java
import java.util.function.Supplier;

/**
 * null も値として取れる {@link java.util.Optional}。
 * 
 * 今回使わないメソッドについては省略。
 */
public class MayBe<T> {

    /**
     * 値が存在するインスタンスを生成する。
     */
    public static <R> MayBe<R> of(R result) {
        return new MayBe<>(true, result);
    }

    /**
     * 値が存在しないインスタンスを生成する。
     */
    public static <R> MayBe<R> empty() {
        return new MayBe<>(false, null);
    }

    private final boolean isPresent;
    private final T value;

    private MayBe(boolean isPresent, T value) {
        this.isPresent = isPresent;
        this.value = value;
    }

    /**
     * 値が存在するかどうかを返す。
     */
    public boolean isPresent() {
        return this.isPresent;
    }

    /**
     * 値が存在すればそれを返し、存在しなければ引数 other から得た値を返す。
     */
    public T orElseGet(Supplier<T> other) {
        return isPresent() ? this.value : other.get();
    }
}
```

## 本番

```Java
import java.util.function.BooleanSupplier;
import java.util.function.Supplier;

public class If<R> {

    public static <R> If<R> test(BooleanSupplier predicate) {
        return new If<>(predicate, null);
    }

    private final If<R> prev;
    private final BooleanSupplier predicate;

    private Then<R> then = null;

    /**
     * @param predicate この if の述語。
     * @param prev      この if が else if である場合、1つ前の if。
     *                  そうでない場合、null。
     */
    private If(BooleanSupplier predicate, If<R> prev) {
        this.prev = prev;
        this.predicate = predicate;
    }

    public Then<R> then(Supplier<R> valueSupplier) {
        if (this.then != null) {
            throw new IllegalStateException("`then` が既に呼び出されています。");
        }

        return this.then = new Then<>(this, valueSupplier);
    }

    /**
     * この if までを評価する。
     * 
     * @return 評価した結果の値。
     */
    private MayBe<R> eval() {
        if (this.then == null) {
            throw new IllegalStateException("`then` が未だ呼び出されていません。");
        }

        if (this.prev != null) {
            MayBe<R> prevValue = this.prev.eval();
            if (prevValue.isPresent()) {
                return prevValue;
            }
        }

        return this.predicate.getAsBoolean()
                ? MayBe.of(this.then.getThenValue())
                : MayBe.empty();
    }

    /**
     * {@link If#then} が返すオブジェクトのクラス。
     */
    public static class Then<R> {
        private final If<R> relatedIf;
        private final Supplier<R> thenValueSupplier;

        /**
         * @param relatedIf     この then の if。
         * @param valueSupplier この then の if が true である場合に返す値。
         */
        Then(If<R> relatedIf, Supplier<R> valueSupplier) {
            this.relatedIf = relatedIf;
            this.thenValueSupplier = valueSupplier;
        }

        public If<R> elif(BooleanSupplier predicate) {
            return new If<>(predicate, this.relatedIf);
        }

        public R el(Supplier<R> valueSupplier) {
            return this.relatedIf.eval().orElseGet(valueSupplier);
        }

        /**
         * この then の if が true の場合の値を返す。
         */
        R getThenValue() {
            return this.thenValueSupplier.get();
        }
    }
}
```

## 特長

- if の述語は必要なものだけが評価される。
- 値の取得も必要なものだけが評価される。
- else if （`elif`）が使えるため、入れ子が深くならずに済む。

## 所感

JavaScript でいう即時関数のようにした方が読みやすいな。

```Java
    public static void main(String... args) {
        for (int i = 1; i <= 100; ++i) {
            int count = i;
            String fizzBuzzed = ((Supplier<String>) () -> {
                if (count % 15 == 0) return "FizzBuzz";
                else if (count % 3 == 0) return "Fizz";
                else if (count % 5 == 0) return "Buzz";
                else return Integer.toString(count);
            }).get();
            System.out.println(fizzBuzzed);
        }
    }
```

／以上