関数型と手続き型の違い
ふととあるところで、 関数型に書かれていない みたいな記述を見つけました。このときなんかモヤっとしたんですが、うまく言語化出来なかったので、ちょっと書いてみます。 <!–more–> まず始めに観測する 関数型 とか 手続き型 と言いますが、一体どういう基準で話しているかは、書き手・話し手に依存するようです。ただ、ある程度一貫しているのは 関数型という場合、多くの場合は関数がファーストクラス 手続き型という場合、低レイヤーな言語で書かれているようなものを指しているケースが多い 稀に、関数型言語と手続き型言語という感じでの使い方もされる様子 関数型言語としてはHaskell/Lispなど 手続き型言語としてはC/昔のJavaなど くらいのようです。私の観測範囲が狭すぎるのであれですが・・・。 関数型の書き方とは? Java7から8になったタイミングでよく言われたのは、 Project Lambda によって導入されたLambda式でした。私もご多分に漏れずよろこんで使っているわけですが。ただ、これはJavaという言語が関数を言語のファーストクラスにした、という意味ではなく、単純にあまりに冗長だった無名インターフェースを簡単に書けるようにした糖衣構文です。 例えばこういうのが Thread thread = new Thread(new Runnable() { @Override public void run() { ... } }); こうなります。 Thread thread = new Thread(() -> {...}); どう見ても後者の方が圧倒的に短いです。ですが、これは単に () -> {} が、 Runnableインターフェースの run メソッドの実装として扱われているだけです。IntelliJとかであれば、RefactorだったかSourceから、糖衣構文にした場合としない場合にそれぞれ変換できますので、やってみるとわかりやすいです。 同じくJava8で入った Stream は、このLambdaを使い倒して貰おうというのが明白なインターフェースをしています。大抵、このStreamとLambdaを組み合わせて書いたものを関数型的というケースが多いようです。 何がモヤッとするのか 一応今までに Haskell や Common Lisp、 OCaml(公式が表示されなかったので日本版) を触っていますし、OCamlは今も継続して使っています。Javaは仕事で大量に書きましたし、JavaScriptも大量に書いています。C/C++も普通に使っていました。それぞれ、関数型言語と言われたりオブジェクト指向言語であったり、手続き型(C++はあれですが)言語と言われていたりします。 そんな中でモヤっとするのは、 見た目だけで関数型かどうかは決まらないのに、スタイルで語るのはなんか違うのでは無いか と最近思ったりするからです。試しにやってみるとわかりますが、Stream + Lambdaで調子に乗ってベタ書きすると、すぐに再利用不可かつ、for文で書くよりも可読性の悪いものが出来上がります。 関数型と手続き型の狭間 では実際に、私の思う手続き型と関数型の違いをコードにしていってみます。ここでは私が一番Loveな言語であるOCamlを使います。 let () = let num = ref 12345 in let buffer = Bytes.make 5 ' ' in for i = 5 downto 1 do let n = !num mod 10 in let v = match n with | 1 -> '1' | 2 -> '2' | 3 -> '3' | 4 -> '4' | 5 -> '5' | _ -> assert false in Bytes.set buffer (pred i) v; num := !num / 10 done ; print_string (Bytes.to_string buffer) 12345 という数字を "12345" という文字列にするのを、ものすごく冗長に、かつrefや副作用バリバリで書いてみました。OCamlにはwhileもありますが、ここではforを使いました。OCamlでforを使ったのは初めてです。 ...