関数型プログラミングに関する世界観です。
関数型プログラミングとは、Lisp、Haskell、MLなどの関数型プログラミング言語にみられる考え方。
関数型プログラミングでは、ステートメントが「文」ではなく「式」として扱われる。このため、ほとんどすべての記述が返り値を持つ。
また、関数は第一級オブジェクトとなる。このため、関数オブジェクトを変数に格納したり、関数への引数として関数を渡すことができる。そして、map()のような関数を引数とする関数(高階関数)を多用する。
また、変数は代入ではなく束縛される。値を変更することができず、基本的に値から新しい値を作成すること、関数として引数から新しい値を返すことで計算を行う。
また、繰り返しはfor文のような手続き的なループを用いることなく、関数から自分自身の関数を呼び出す「再帰」を用いて行う。
イテレータ、クロージャ、無名関数・ラムダ式などは、関数型プログラミング言語における特有の言語機能であり、上手く使うことでC/C++を使うよりもエレガントにプログラムを記述できる。
2023.01.19編集
また、純粋関数型言語では、式や関数が評価される時に、副作用が生まれない。
副作用とは、参照している変数の状態を破壊するという意味。
また、「同じ引数と同じ式を与えれば必ず同じ結果が返る」という性質を「参照透過性」という。
C/C++のような手続き型のプログラミング言語では、変数を代入したり変更したりすること、すなわち「副作用」が前提となってプログラミングパラダイムとなっている。
だが、Lispのような関数型言語では、副作用を嫌い、多くのことを関数呼び出しによる返り値で行う。
すなわち、値を「変更」するのではなく、新しく「新作成」するのである。
このようなプログラミングパラダイムは関数型プログラミングと呼ばれる。最初のうちは、「副作用なしでどうやったらプログラムが書けるの?」と思うかもしれない。だが、実際やってみると、副作用が必要となる場合は、特にLispのような関数型プログラミング言語においては「とてもレアケース」であることが分かる。
そう、わたしたちは関数型プログラミングを行うことで、副作用なしでもプログラムを書けるのである。
後日注記:確かに、変更できる変数は便利です。たとえば、インデックスのカウンタとして、1回目、2回目、3回目をカウントする時に、記憶を変更してインクリメントすることは、手続き型プログラミングでは慣習となっています。しかしながら、このような場合、関数をその回数の分だけ実行して、その引数として連続した値を与えることでも、反復処理は実現できます。副作用を用いずカプセル化をする、関数型プログラミングはこのようなところから始まります。
後日注記:そもそも、人間の頭脳として、記憶を「変更」する必要があるというのは稀だと思います。多くの場合、記憶を「新しく作り出す」ことのほうが多いでしょう。しかしながら、例外があります。それはIO処理です。入出力は、相手と自分の情報を「手紙という媒体によって渡す」ということであり、IO処理を副作用なしに書くことは難しいです。Haskellでは、IO処理について、圏論のアイデアを使った「モナド」という方法を使っています。
後日注記:変更可能な変数をミュータブル、変更不可能な変数をイミュータブルと言います。
詳しくは以下の書籍が参考になります。
関数型言語などで使われる関数の再帰的呼び出しの方法。
自分の中で自分を呼び出す関数のことを言う。「関数の中でその関数を呼び出す」ことを呼ぶ、と言うのが定義らしい。
Lispなどで計算する時に、良く使われる。
僕も昔は再帰処理を色んなところで書くのが好きだった。
オブジェクト指向、ポインタ、再帰は、プログラムを書く時の三大重要概念だと僕は思っています(ビル・ジョイがポインタと再帰の重要性について言っていました)。出来れば使いこなせることが望ましいでしょう。
後日注記:再帰は、データ構造のスタックと相性がいい。たとえば、算術式のカッコを処理する時、最後に開いたカッコが最初に閉じるべきカッコとして対応する。このような時、オペコード・オペランドと式の操作は再帰的に処理される。
詳しくは以下の書籍が参考になります。
以下は自分で書いたコード。
再帰を使う場合:
int funcA(int x)
{
static int i = 0;
printf("%d : %d\n", i, x);
i++;
if (i >= 10) {
return 0;
} else {
funcA(x + 10);
}
}
int main()
{
funcA(1);
}
for文を使う場合:
int main()
{
int i = 0;
int x = 1;
for (i = 0; i < 10; i++) {
printf("%d : %d\n", i, x);
x = x + 10;
}
}
実行結果:
0 : 1 1 : 11 2 : 21 3 : 31 4 : 41 5 : 51 6 : 61 7 : 71 8 : 81 9 : 91
「値を変えて同じルーチンを繰り返す」という意味では、for文のブロックと再帰関数は変わらない。
funcA()関数では、関数を繰り返し実行してもローカル変数iを静的に保持するために、static宣言をつけている。
繰り返し処理を行うには、これら「for文による反復」と「再帰」以外に、イテレータ(反復子)を使う方法がある。大規模なプログラミングでは、イテレータを使ってプログラムを記述することもある。
for (Iterator i = lst1.iterator(); i.hasNext();) {
System.out.println(i.next());
}
Java(コレクションと配列)も参照のこと。
2025.02.10編集
Lispでプログラムを書く時に記述する文章の形式をS式と言います。特に、(lisp (lisp lisp (lisp lisp)))のように、「カッコの中にいくらでもカッコが出来る」のが特徴です。
Lispを参照のこと。
関数型プログラミングにおいては、破壊的操作や副作用を嫌います。すなわち、変数に対して、代入したり、変更したりといったことを行うのではなく、それぞれの値を束縛して、関数的に、その時その時値を作り出します。
この意味を理解するためには、従来の手続き型のプログラミングを「箱に入れる型」とし、関数型のプログラミングを「ラベルを付ける型」であると考えると分かりやすいです。
C言語などでは、変数を入れるボックスとしてxを作ります。このxに、最初は2を入れるとしたら、xには2が入っています。次に、このxに3を入れると、今はxには3が入っています。同様に、4, 5, 6と変更していきます。そうすると、同じxというボックスの名前であっても、その時ボックスの中にある値は異なります。
ですが、わざわざボックスの中に入れなくても、それぞれの入れようとする値に対して、ラベルをつければどうでしょうか。
まず、xに入れる2について、twoというラベルをつけます。3にはthree、4にはfour、5にはfive、6にはsixとラベルをつけます。こうすることで、ボックスであるxの中に入れなくても、入れる前の段階でラベルを付けることで、それぞれの値にラベルがつき、そのラベルを使って、xというボックスの名前を使わずに、それぞれの値を参照できるのです。
ラベルの場合、2は必ずtwoです。2がthreeになることはありません。なので、xに今何が入っているのか、ということを気にする必要がありません。また、もし4をfourとして参照して、その前の値だったthreeから3を参照することが、fourを参照した後でもできます。すなわち、前の値が消え去らず、全部残った上で作業できるのです。
ほかの例としては、アンドゥのような元に戻す機能を作る際に、「ひとつ前に戻す」という機能しかなく、そのひとつ前に戻すアクションを、ひとつひとつの行動のたびに書き換えるのではなく、すべての履歴を取っておいて、その履歴をいつでもひとつ前に戻すことができるようにすること、そのような感覚が、関数型言語に近いと言えるでしょう。そうすることで、ひとつ前だけではなく二つ前、三つ前、あるいは履歴すべての前に戻ることも簡単にできるようになるのです。
2025.06.09
純粋関数型言語においては、IOやネットワークの処理ができないとか、できたとしても普通の手続き型プログラミング言語のコードとはまったく異なるあり得ないコードになると多くの人は思い込んでいます。
ですが、以下の記事にあるように、純粋関数型言語のひとつPureScriptでは、JavaScriptとほぼ同様の書き方が可能です。
純粋関数型言語はおかしなコードになるというのは誤った思い込みです。現代的な純粋関数型言語では、手続き型とほぼ同様に、状態や作用を普通に書くことができます。
2026.02.01
「Scalaスケーラブルプログラミング第3版」で記述されているように、関数型と命令型のプログラミングスタイルを見分けるためには、副作用の有無を知ればいい。
関数型言語には、副作用がないが、命令型言語には、副作用がある。
そして、Scalaにおいては、関数の戻り値がUnit型であれば、副作用によって成り立っているプログラミングスタイルであると判別できる。
Unit型とは、空の値のことだ。Unit型は()という値を持つ型であり、式が意味のある値を返さない時に返される。たとえば繰り返しを意味するforループなどは、それ自体が意味のある値を返さない。だから、ScalaではforループはUnit型を返す。
Unit型を返す処理ということは、関数の戻り値以外のなんらかの作用、すなわち「副作用」によってプログラムが記述されていることを意味する。
たとえば、文字列を一行ごとに出力する関数などで、戻り値を返すことなく、println()で一行ごとに画面に出力するようなプログラムは、関数型ではなく、命令型である。
そのようなプログラムを、どうしたら関数型に変えられるか。それは、関数の戻り値でプログラムを記述するようにすることだ。たとえば、一行ごとに出力するのではなく、各行を整形して連結し、その文字列を戻り値として返すような関数を作ることができる。これは関数の戻り値として、文字列から文字列を返すようなプログラムとなる。上の書籍でも、このプログラムの記述例が登場するが、これは関数型である。関数に引数として渡した文字列から戻り値の文字列を返すようにした上で、最終的にこれをprintln()で出力する。これは副作用のない、関数型プログラミングの一例である。
このようにすることで、大きくメリットとして挙げられるのは、テストの容易さである。関数型のプログラミングスタイルで記述されたコードは、関数の引数と戻り値によって成り立つため、容易にテストできる。関数の引数と戻り値は、テスト用の関数で簡単にチェックすることができる。
さらに言えば、変数を変更可能なvarではなく、変更不可能なvalを使うことなどでも、さらに関数型のスタイルを突き詰められる。変更可能、すなわちミュータブルな変数は副作用を持つ。副作用をなくすためには、変更不可能、すなわちイミュータブルなvalを使うことが必要である。これはC++などでもconstの利用が推奨されるのと同様で、変数の値がどこかの誰かによって変更されていないことを保証できるため、不用意にバグが混入しづらいし、理解しやすいプログラムになる。
Scalaでは、関数型のスタイルも、命令型のスタイルも、どちらも利用することができる。Scalaでは命令型のプログラミングを必ずしも排除しない。命令型も関数型もどちらも有効ならば積極的に使っていくための「ハイブリッドスタイル」、これがScalaの大きな特徴である。しかしながら、関数型のスタイルにはメリットが大きいため、できるだけ関数型で書ける際には関数型で書くべきだし、命令型に慣れたプログラマも、努力して関数型のスタイルを身に着けるべきだとScalaは推奨している。
たとえば、Scalaのコレクションクラスでは、イミュータブルなコレクションクラス(配列、リスト、ベクター、マップ、集合など)だけではなく、ミュータブルなコレクションクラスも用意されている。真に破壊的作用のない関数型のプログラミングを突き詰めるのであれば、配列やリストであっても、一部を変更するのではなく、変更されるたびに全体を戻り値として返すようにするべきだが、それではプログラムの実行効率が悪くなり、パフォーマンスが悪化してしまう。なので、一部分だけを変更できるようなコレクションクラスを使うこともできるが、そのためには明示的なインポートが必要となる。
ほかにも、文字列などは標準ではイミュータブルだが、テキストエディタのバッファのように、変更可能な文字列のバッファを使いたい場合は、文字列ビルダーというJavaにも存在するクラスが用意されている。また、Scalaでは、新しい値全体のコレクションを生成するようなコレクションクラス(トランスフォーマー)であっても、ビューを使うことで、オンデマンドで値を生成するように、コレクションクラスの内部の処理を変えることができる。なので、命令型のプログラマも安心してScalaを利用することができる。
Scalaも参照のこと。
2026.04.01
関数型プログラミングについて知るには、以下の入門記事がお勧めです。宣言的プログラミング、イテレータの意味と遅延評価、関数を返す関数やカリー化などの基本がわかります。
2026.04.15-16
イテレータとジェネレータを参照のこと。
クロージャ・無名関数・関数オブジェクトを参照のこと。
宣言的プログラミングについてはElmを参照のこと。
for文を使うのではなく、関数の内部から「自身の関数を呼び出す」ことで、繰り返しを「引数とreturn」で表現する。