iChain’s Engineer Blog

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

関数型っていいよね、って思います。

アプリ開発を担当しています。どんなネタがいいかな〜と悩みましたが、手続き型と関数型の違いを説明しようかなと思いました。

みなさん最初は手続き型から入るとおもうのですが(よく見る教材は全部手続き型ですよね)、最近の開発のトレンド(マイクロサービス化とかね)からすると、関数型の方がコードを小さいユニットに分解できて、良い選択になってきていると思いますし、iChainのサービスもそのトレンドを踏襲しています。また、シンプルかつコードの安全性を高めやすいので、個人的には関数型を強く推してます。

ただ、「こっちの方がいいよ!!」って叫んでも伝わらないですよね。それなら実物を見てみましょう!というわけで。みんな大好きwcコマンドを手続き型と関数型で書いてみました。 (Java8、標準ライブラリだけで書きました。)

まず手続き型。

package jp.co.ichain.study.javaproc;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.Reader;

public class App {
  public static void main(String[] args) {
    int tlCtLines = 0;
    int tlCtWords = 0;
    int tlCtBytes = 0;
    int ctFiles = 0;

    for (String path : args) {
      File file = new File("..\\" + path);
      if (!file.isFile()) {
        continue;
      }

      int ctLines = 0;
      int ctWords = 0;
      int ctBytes = 0;

      try (Reader reader = new BufferedReader(new FileReader(file))) {
        boolean inWord = false;
        int ch;
        while ((ch = reader.read()) != -1) {
          ctBytes++;

          if (Character.isWhitespace(ch)) {
            if (inWord) {
              inWord = false;
              ctWords++;
            }

            if (((char) ch) == '\n') {
              ctLines++;
            }
          } else {
            inWord = true;
          }
        }
      } catch (Exception e) {
        e.printStackTrace();
      }

      tlCtLines += ctLines;
      tlCtWords += ctWords;
      tlCtBytes += ctBytes;
      ctFiles += 1;

      System.out.printf("l: %d, w: %d, b: %d, f: %s\n", ctLines, ctWords, ctBytes, path);
    }

    if (ctFiles > 1) {
      System.out.printf("l: %d, w: %d, b: %d, f: total\n", tlCtLines, tlCtWords, tlCtBytes);
    }
  }
}

どうですか?

続いて関数型。

package jp.co.ichain.study.javafunc;

import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.collectingAndThen;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.IntStream;

public class App {
  private static class CountResults {
    private final String path;
    private final int ctLines;
    private final int ctWords;
    private final int ctBytes;

    CountResults(final String path, final int cls, final int cws, final int cbs) {
      this.path = path;
      this.ctLines = cls;
      this.ctWords = cws;
      this.ctBytes = cbs;
    }
  }

  private static byte[] readAllBytes(final Path p) {
    try {
      return Files.readAllBytes(p);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  private static IntStream newByteStream(final byte[] bs) {
    final ByteArrayInputStream input = new ByteArrayInputStream(bs);
    return IntStream.generate(input::read).limit(input.available());
  }

  private static boolean isLF(final int c) {
    return ((char) c) == '\n';
  }

  private static int countLines(final byte[] bs) {
    return (int) newByteStream(bs).filter(App::isLF).count();
  }

  private static int countWords(final byte[] bs) {
    return new String(bs).trim().split("\\s+").length;
  }

  private static CountResults countFile(final File f) {
    final byte[] bs = readAllBytes(f.toPath());
    return new CountResults(f.getPath(), (int) countLines(bs), countWords(bs), bs.length);
  }

  private static void printCountLine(final CountResults res) {
    System.out.printf("l: %d, w: %d, b: %d, f: %s\n", res.ctLines, res.ctWords, res.ctBytes, res.path);
  }

  private static File newFile(final String path) {
    return new File("..\\" + path);
  }

  private static CountResults sumCountResults(final CountResults a, final CountResults b) {
    return new CountResults("total", a.ctLines + b.ctLines, a.ctWords + b.ctWords, a.ctBytes + b.ctBytes);
  }

  public static void main(final String[] args) {
    final List<CountResults> resList = Arrays.stream(args)
        .map(App::newFile)
        .filter(File::isFile)
        .map(App::countFile)
        .collect(collectingAndThen(toList(), Collections::unmodifiableList));

    resList.forEach(App::printCountLine);

    if (resList.size() > 1) {
      resList.stream()
          .reduce(App::sumCountResults)
          .ifPresent(App::printCountLine);
    }
  }
}

オプションがないとか言わないで。(意外と大変だった)

どっちがいいか、ケースバイケースなことが多いので、目的や会社の方針に合わせて選択するのでしょう。

でも私は、関数型が好き。

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