2008年12月15日月曜日

Python における関数型プログラミング のための functools, itertools (1) – Haskell と同じ名前の関数たち

1. 関数型プログラミングのための functools, itertools モジュール

Python 2.7

Python の標準ライブラリには、関数型プログラミングにとって重要なモジュールがある。

itertools, functools は、

の階層の中にある。

9. Numeric and Mathematical Modules には、これまで何度か見た decimal モジュールが含まれる。

Python には、限定的ではあるけれど、関数型プログラミングをサポートしており、Haskell などから借りてきた 2 つのモジュールがある。

Python - Wikipedia によると、

The design of Python offers limited support for functional programming in the Lisp tradition. However, there are significant parallels between the philosophy of Python and that of minimalist Lisp-family languages such as Scheme. The library has two modules (itertools and functools) that implement proven functional tools borrowed from Haskell and Standard ML.[64]

(太字は引用者による)

functools, itertools の関数については、これまでに 2 つ試した。

 

Python 3.2

追記(2012/05/22): Python 3.2 では、9. Functional Programming Modules 8. Numeric and Mathematical Modules から独立している。

 

2. functools, itertools モジュールの関数名は、Haskell の関数名に似ている

itertools のドキュメントにざっと目を通すと、関数の名前に親近感を感じる。(@_@) Haskell に同じ名前の関数がある。

Haskell の分類に倣い、関数を分類すると、

上記は、全て itertools の関数なので、

イテレータ

が絡んでくる。

 

3. 無限リストを扱う

cycle

cycle 関数は、コンテナオブジェクトから要素を取り出す。

引数は `iterable’ .

itertools.cycle(iterable)

引数の iterable は、反復可能オブジェクトのこと。

E. 用語集 によると、

反復可能オブジェクト (iterable)
コンテナオブジェクトで、コンテナ内のメンバを一つづつ返せるようになっているものです。

簡単に言うと、子どもである要素を管理する、親みたいなオブジェクト。

反復可能オブジェクトの例には、 (liststr、および tuple といった) 全ての配列型や、dictfile といった非配列型、あるいは __iter__()__getitem__() メソッドを実装したクラスのインスタンスが含まれます。…
イテレータ (iterator)配列 (sequence)、および ジェネレータ (generator) も参照してください。(同上より)

上記に関しては、以下を参照。

cycle 関数は、引数に渡された、反復可能なオブジェクトを、無限に繰り返すためのイテレータを返す。例えば、range 関数で数値のリストを作り、そこから無限に要素を取り出す。

from itertools import *

it = cycle(range(4))
for i in range(10):
    print it.next(),     # 0 1 2 3 0 1 2 3 0 1

Python では、無限リストを扱うために、イテレータで要素を1つずつ取り出すことにより実現している。

Haskell の cycle の書き方は、以下の通り。

*Main> take 10 $ cycle [0..3]
[0,1,2,3,0,1,2,3,0,1]

以降、Python のコードは、上記と同一モジュールに書くので、import は省略する。

 

repeat

repeat 関数の引数には、オブジェクトを渡す。 iterable である必要はない。

cycle と違いは、オプションとして、繰り返す回数を指定することができる。

for x in repeat('A', 10):
    print x,            #  A A A A A A A A A A 

リスト内包表記で書くと、

print [x for x in repeat('A',10)]

Haskell における repeat は、次のように書く。

*Main> take 10 $ repeat 'A'
"AAAAAAAAAA"

 

4. 部分リストを取得する

takewhiledropwhile は、述語が真の間、それぞれ要素を take または drop する。引数は iterable .

for x in takewhile(lambda x: x < 5, range(10)):
    print x,             # 0 1 2 3 4

for x in dropwhile(lambda x: x < 5, range(10)):
    print x,             # 5 6 7 8 9

Haskell では、takeWhile の `w’ は大文字なのに対して、Python は小文字であることに注意。

先ほどと同じくリスト内包表記を使うと、

print [x for x in takewhile(lambda x: x < 5, range(10))]
print [x for x in dropwhile(lambda x: x < 5, range(10))]

この関数の説明には predicate (述語)という言葉が使われていた。

Make an iterator that returns elements from the iterable as long as the predicate is true.

(takewhile の説明より)

述語に関しては、以下を参照。

 

Haskell の場合

Haskell の takeWhiledropwhile は、以下の通り。

*Main> takeWhile (< 5) [0..10]
[0,1,2,3,4]

*Main> dropWhile (< 5) [0..10]
[5,6,7,8,9,10]

 

5. 要素のグループ化

groupby

groupby 関数は、反復可能なオブジェクトを渡すと、要素をグループ化してくれる。

groupby の `b’ は小文字であることに注意。

for k,g in groupby([1,2,2,3,3,3,4,5,5]):
    print k, list(g)

結果は、グループ化したときのキーと、その要素がリストとして返される。

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

オプションとして関数を渡すと、返されるキーを変更することができる。

for k,g in groupby([1,2,2,3,3,3,4,5,5], lambda x: str(x)+":hoge"):
    print k

結果は、

1:hoge
2:hoge
3:hoge
4:hoge
5:hoge

 

Haskell の group と groupBy

Haskell では、 Data.ListgroupBy がある。

groupBy :: (a -> a -> Bool) -> [a] -> [[a]]

group 関数は、groupBy の特殊な関数。

The groupBy function is the non-overloaded version of group.

group 関数の型を確認しておく。

group :: Eq a => [a] -> [[a]]

型を見ると、関数の引数の制約は、EQ クラスのインスタンスであること。 group 関数は、同値検査によって要素がグループ化される。

Prelude Data.List> group [1,2,2,3,3,3,4,5,5]
[[1],[2,2],[3,3,3],[4],[5,5]]

これに対して、groupBy 関数は、より汎用的になっている。要素をグループ化するときの基準となる関数を第 1 引数に与える。

Prelude Data.List> groupBy (\x y -> x == y) [1,2,2,3,3,3,4,5,5]
[[1],[2,2],[3,3,3],[4],[5,5]]

または、次のように書ける。

Prelude Data.List> groupBy (==) [1,2,2,3,3,3,4,5,5]
[[1],[2,2],[3,3,3],[4],[5,5]]

グループ化する基準を関数で与えることができるので、次のように、複雑な基準でグループ化できる。

Prelude Data.List> groupBy (\x y -> x+1 == y) [1,2,2,3,3,3,4,5,5]
[[1,2,2],[3],[3],[3,4],[5],[5]]

 

6. その他の関数

functools, itertools の中には、上記以外に関数が存在する。名前を列挙すると、

  1. 全く動作の想像がつかないものから、
  2. map, filter, zip, slice のようなお馴染みの関数に接頭辞 `i’ が付いたものや、
  3. combinations, permutations, product のように具体的な計算の意味が分かるものまで様々。