関数型プログラミングとは

関数型: プログラミングは問題をいくつかの関数にわけて考える。

理想的に言うと、関数は入力を受けて出力を吐くだけで、同じ入力に対して異なる出力をするような内部状態を一切持たない。有名な関数型言語には ML系言語(Standard ML, OCaml など) 、Lisp系言語(Common Lisp,Scheme, Clojure など)、 Haskell などがある。

これをひと通り読めばわかるはず。関数型プログラミングHOWTO

以下、読むのが面倒くさいあなたに。

Pythonにおいて、この中でも特に重要なのが、

  • イテレータ
  • 高階関数とlambda式
  • リスト内包表記(map,filter,reduce)

の考え方。

イテレータ

連続データを表現するオブジェクト。普段はリスト等を変換して、イテレータとして扱うことが多い(for 文等)。関数型プログラミングを行う際には、データの集合をイメージするといいかも。

集合のすべての要素、もしくは特定の条件を満たす部分集合に対して、何らかの関数を適用し、新たな集合を作ることをイメージしながらデータを操作していく。

高階関数とlambda式

高階関数: 関数(手続き)を引数にしたり、戻り値にできる関数。以下の様な例を考えてみる。 まず関数を4つ定義する。

1
2
3
4
5
6
7
8
9
10
11
def calc(x,y,method):
    return method(x,y)
    
def add(x,y):
    return x + y
    
def diff(x,y):
    return x - y
    
def pow(x,y):
    return x ** y

calc()に引数として関数を渡してみる。

1
2
3
4
5
6
>>> calc(2,5,add)
7
>>> calc(2,5,diff)
-3
>>> calc(2,5,pow)
32

このようにPythonでは関数を引数として扱える。当然返り値にもできる。ほとんど同じ処理だが、微妙に違う関数を適用したい箇所がある場合、関数中でif文等を利用して条件分岐させるよりも、関数自体を引数として渡すほうが、スマートになる場合が多い。

小さい関数を作成する場合、lambda式も利用できる。例えば、x を2乗して返す関数squareの場合、 次の2通りで書ける。

1
2
def square (x):
    return x * x

もしくは

1
square = lambda x: x * x

どちらで書くかは好みの問題。

map, filter, reduce

  • map ** リストの各要素に関数を適用した結果のリストを返す。リストの全要素を各々2乗したリストを作る場合。
1
2
3
>>> l = [1,2,3,4,5,6,7,8,9]
>>> print map(lambda x: x * x, l)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
  • filter ** リストの部分集合からなるリストを返す。例えば、偶数からなるリストを作る場合。
1
2
3
>>> l = [1,2,3,4,5,6,7,8,9]
>>> print filter(lambda x: x % 2 == 0, l)
[2, 4, 6, 8]
  • reduce ** リスト全体に対する計算。リストの全要素の総和を求める場合。
1
2
3
>>> l = [1,2,3,4,5,6,7,8,9]
>>> print reduce(lambda a,b: a + b, l)
45

イメージとしては(1 + 2) + 3) + 4) + 5) ... + 9)と考えればよい。

リストの全要素の総乗を求める場合。

1
2
3
>>> l = [1,2,3,4,5,6,7,8,9]
>>> print reduce(lambda a,b: a * b, l)
362880

リスト内包表記

かなり便利なので使い方を覚えておくと良い。mapやfilterを使うより、こっちで書くほうがPythonぽい書き方。

例えば、数値からなるリスト l から unicode 型に変換したリストを作る場合を考える。普通に書くとこうなる。

1
2
3
4
5
6
7
l = [1,2,3,4,5,6,7,8,9]
    
def num_to_unicode(iter):
    r = []
    for x in iter:
        r.append(unicode(x))
    return r

これをリスト内包表記で書くとこうなる。

1
2
l = [1,2,3,4,5,6,7,8,9]
u_list = [unicode(x) for x in l]

また、偶数のみを取り出したリストを返す場合を考える。

1
even_list = [x for x in l if x % 2 == 0]

これでOK。unicode() や x % 2 == 0 の部分をもっと複雑な関数にすると、 かなりのことができるはず。

問題

(1) 文字列からなるリストの各要素末尾に改行コードを付加する関数を作成せよ。

(2) 整数からなるリストを受け取り、各要素を「3の倍数ならfizz、5の倍数ならbuzz、15の倍数ならfizzbuzz、その他の場合はそのまま」に書き換えたリストを返す関数を作成せよ。

(3) [[1,2,3],[4],[5,6],[7,8,9]]を[1,2,3,4,5,6,7,8,9] のように畳み込む関数を作成せよ。