[入門]関数プログラミング―質の高いコードをすばやく直感的に書ける!

第1章 関数プログラミングは難しくない!―初めて学ぶ人にも,挫折した人にもきちんとわかる

この記事を読むのに必要な時間:およそ 4.5 分

マルチコア環境が身近になった影響からか,勉強熱心なプログラマの間で関数型言語が話題になっているようです。関数型言語が奨励する関数プログラミングでは,これまで命令型言語で習得した,命令プログラミングの再代入を使う技法があまり通用しません。そのため,「関数型言語は難しい」と言って途中で投げ出してしまう人も多いようです。

この特集では,関数プログラミングの習得を一度諦めてしまった人や,これから始める人のために,関数プログラミングのポイントをできるだけわかりやすく説明します。

筆者がわかるようになるまで

実は筆者も長い間,関数プログラミングを習得できませんでした。筆者は,長年Emacs Lispを使って,Mewというメールリーダーを開発してきました。人によってはLispを関数型言語に分類していて,そういう人は筆者のことを関数プログラマだと思っていたようです。しかし,実際のところEmacs Lispは命令型言語に分類するほうが適切であり,筆者がEmacs Lispでやっていたのは命令プログラミングでした。

そもそも関数プログラミングと命令プログラミングの違いもわかっていませんでしたし,「関数プログラミングでは再代入(破壊的代入)を使わない」と聞いたことはありましたが,再代入なしでプログラミングができるとは想像もできませんでした。

関数プログラマと思われているのに関数プログラミングをまったく知らないというジレンマを打破するために,また再代入なしのプログラミングとは何かを理解するために,2007年に関数型言語Haskellを勉強しはじめました。そして,関数プログラミングが本当にわかったと思えるまでに,1年半かかりました。

こう言うと関数型言語や関数プログラミングは難しいと思われるかもしれませんが,習得に時間がかかったのは先入観を持っていたり,良い入門書に出会えなかったからです。わかりやすい説明があれば,関数プログラミングはそれほど難しくはありません。ただ,関数プログラマの数が少ないので,良い入門書が圧倒的に少ないのは確かです。この特集では,「はじめからそう言ってもらえればわかったのに」という筆者の経験をもとに,重要なポイントを説明していきます。

関数型言語とは何か

あるようでない関数型言語の共通理解

そもそも関数型言語や関数プログラミングとは何でしょうか? 驚かれるかもしれませんが,これらの言葉に共通理解はありません。それぞれの人が,それぞれの意味でこれらの言葉を使っています。

たとえば,「関数に関数を渡せれば関数型言語だ」と言う人がいます。これは,あとで述べる筆者の定義とは異なります。

筆者の定義のほうが正しいと言うつもりはありません。理解していただきたいのは,このような共通理解の欠如が,関数型言語を難しいと思わせている一因だということです。関数型言語について議論するときは,まず「僕の関数型言語の定義はこう。君の定義は何?」と聞く必要があります。

筆者の関数型言語の定義

筆者の関数プログラミングの定義,すなわちこの特集での定義は,「永続データプログラミング」です。永続データとは,破壊できないデータ,つまり再代入できないデータのことです。そして,永続データを駆使して問題を解くのが永続データプログラミングです。

また関数型言語とは,永続データプログラミングを奨励し支援している言語のことです。関数型言語では,再代入の機能がないか,再代入の使用は限定されています。筆者の定義はかなり厳しいほうだと言えます。

この対比で言えば,命令プログラミングとは「短命データプログラミング」ということになります。短命データとは再代入により破壊できるデータのことです。for文のカウンタ変数iが典型的な短命データです。

後述するように,永続データはマルチコアと相性が良く,短命データはマルチコアと相性が悪いことが知られています。筆者の関数プログラミングの定義から言えば,関数プログラミングがマルチコア時代に期待されている理由がよくわかると思います。

関数型言語の仲間たち

筆者の関数型言語の定義に合うメジャーな言語を次に示します。

  • 静的型付き:Clean,F#,Haskell,OCaml,Scala,SML
  • 動的型付き:Clojure,Erlang

型の検査を(コンパイル時など)実行の前に実施するのが静的型付き,実行時に実施するのが動的型付きです。

Lisperの方には申しわけありませんが,筆者はCommon LispやEmacs Lispを関数型言語には分類していません。Schemeは微妙です。これらのLispは,「Lisp」という独自のパラダイムだと考えるほうがよいと思います。ClojureはLispの一種ですが,永続データプログラミングを推進しているので関数型言語に分類できます。

それぞれの言語を簡単に説明します(順不同)。

SML

その名の通り標準化されたMLMeta-Language)です。ML系の言語では,関数の型を書かなくても,正しいコードであれば型が推論でき,推論できた場合は型レベルの誤りがないことが保証されます。参照型や配列に対しては破壊的代入ができます。いくつか実装がありますが,筆者としては東北大学で開発されているSML#に期待しています。

OCaml

フランスのINRIA注1が開発したML系の言語です。コンパイラがあまり最適化を施さないにもかかわらず,出力されたコードが高速に動作します。参照型などへの破壊的代入が可能です。構造的サブタイピングによる型安全なオブジェクトや多彩なモジュール機能を持っています。

F#

MicrosoftがOCamlをもとにして開発した.NET上のML系言語です。Visual Studioで開発でき,多様な.NETのライブラリを利用可能です。

Scala

Martin Odersky氏が,オブジェクト指向プログラミングと関数プログラミングを融合するために作ったJVM用の言語です。環境の変化に追随できるように拡張性を重視して設計されており,一見組込みだと思われる機能がライブラリとして実装されています。

Clojure

Rich Hickey氏が開発したJVM用の革新的なLispです。古いLispの欠点(たとえば,わかりにくい関数名や括弧の多さ)を改善しています。永続データプログラミングを基本としており,破壊的な操作はSTMSoftware Transactional Memoryを使うことでのみ許されます。

Haskell

純粋関数型言語が乱立した状態を解決すべく標準化された純粋関数型言語です。Miranda注2に似た文法を持ち,デフォルトで遅延評価です。実質的な標準実装であるGHCでは,軽量スレッドやSTMが利用できます。

Clean

オランダのナイメーヘン大学で開発された純粋関数型言語です。Haskellと同様Mirandaに似た文法を持ち,デフォルトで遅延評価です。破壊的代入や入出力にまつわる問題を一意型という機能を使って解決しています。

Erlang

軽量プロセスを用いたネットワークプログラミングに特化した言語です。エリクソン社が開発しました。軽量プロセスは,ほかの軽量プロセスとメッセージをやりとりすることで処理を進めます。高い耐故障性を持ち,コードのホットスワップもできます。

注1)
Institut National de Recherche en Informatique et Automatiqueフランス国立情報学自動制御研究所。
注2)
商用目的で作られた最初の純粋関数型言語です。

これまでの知識が足枷(あしかせ)

かつての筆者がそうだったように,命令プログラマであれば,再代入なしにプログラムが書けるなんて想像もできないのではないでしょうか? 一体,再代入できる変数なしでどうやってfor文を実現するのでしょう?

関数プログラミングを習得するには,これまで命令プログラミングで培った技術はいったん忘れ,真っ白な気持ちで臨む必要があります。関数型の山を登るためには,命令型の山を降りなければなりません。関数プログラミングの習得が難しい理由の一つは,このようにプログラミングの初心者に戻ることを要求されるからだとも言えます。

この特集では,4つの問題を再代入なしに解いていきます注3)。

  • 数値計算──第2章
  • 最長重複文字列探索──第3章
  • 赤黒木(ハッシュテーブル)──第4章
  • CSVパーサの実装──第5章

関数プログラミングの雰囲気が伝われば幸いです。

注3)
本特集で掲載しているサンプルコードは,WEB+DB PRESS Vol.67サポートサイトからダウンロードできます。

著者プロフィール

山本和彦(やまもとかずひこ)

株式会社IIJイノベーションインスティテュート(IIJ-II)技術研究所 主幹研究員。

開発した代表的なオープンソフトにMew,Firemacs,Mightyがある。『プログラミングHaskell』や『Haskellによる並列・並行プログラミング』の翻訳者。職場ではHaskell,家庭では3人の子供と格闘する日々を送っている。

web:http://www.mew.org/~kazu/

twitter:@kazu_yamamoto

コメント

コメントの記入