Java コンビネーション

# 概要

Java のコンビネーションパターンを試してみました

結果は、関数型(コンビネーションパターン)の方がコードは可読性が増すけど、メモリを多く消費するようです。速度はほとんど変わりがないようでした。

## 参考

- [Combinator Pattern in Java 8](https://www.slideshare.net/gtrefs/combinator-pattern-in-java-8)
- [Javaラムダ式：コンビネーターパターンの改善](https://www.fixes.pub/program/54986.html)
- [functional programming](https://stackoverflow.com/questions/69079291/combinator-pattern-java-function-e-t)

# やり方

## 手続き型

```java
    public ValidationResult imperativeValidate(Parson parson) {
        var result = ImperativeValidator.isValidName(parson.getName());
        if (result != ValidationResult.SUCCESS) {
            return result;
        }
        result = ImperativeValidator.isValidAddr(parson.getAddr());
        if (result != ValidationResult.SUCCESS) {
            return result;
        }
        return ImperativeValidator.isValidAge(parson.getAge());
    }
```

## 関数型


```java
    public ValidationResult functionalValidate(Parson p) {
        return CombinatorPatternValidator.isValidName()
                .and(isValidAddr())
                .and(isValidAge())
                .apply(p);
    }
```

# 結果

##  JVM ヒープ

関数型が 124 KB に対して、 手続き型が 66 KB でした。

```
[QUICK PERF] Measured heap allocation (test method thread): 124.14 Kilo bytes (127 120 bytes)
[QUICK PERF] Measured heap allocation (test method thread): 66.49 Kilo bytes (68 088 bytes)
```

## ベンチマーク

何回かやりましたが、あんまり変わらない、関数型の方がちょっと遅いという感じの結果になっています
(ここはバリデーションの内容によるのかもしれないです)

```
Benchmark                                   Mode  Cnt       Score      Error  Units
CombinatorPatternBenchmarkTest.functional  thrpt    5  859447.226 ± 5632.616  ops/s
CombinatorPatternBenchmarkTest.imperative  thrpt    5  865311.952 ± 4481.358  ops/s
```

# 付録

## コード全体

### Data

```java
@AllArgsConstructor
@Data
public class Parson {
    private String name;
    private int age;
    private String addr;
    private String addr2;
}
```

### enum

```java
public enum ValidationResult {
    SUCCESS,
    HOGE_CANNOT_BE_USED_IN_NAME,
    ADDR_IS_TOO_LONG,
    IS_A_CHILD,
}
```

### 手続き型バリデーター

```java
public class ImperativeValidator {
    public static ValidationResult isValidName(String name) {
        return name.contains("hoge") ? HOGE_CANNOT_BE_USED_IN_NAME : SUCCESS;
    }

    public static ValidationResult isValidAddr(String addr) {
        return addr.length() > 100 ? ADDR_IS_TOO_LONG : SUCCESS;
    }

    public static ValidationResult isValidAge(int age) {
        return age <= 18 ? IS_A_CHILD : SUCCESS;
    }
}
```

### 関数型(コンビネーターパターン)バリデーター

```java
interface CombinatorPatternValidator extends Function<Parson, ValidationResult> {

    static CombinatorPatternValidator isValidName() {
        return p -> p.getName().contains("hoge") ? HOGE_CANNOT_BE_USED_IN_NAME : SUCCESS;
    }

    static CombinatorPatternValidator isValidAddr() {
        return p -> p.getAddr().length() > 100 ? ADDR_IS_TOO_LONG : SUCCESS;
    }

    static CombinatorPatternValidator isValidAge() {
        return p -> p.getAge() <= 18 ? IS_A_CHILD : SUCCESS;
    }

    default CombinatorPatternValidator and(CombinatorPatternValidator other) {
        return p -> {
            var result = this.apply(p);
            return result.equals(SUCCESS) ? other.apply(p) : result;
        };
    }
}
```

### バリデーションサービス

```java
public class ValidationService {

    public ValidationResult imperativeValidate(Parson parson) {
        var result = ImperativeValidator.isValidName(parson.getName());
        if (result != ValidationResult.SUCCESS) {
            return result;
        }
        result = ImperativeValidator.isValidAddr(parson.getAddr());
        if (result != ValidationResult.SUCCESS) {
            return result;
        }
        return ImperativeValidator.isValidAge(parson.getAge());
    }

    public ValidationResult functionalValidate(Parson p) {
        return CombinatorPatternValidator.isValidName()
                .and(isValidAddr())
                .and(isValidAge())
                .apply(p);
    }
}
```


## テストコード

### ヒープ測定

[Javaでヒープサイズ測定 JUnit5編](https://qiita.com/gosshys/items/e6aa2fe12762cf29e9c0) を参考

```java
@QuickPerfTest
public class CombinatorPatternTest {

    ValidationService service = new ValidationService();

    @MeasureHeapAllocation
    @Test
    void imperative() {
        service.imperativeValidate(create());
    }

    @MeasureHeapAllocation
    @Test
    void functional() {
        service.functionalValidate(create());
    }

    Parson create() {
        return new Parson(RandomStringUtils.randomAlphabetic(10), new Random().nextInt(100), RandomStringUtils.randomAlphabetic(20), RandomStringUtils.randomAlphabetic(20));
    }
}
```

### ベンチマーク

[Javaでベンチマーク(性能測定) JUnit5編](https://qiita.com/gosshys/items/72f56daf9ee01e10e987)　を参考

```java
@State(value = Scope.Benchmark)
public class CombinatorPatternBenchmarkTest {

    ValidationService service = new ValidationService();

    @Benchmark
    public void imperative() {
        service.imperativeValidate(create());
    }

    @Benchmark
    public void functional() {
        service.functionalValidate(create());
    }
    
    @Test
    void benchMark() throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(CombinatorPatternBenchmarkTest.class.getSimpleName())
                .forks(1) // 1回実行
                .warmupIterations(1) // 1回繰り返し
                .build();
        new Runner(opt).run();
    }

    Parson create() {
        return new Parson(RandomStringUtils.randomAlphabetic(10), new Random().nextInt(100), RandomStringUtils.randomAlphabetic(20), RandomStringUtils.randomAlphabetic(20));
    }
}
```