関数型プログラミング

関数型プログラミング

Python は オブジェクト指向言語 でありながら、関数型プログラミング としての機能も備えています。命令を処理するために複数の手法(パラダイム)を備える言語のことをマルチパラダイム言語とよぶことがありますが、Python もそうしたマルチパラダイム言語のひとつです。

オブジェクト指向において、オブジェクトは内部データとそれを変更するためのメソッドが一体となったカプセルであり、プログラムはメソッドの使用によって内部データを変化させていく手続きです。一方で関数型プログラミングにおいては、できる限り状態の変更を避けるようにして、プログラムは関数同士で受け渡されるデータの流れ(複数の関数の組み合わせ)として表現されます。

Python においては、イテレータ、ジェネレータ、高階関数、デコレータなどの機能を使って関数型スタイルのコードを記述します。こうしたツールを使いこなせるようになると、驚くほど短く簡潔なコードを書くことができるようになります。

とはいえ、先にも述べたように、Python は純関数型言語ではないので、完全に首尾一貫した関数型プログラミングのコードを記述は困難ですし、またその方向を目指すことは現実的ではないでしょう。普通は他の方法と組合わせたほうがシンプルで分かりやすいコードになるはずです。だから敢えて関数型プログラミングではなく、関数型スタイル と表現しています。関数型プログラミングの概念は非常に奥深いものですが、Python を扱う限りにおいては「関数型プログラミングとは何ぞや?」ということを突き詰める必要はありません。まずは上のメニューに並んでいるリンク先の記事を読み、サンプルコードの動作を確認しながら、関数型プログラミングの雰囲気を掴んでみてください。状況に応じて「この部分は関数型スタイルで書いたほうが効率的だ」のように判断できるようになれば、それで十分だし実践的だと思います。

 

関数オブジェクト

Python で関数を使用するときは、関数名(引数) と記述します。コードを実行すると、その関数の機能に応じて様々なクラスのオブジェクトが返ります。たとえば、オブジェクトの長さを返す組み込み関数 len() に “Python” という文字列を渡すと「6」という intクラスのオブジェクトが返ります。

# FUNC_01

# In[1]

x = len("Python")

print(x)
print(type(x))
# 6
# <class 'int'>

今度は () を添えずに len という関数名だけを変数 x に渡して、print(x) で表示させてみます。

# In[2]

x = len

print(x)
print(type(x))
# <built-in function len>
# <class 'builtin_function_or_method'>

変数 x は関数オブジェクト (builtin_function_or_method クラスのインスタンス)を参照していることがわかります。x() にオブジェクトを渡すと、そのオブジェクトは参照先の len() に渡され、オブジェクトの長さを返します。

# In[3]

# xに関数lenを格納
x = len

# 関数xに文字列を渡す
y = x("Python")

print(y)
# 6

Python ではすべてがオブジェクトです。もちろん関数もオブジェクトです。
関数をオブジェクトであることを明示するために、関数のことを敢えて 関数オブジェクト とよぶことがあります。関数型プログラミングでは関数オブジェクトの受け渡しがコードの核となります。

Python のリストはほとんどあらゆるオブジェクトを格納できます。もちろん、関数オブジェクトを要素にもつこともできます。それほど頻繁に使うわけではありませんが、複数の関数をリストにまとめておくと便利なこともあります。まずは簡単な例を見てみましょう。

# FUNC_02

# In[1]

import math

# 関数リスト
func_list = [math.sqrt, math.exp, math.cos]

# √10, exp(10), cos(10)を計算
for f in func_list:
    print(f(10.0))

# 3.1622776601683795
# 22026.465794806718
# -0.8390715290764524

このコードのループ処理では最初にループ変数 f に関数オブジェクト math.sqrt を入れてます。そして関数 f に 10.0 を渡して平方根を表示します。次に f に math.exp を入れて、10.0 を渡してexp(10.0) を表示します。最後に f に math.cos を入れて、10.0 を渡して cos(10.0) を表示させています。
 
次のサンプルは、あるオブジェクトを float() に渡し、その戻り値を int() に渡し、その戻り値を str() に渡すというコードです。

# In[2]

# 関数のリスト
func_list = [float, int, str]

x = "10.5"

for k in range(3):
    x = func_list[k](x)

print(x)
# 10

最終的に「10」という文字列 (strクラスのオブジェクト) が返ります。ループ処理のブロックに記述されている

x = func_list[k](x)

という部分では、func_list のインデックス k の要素に x を渡し、その戻り値を x に入れるという再帰的な処理が行われています。

ChatGPTに関数型プログラミングについて訊いてみました

[Q] 関数型プログラミングについて、プログラミング初心者にもわかるように説明してください。

[ChatGPT] 関数型プログラミングは、プログラムを関数の組み合わせとして記述するプログラミングのスタイルです。関数型プログラミングは、手続き型プログラミングやオブジェクト指向プログラミングといった他のスタイルとは異なる特徴を持っています。関数型プログラミングでは、プログラムを「副作用のない関数」の組み合わせとして記述します。副作用とは、プログラムの実行中にプログラム外の状態を変更することを指します。例えば、変数の値を変更したり、ファイルを書き換えたりすることが副作用に当たります。関数型プログラミングでは、副作用を避けることで、プログラムの正確性や再利用性を高めることを目指します。また、関数型プログラミングでは、変数やループなどの制御構造を極力排除し、代わりに高階関数や無名関数などを用いて処理を記述します。これにより、コードの可読性が向上すると同時に、コードの短縮化や再利用性の向上も図ることができます。関数型プログラミングは、数学的な考え方に基づいており、プログラムが理論的に正しいことを保証することができます。また、関数型プログラミングは、並列処理に適したプログラムを記述することができるため、近年のコンピューターの性能向上に合わせて、注目されるようになっています。ただし、関数型プログラミングは、手続き型プログラミングやオブジェクト指向プログラミングとは異なる考え方を必要とするため、最初は難しいと感じるかもしれません。しかし、関数型プログラミングを理解することで、より効率的で保守性の高いプログラムを記述することができるようになるため、学習する価値は十分にあると言えます。