HEXA BLOG

プログラム

HEXA BLOGプログラム2016.6.13

関数型プログラミングをpythonで始めてみよう ! Func.5

お久しぶりです、コウスケです。

今回は久々に関数型プログラミングの連載を再開したいと思います。

これまでの連載はこちらです。

 

関数型プログラミングを始めてみよう ! Func.1

関数型プログラミングを始めてみよう ! Func.2

関数型プログラミングを始めてみよう ! Func.3

関数型プログラミングを始めてみよう ! Func.4

 


 

さて、関数型プログラミングは、今までの連載でいろいろ書きましたがものすごくざっくり一言で言うと・・

「関数を部品として扱えたらいろいろと便利だよ!」

という概念でした。

 

今回はその中でも、最も基本的かつ重要といえるかもしれない部品を紹介します。
関数型プログラミングでは、

データの集合(=リスト)に対して、手続き(=関数)を行う

ことを基本とするものでした。

(参考:関数型プログラミングをpythonで始めてみよう ! Func.2)

 

このとき、データ集合のそれぞれの要素を走査する必要があるわけですが、

これをどんなデータ集合に対しても同じインターフェースで行いたいものです。

それを実現するものが「イテレータ」です。

(C++のSTL, JavaのCollectionライブラリなどで、イテレータオブジェクトを

返すデータ集合型を使ったことはあると思いますが、それです。)

イテレータは
任意のデータ集合型に対して、自分の保持するデータを一つずつ返す機能を持つオブジェクト
です。

pythonにもデータ集合として
・リスト

list = [1,2,3]

・辞書

dict = {"a":1, "b":2, "c":3}

・文字列

str = "abcde"

など様々な表現があります(pythonではシーケンス型と呼びます)が、

下記のように、すべてイテレータを作ることができます。

it = iter(list) # 任意のシーケンス型をiterに渡すことでイテレータ作成<br />it.next()  # 1を返す<br />it.next()  # 2を返す<br />it.next()  # 3を返す<br />it.next()  # StopIteration例外を返す

これらはすべてfor文で走査することができますが、その際実は、これらのデータ集合型オブジェクトは内部的にイテレータを返しています。

ですので、下記の記述は同じことになります。

L = [1 2 3]<br />for i in L:<br />    print i<br /><br />for i in iter(L): # L.__iter__()でも可<br />    print i<br /><br /># どちらも出力として1,2,3を返す

さて、イテレータはリストなどのデータ集合の要素を走査して値を返すわけですが、

その走査対象であるリストは巨大なデータであることがあります。

その場合、予めリストの内容をメモリに展開して返すよりも、

必要とされる都度リスト要素を生成して返すほうが実行速度やメモリ効率の点で

有利ですので、そういった記述ができれば有用です。

この性質を「遅延評価」と呼び、関数型プログラミングにおける重要な性質の一つです。

そういったイテレータの作成を実現するのが「ジェネレータ」で、イテレータを返す特殊

な関数です。

pythonではreturnの代わりにyieldキーワードを使うと

その関数はジェネレータ関数になります。

その場合、通常、関数は抜けるときにローカル変数を破棄しますが、

それをせずに次に呼ばれた時に抜けたところから関数を続行することができます。

例えば下記のジェネレータは整数Nまでの数列を発生させることができます。

def generate_ints(N):<br />  for i in range(N):<br />    yield i  # この時点で関数を抜け、次はそこから再開できる<br /><br />gen = generate_ints(5)<br />for i in gen:<br />  print i

実用的な例として、巨大なファイルを読むときを考えます。

ファイルオブジェクトに対し、下記のように予めファイル内容を読んでおくメソッドreadlinesを使うと、
すべてメモリに展開してからの処理になってしまいます。

f = file("big_text.txt")<br />for l in f.readlines():<br />  print l

それに対し、ファイルオブジェクトのイテレータを用いる(for文内で走査するとそうなります)

ことで、ファイルの中身を一行ずつ読み込むイテレータを返され、一行ずつの処理になります。

f = file("big_text.txt")<br />for l in f:<br />  print l

このジェネレータの性質を用いると、たとえば以下のコードはツリー構造データt

インオーダー探索するジェネレータになります。

def inorder(t):<br />    if t:<br />        for x in inorder(t.left):<br />            yield x<br /><br />        yield t.label<br /><br />        for x in inorder(t.right):<br />            yield x

ちなみに、このように一旦処理を中断したあと続きから処理を再開できる機構を

コルーチン」と呼び、一連の処理を状態管理を意識せず行えるため、いくつかの分野で

用です。

 


 

さて、ここまでの連載で関数型プログラミングのさまざまな性質、機能を見てきました。

まだ掘っていけばいろいろとネタは有るんですが、

表立ったところは一通り消化してきたので、今回で一旦最終回としたいと思います。

で、結局関数型プログラミングってなんだったの?というところですが、正直コレは難しいです。

関数型プログラミングはもともとは数学的な理論に基づいて生まれたもので、

Haskell, Lispなどより純粋な関数型プログラミングを行うための言語も存在し、

「バグの生まれにくさ、モジュール性(再利用性)、テスト容易性、結合性」

という点が優れていると主張されています。

 

その背景から説明することは私にはできませんし、

プログラミングを行う上でこのやり方が絶対に優れている、というわけではありません。

ですが、関数型プログラミングから生まれた、一つ一つの性質や機能をかいつまんで説明することはできます。

 

例えばですがGUI はオブジェクト指向で、でも処理ロジックは手続き型や関数型でというように、それぞれ適した分野もあります。

その考え方を、普段のC/C++Javaなど手続き型言語でのプログラミングに

取り入れることはできますし、pythonjavascriptC#などの比較的新しい言語や、

普段の開発環境でのライブラリやフレームワークを見たときに、

関数型プログラミングの影響を受けていると思われるものもあり、

そういった時になぜこういったAPIになっているかなどの考え方や背景などの理解が容易

になったりします。

 

そうすると、また広い大きな視点でプログラミングを行うことができますし、

プログラマとしても成長できるのではないでしょうか。

みなさんが関数型プログラミングに触れるきっかけになれば幸いです。それでは。

 

[参考文献]

関数型プログラミング HOWTO

関数型言語のウソとホント

 

 

RECRUIT

大阪・東京共にスタッフを募集しています。
特にキャリア採用のプログラマー・アーティストに興味がある方は下のボタンをクリックしてください

RECRUIT SITE 

NEWS