iChain’s Engineer Blog

iChain株式会社の開発チームによるブログです

関数型っていいよね②

こんにちは。アプリ開発担当です。 以前手続き型と関数型の違いについて実際にコードを書きました。社内のエンジニアから続編を要望されたので、テーマを変えてまた書いてみたいとおもいます。今回のテーマはFizzBuzzです。

Fizz Buzz - Wikipedia

今回はJava11、標準ライブラリで書きました。

  void fizzbuzz1() {
    println("* fizzbuzz1()");

    for (var i = 1; i <= 100; i++) {
      if (i % 15 == 0) {
        println("FizzBuzz");
      } else if (i % 3 == 0) {
        println("Fizz");
      } else if (i % 5 == 0) {
        println("Buzz");
      } else {
        println(i);
      }
    }
  }
  void fizzbuzz2() {
    println("* fizzbuzz2()");

    for (var i = 1; i <= 100; i++) {
      var s = "";
      if (i % 3 == 0) {
        s += "Fizz";
      }
      if (i % 5 == 0) {
        s += "Buzz";
      }
      if (s.isEmpty()) {
        s += i;
      }
      println(s);
    }
  }
  void fizzbuzz3() {
    println("* fizzbuzz3()");

    var d = Map.of(3, "Fizz", 5, "Buzz", 15, "FizzBuzz");

    for (var i = 1; i <= 100; i++) {
      var b1 = BigInteger.valueOf(i);
      var b2 = BigInteger.valueOf(15);
      var gcd = b1.gcd(b2);
      println(d.getOrDefault(gcd.intValue(), Integer.toString(i)));
    }
  }

関数型だと、例えばこのように。

  boolean isFizz(int n) {
    return n % 3 == 0;
  }

  boolean isBuzz(int n) {
    return n % 5 == 0;
  }

  boolean isFizzBuzz(int n) {
    return n % 15 == 0;
  }

  String asFizzBuzz(int n) {
    return isFizzBuzz(n) ? "FizzBuzz"
        : isFizz(n) ? "Fizz"
        : isBuzz(n) ? "Buzz"
        : Integer.toString(n);
  }  

  void fp_fizzbuzz1() {
    println("* fp_fizzbuzz1()");

    IntStream.rangeClosed(1, 100)
        .mapToObj(this::asFizzBuzz)
        .forEach(this::println);
  }
  int increment(int n) {
    return n + 1;
  }

  void fp_fizzbuzz2() {
    println("* fp_fizzbuzz2()");

    var res = Stream.iterate(1, this::increment)
        .map(this::asFizzBuzz)
        .limit(100)
        .collect(Collectors.joining(System.lineSeparator()));

    println(res);
  }

  String combineLines(String a, String b) {
    return a + System.lineSeparator() + b;
  }
  void fp_fizzbuzz3() {
    println("* fp_fizzbuzz3()");

    Stream.iterate(1, this::increment)
        .map(this::asFizzBuzz)
        .limit(100)
        .reduce(this::combineLines)
        .ifPresent(this::println);
  }

いかがでしょう。 考え方、捉え方によって書き方は一通りではない、ということですね。簡単な動作の方が手軽に様々な書き方を試せるとおもいます。まだ関数型に慣れてない人は、このような簡単なものから始めた方がいいかもしれません。

関数型のほうが汎用性が高くなったり、複雑な処理も簡潔にかけるので便利なことが多いです。ただ処理スピードは手続き型の方が速いため、やむを得ず手続き型を使う場面もあります。また、実際の開発の場面では、関数型だけで実装できることは少なく、手続き型と併用して使用するケースがほとんどです。
重要なのは、要件や開発環境に合わせて適切に使えるということですね。

ただ、私は関数型が大好きです。

合わせて高階関数についても作成したので、そちらはいつか記事にしたいとおもいます。

※ iChainでは一緒に働く仲間を募集しています。よかったらHPとかwantedlyとかみて、是非応募ください!