関数型言語はこわくないよ!
関数型言語と聞くと、LispとかHaskelとかScalaとか難しいんだろうなーという先入観を持ってる人がいると思います。ぼくもそうでした。
でも、最近業務でScalaでプロトタイプのWebアプリケーション書いてて、関数型言語の概念が分かってきて、これってそんなに難しくないよね?と思い始めてきたのでその話です。
関数型は身近な存在 ~Linux パイプ編~
関数型言語は難しいと思ってる人でも、実は既に使いこなしている可能性があります。JavaやPHPしか書いたことがないという人でも、Linuxのコマンドは使っていると思います。
例えば、今起動中のプロセスからjavaが入っているもの
を調べてと言われたらどうするでしょうか。
多分こうしますよね。
$ ps aux | grep java
当たり前過ぎて気づいてないかもしれませんが、これが関数型の考え方なんです。
ps aux
でプロセスの一覧を取得- 1で取得した一覧に対して、
grep
という関数(引数にjavaをとる)を適用する
1と2をパイプでつなぐことによって、関数を適用して、今起動中のプロセスからjavaが入っているもの
を表示するということを実現しています。
何を言ってるか分からないよという人は、これを手続き型で書いてみましょう。
#!/bin/bash IFS=$'\n' # ここからが処理内容 for p in `ps aux` do if [[ $p =~ .*java.* ]]; then echo $p fi done
1行で済んでたのが、なんだかすごく長くなってしまいましたね。
自分で書いた文字列判定処理まで入っています。自分で書いた判定処理が正しいかどうかテストしなきゃいけないし、そもそもこんな処理書きたくないですよね。当然たくさんの人によって使われているgrepというコマンドの方がテストもされているし正しいと言えます。
こんな風に関数型というのは、実はみなさんの身近に存在しているんだよという話でした。
身近な関数型っぽいもの ~jQuery編~
その他の、身近な例としてjQueryにも関数型っぽいものがあります。これも気づかないうちに使ってた人いるんじゃないんでしょうか。
例えば、セレクトボックスのリストの中からvalueが5のものだけを取得する
という処理を考えてみましょう。まずは、手続き型。手続き型で考えるなら、こういう処理になります。
- セレクトボックスの中身を取得
- 結果格納用の変数を用意
- 1で取得したリストをループで回しvalueが5か判断する
- valueが5だったら結果格納用の変数に格納する
var $list = $("select option"); var $result; $list.each(function() { var $option = $(this); if ($option.val() == "5") { $result = $option; } }); console.log($result.val());
次は、関数型で書いてみます。jQueryには、filter
という関数型的な考え方が適用できるメソッドが用意されています。これを使って、関数型だとこういう処理になります。
- セレクトボックスを取得
- 1で取得したセレクトボックスに対してvalが5だけのものを取得する
filter
関数を適用
var $list = $("select option"); var $result = $list.filter(function(index) { return ($(this).val() == "5"); }); console.log($result.val());
どうでしょうか。手続き型よりも、すっきり書けていると思いませんか。 先ほどのLinuxのパイプみたいに、取得したリストに対して関数を適用するという考え方です。
関数型がすっきり書けるという例
身近な例で、関数型を紹介してきましたがこわくないと思ってもらえたでしょうか。
次は、実際に、Scalaで処理がとかでさらにすっきり書ける例を見ていきます。
例えば、複数のURL文字列から、ホスト名だけを抜き出したいみたいな処理って出てきたりしませんか?
URLはないにしても、なんか、文字列を別のクラスとして生成して配列につめ直すみたいな処理です。
手続き型で考えるとこうなりますよね。
処理対象のURL文字列があったとして、
- URLオブジェクト格納用の配列を用意
- URL文字列をループでまわしてURLオブジェクトを生成
- 2で生成したオブジェクトを1で用意した配列に追加
- URLオブジェクト配列をループでまわしてホスト名だけを表示
// 処理の対象となるURL文字列の配列 List<String> urlStrings = Arrays.<String>asList( "http://main.example.com/index/", "http://sub.example.com/about/", "http://other.example.com/sitemap/"); // URLオブジェクト格納用の配列 List<URL> urls = new ArrayList<URL>(); // URL文字列をループでまわしてURLオブジェクトを生成して格納 for (String urlString : urlStrings) { urls.add(new URL(urlString)); } // URLオブジェクト配列をまわして表示 for (URL url : urls) { System.out.println(url.getHost()); }
手続き型だと、どうしてもこの格納用のものを用意するという作業が発生してしまいます。しかし、関数型だとこの一時変数みたいなものがなくせるのですっきりと書けるようになります。
では、Scalaではどうなるか見てみましょう。
// 処理の対象となるURL文字列の配列 val urlStrings = List( "http://main.example.com/index/", "http://sub.example.com/about/", "http://other.example.com/sitemap/") // URL文字列リストをURLオブジェクトリストに変換して変換後の各要素に対してprintlnを実行 urlStrings.map(new URL(_)).map(_.getHost).foreach(println)
どうでしょうか。ヤバくないですか? Scalaの文法を知らないと読めないものはあるにしても、雰囲気は伝わるんじゃないかと思います。
簡単に説明すると、以下のようなことが行われています。
urlStrings.map(new URL(_))
という部分が、URL文字列リストをURLオブジェクトリストに変換するmap
というのがScalaの関数でリストの各要素に対して処理を行って、変換後のリストをまた返しているnew URL(_)
の_
は、元のリストの要素を表す。ここでは、URL文字列の一つ一つの要素- 2つ目の
.map(_.getHost)
がさらに、URLオブジェクトリストをホスト名だけのリストに変換する - その変換された各要素に対して、
foreach
がprintln
という出力するメソッドを実行する
今までLinuxのパイプやjQueryでも見てきたあるリストに対して関数を適用するという考え方ですね。 どうでしょうか。Scala(関数型)の良さが伝わりましたか?
まとめ
すごく難しいと思ってた関数型ですが、実はすごく分かりやすいというか、そもそも既に使っていた概念だったということを分かってもらえたでしょうか。まだまだ、Scala風な書き方というのは習得できていないのでそのあたりはこれからですが、関数型という概念の説明にはなってるかなと思います。これを見て、Scala以外でも、関数型言語を触ってみようというきっかけになれば嬉しいです。