アプリ開発を担当しています。どんなネタがいいかな〜と悩みましたが、手続き型と関数型の違いを説明しようかなと思いました。
みなさん最初は手続き型から入るとおもうのですが(よく見る教材は全部手続き型ですよね)、最近の開発のトレンド(マイクロサービス化とかね)からすると、関数型の方がコードを小さいユニットに分解できて、良い選択になってきていると思いますし、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とかみて、是非応募ください!