Pythonで関数型風プログラミング

この記事は広告を含みます。

関数型プログラミング」とは、プログラムを“関数”という部品を使って組み立てる考え方のようで、Rustの台頭で近年よく見かけるので、ちょっと調べながら、メモがてら記載していきます。

ふだんの Python プログラムでは、変数を作って値を書き換えたりしますが、関数型プログラミングの世界では

  • できるだけ値を書き換えない(イミュータブル)
  • 同じ入力ならいつでも同じ出力が返る関数(純粋関数)を使う
  • 関数を「引数」として渡したり、関数から関数を返したりする(高階関数

といったルールを大事にします。(もちろん、これだけじゃないよ)

例えば、、、 学校の数学を思い出すと。関数 f(x) は、ある数 𝑥 を入れたら決まった結果が返ってきます。たとえば、

f(x)=x×2 なら、𝑥 を 3 にすれば常に 6 が返ってくるし、8 を入れれば 16 が返ってくる。 何度やっても同じ答えですね。 これが「同じ入力なら同じ出力が返ってくる」という考え方のようです。

なので、Python でこの考え方をちょっと試してみてゆきます。

関数型風な書き方

map と filter を使う

map と filter は、「関数を引数に渡す」Python の機能の代表例のようです。

numbers = [1, 2, 3, 4, 5]

# 1. map: すべての数を2倍にする
double_numbers = list(map(lambda x: x * 2, numbers))

# 2. filter: 偶数だけにしぼりこむ
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))

print(double_numbers) # => [2, 4, 6, 8, 10]
print(even_numbers) # => [2, 4]

どちらの操作も、もとの numbers リストを書き換えずに、新しいリストを返しているところがポイントのようです。

これが「できるだけ値を書き換えずに、新しいものを作る」という関数型っぽい考え方のようです。

C++を頑張ってた時、値が変わると、入れていたアドレスが変わって、nullアクセスとかあったような。。。

同じ入力なら同じ結果を返す「純粋関数」

たとえば、次のような add_one 関数を考えます。

def add_one(x):
    return x + 1

print(add_one(3))  # => 4
print(add_one(3))  # => 4 (何度呼んでも必ず 3 に対して 4 を返す)

この関数は、同じ 3 を入れるといつでも 4 を返します。内部でほかの変数を変更するようなこともなく、単に「x + 1 した結果」を返しているだけです。 こうした関数が多いほど、「関数型プログラミング」に近い書き方になるようです。

一方、もし関数の中で外側の変数を書き換えたりしていると、 同じ入力でも結果が変わる場合があり、関数型の考え方から少し遠ざかってしまうようです。

まとめ

  • 関数型プログラミングとは、「値をあまり書き換えず、同じ入力なら同じ結果を返す関数」をたくさん使ってプログラムを組み立てる方法。
  • Python でも map や filter、lambda といった機能を使うと、関数型っぽい書き方ができる。
  • 純粋関数を意識することで、バグを減らしたり、読みやすいコードを書いたりしやすくなる。

これだけでも、ふだんの Python とは少し違った考え方が味わえるので、慣れてからRustに挑戦するのも悪くない??

次回は、高階関数(Higher-Order Functions): 関数を引数として渡したり、関数から関数を返したりすることで、コードをより柔軟に組み立てる方法を学ぼうと思います。