関数型プログラミングに関する世界観です。
関数型プログラミングとは、Lisp、Haskell、MLなどの関数型プログラミング言語にみられる考え方。
関数型プログラミングでは、ステートメントが「文」ではなく「式」として扱われる。このため、ほとんどすべての記述が返り値を持つ。
また、関数は第一級オブジェクトとなる。このため、関数オブジェクトを変数に格納したり、関数への引数として関数を渡すことができる。そして、map()のような関数を引数とする関数(高階関数)を多用する。
また、変数は代入ではなく束縛される。値を変更することができず、基本的に値から新しい値を作成すること、関数として引数から新しい値を返すことで計算を行う。
また、繰り返しはfor文のような手続き的なループを用いることなく、関数から自分自身の関数を呼び出す「再帰」を用いて行う。
イテレータ、クロージャ、無名関数・ラムダ式などは、関数型プログラミング言語における特有の言語機能であり、上手く使うことでC/C++を使うよりもエレガントにプログラムを記述できる。
2023.01.19編集
また、純粋関数型言語では、式や関数が評価される時に、副作用が生まれない。
副作用とは、参照している変数の状態を破壊するという意味。
また、「同じ引数と同じ式を与えれば必ず同じ結果が返る」という性質を「参照透過性」という。
C/C++のような手続き型のプログラミング言語では、変数を代入したり変更したりすること、すなわち「副作用」が前提となってプログラミングパラダイムとなっている。
だが、Lispのような関数型言語では、副作用を嫌い、多くのことを関数呼び出しによる返り値で行う。
すなわち、値を「変更」するのではなく、新しく「新作成」するのである。
このようなプログラミングパラダイムは関数型プログラミングと呼ばれる。最初のうちは、「副作用なしでどうやったらプログラムが書けるの?」と思うかもしれない。だが、実際やってみると、副作用が必要となる場合は、特にLispのような関数型プログラミング言語においては「とてもレアケース」であることが分かる。
そう、わたしたちは関数型プログラミングを行うことで、副作用なしでもプログラムを書けるのである。
後日注記:確かに、変更できる変数は便利です。たとえば、インデックスのカウンタとして、1回目、2回目、3回目をカウントする時に、記憶を変更してインクリメントすることは、手続き型プログラミングでは慣習となっています。しかしながら、このような場合、関数をその回数の分だけ実行して、その引数として連続した値を与えることでも、反復処理は実現できます。副作用を用いずカプセル化をする、関数型プログラミングはこのようなところから始まります。
後日注記:そもそも、人間の頭脳として、記憶を「変更」する必要があるというのは稀だと思います。多くの場合、記憶を「新しく作り出す」ことのほうが多いでしょう。しかしながら、例外があります。それはIO処理です。入出力は、相手と自分の情報を「手紙という媒体によって渡す」ということであり、IO処理を副作用なしに書くことは難しいです。Haskellでは、IO処理について、圏論のアイデアを使った「モナド」という方法を使っています。
後日注記:変更可能な変数をミュータブル、変更不可能な変数をイミュータブルと言います。
詳しくは以下の書籍が参考になります。
関数型言語などで使われる関数の再帰的呼び出しの方法。
自分の中で自分を呼び出す関数のことを言う。「関数の中でその関数を呼び出す」ことを呼ぶ、と言うのが定義らしい。
Lispなどで計算する時に、良く使われる。
僕も昔は再帰処理をWikipediaのテンプレートなどで書くのが好きだった。
オブジェクト指向、ポインタ、再帰は、プログラムを書く時の三大重要概念だと僕は思っています(ビル・ジョイがポインタと再帰の重要性について言っていました)。出来れば使いこなせることが望ましいでしょう。
後日注記:再帰は、データ構造のスタックと相性がいい。たとえば、算術式のカッコを処理する時、最後に開いたカッコが最初に閉じるべきカッコとして対応する。このような時、オペコード・オペランドと式の操作は再帰的に処理される。
詳しくは以下の書籍が参考になります。
以下は自分で書いたコード。
再帰を使う場合:
int value[10]; int funcA(int x) { static int i = 0; value[i] = x; i++; if (i >= 10) { return 0; } else { funcA(x + 10); } } int main() { funcA(1); }
for文を使う場合:
int value[10]; int main() { int i = 0; int x = 1; for (i = 0; i >= 10; i++) { value[i] = x; x = x + 10; } }
「値を変えて同じルーチンを繰り返す」という意味では、for文のブロックと再帰関数は変わらない。
funcA()関数では、関数を繰り返し実行してもローカル変数iを静的に保持するために、static宣言をつけている。
繰り返し処理を行うには、これら「for文による反復」と「再帰」以外に、イテレータ(反復子)を使う方法がある。大規模なプログラミングでは、イテレータを使ってプログラムを記述することもある。
for (Iterator i = lst1.iterator(); i.hasNext();) { System.out.println(i.next()); }
Java(コレクションと配列)も参照のこと。
Lispでプログラムを書く時に記述する文章の形式をS式と言います。特に、(lisp (lisp lisp (lisp lisp)))のように、「カッコの中にいくらでもカッコが出来る」のが特徴です。
Lispを参照のこと。
イテレータとジェネレータを参照のこと。
クロージャ・無名関数・関数オブジェクトを参照のこと。
for文を使うのではなく、関数の内部から「自身の関数を呼び出す」ことで、繰り返しを「引数とreturn」で表現する。