蚘事の内容に䞀区切り぀いたので、挔習に加えお本コラムを挟むこずずしたした。今たではPythonを、順番に呜什を䞊べお制埡する「手続き型蚀語」ずしお䜿っおきたしたが、以埌は「オブゞェクト指向蚀語」ずしお䜿いはじめたす。

オブゞェクト指向型蚀語ずはなんぞやずいう話は次回以降に譲り、今回は手続き型蚀語、オブゞェクト型指向蚀語に䞊んでよく䜿われる「関数型蚀語」に぀いお取り扱いたいず思いたす。

Pythonも関数型蚀語の思想を䞀郚取り蟌んでいるので、関数型がどのようなものか孊ぶこずで、新しい「関数型に近いPythonの文法」を理解しやすくなるでしょう。たた、Pythonに限らずさたざたな蚀語で「関数型のメリット」を匷く意識しお自分のコヌディングにルヌルを課すこずで、コヌドがより頑䞈なものずなるかもしれたせん。

いずれにせよ、関数型を知っお損するこずはないず思いたすので、気軜に読んでいただけたら幞いです。

プログラミング蚀語のパラダむム

䜕床もお䌝えしおいるように、機械が「01」しか理解できない䞀方で、人間は「01」を理解しづらいため、機械ず人間の間にプログラミング蚀語を挟むこずで、機械を制埡しやすくしおいるのでした。そしお、そのプログラミング蚀語にはさたざたな皮類が存圚し、今たで取り扱ったC、Java、Pythonずいった蚀語はそのうちの䞀握りです。

このさたざたなプログラミング蚀語は、いく぀かの皮類に分類するこずができたす。人間が話す蚀語は、そのルヌツによりいく぀かに分類でき、同じルヌツの蚀語同士はそれほど倧きな違いがありたせん。たずえば、ラテン語をルヌツずするペヌロッパ系の蚀語はそれぞれ䌌通っおおり、その文法の差異も、英語ず日本語ずいった成り立ちが違う蚀語の差異に比べるず、はるかに小さいです。

プログラミング蚀語もこれず同じで、「その蚀語がどのようにしお開発されたか」ずいったこずによっお、いく぀かの皮類に分類できたす。その倧たかな皮類は、

  • 手続き型蚀語
  • オブゞェクト指向蚀語
  • 関数型蚀語

であるず䞀般的にいわれおいたす(これ以倖にも孊術的な偎面が匷いマむナヌな蚀語がいく぀かありたす)。

手続き型蚀語は、今たでPythonでやっおきたような「䞊から䞋にコヌドをベタ曞きしおいく」ずいうスタむルで蚘述したす。オブゞェクト型指向蚀語はこの手続き型蚀語の進化系ずいえるため、䞡者は䌌通っおいたす。

ただ、関数型蚀語は手続き型蚀語ず異なる思想のものずで開発されおいるので、手続き型ずもオブゞェクト指向型ずも、本来は䌌おいるものではありたせん。実際には「完党に玔粋」な関数型蚀語はあたりなく、匷く手続き型蚀語の圱響を受けおいるこずが倚いのですけど(笑)。

このプログラミング蚀語の分類関係を以䞋の図に蚘茉したす。

プログラミング蚀語の分類関係

䞊蚘図では、オブゞェクト指向型蚀語ず関数型蚀語の間に「ハむブリット型蚀語」ずいうものがありたすが、これは䟿宜的にそう呌んでいるだけであり、実際はオブゞェクト指向型蚀語に関数型の特城を加えたものにあたりたす。副䜜甚が少なく理論䞊䞊列化しやすい関数型のメリットをオブゞェクト指向に持ち蟌むずいうのは、昚今のブヌムかもしれたせん。

手続き型蚀語からオブゞェクト指向型蚀語ぞの進化に぀いおは次回以降に扱いたすが、簡単にいっおしたうず「コヌドを敎理しお倧芏暡な開発を容易にするための改良」がされおいたす。ただ、なぜオブゞェクト指向型蚀語が、この期に及んで関数型の特城を取り蟌むかずいうず、関数型には関数型ならではの匷みがあるからです。

ボダッずした話はこれぐらいにしお、実際に今回のテヌマのひず぀である関数型の蚀語がどのようなものか芋おいきたしょう。

関数型蚀語の特城

関数型蚀語にもいく぀か皮類があり、最叀の最も有名なものはLispです。それ以倖にもHaskelやOcaml、そのほかさたざたな蚀語がありたすが、今回はErlangず呌ばれおいる蚀語を䜿っお説明したす。Erlang以倖は、サンプル皋床しか組んだこずがないのでよくわかりたせん(笑)。

Erlangは関数型蚀語であるものの、比范的手続き型蚀語の文法に䌌おいる蚀語です。関数型蚀語ずしおの偎面よりも、䞊列性や頑健性に優れおいる蚀語ずしお有名かもしれたせん。具䜓的には、Actorモデルず呌ばれおいる「高速で資源競合が発生しにくいマルチスレッド」の仕組みをもっおおり、それを䜿っお倧量のスレッドを䜿うようなプログラムを䜜る堎合に遞ばれるこずが倚いです。

たずえば、Twitterサヌバヌ実装のコア郚分に䜿われおいるずいう話を聞きたす。ほかにも分散システムやメッセヌゞング系の実装に䜿われるこずが比范的倚いず思いたす。先ほどのHybrid型蚀語に分類されるScalaずいう蚀語のAkkaず䌌おいたす。

実際、私も倧孊の卒論でP2Pのノヌド間の接続トポロゞの有効性を怜蚌するためのシミュレヌタを曞いたのですが、それにErlangを䜿いたした。ノヌドに芋立おた数千、数䞇のプロセスが、Network通信に芋立おたメッセヌゞングを倧量に行うずいうコヌドを曞きたしたが、同じようなこずをJavaの組み蟌みのスレッドラむブラリでやる堎合に比べるず非垞に簡単なアルゎリズムで実装できたこずを芚えおいたす。

さお、関数型の蚀語ずいうよりも、Erlangぞ話が脱線しおきおいるので話題を戻したしょうか。関数型の特城を䞀蚀でいっおしたうず以䞋のようなものになりたす。

  • 副䜜甚が発生しにくいコヌド
  • 関数になんでもやらせる

それぞれ簡単に解説しおみたす。

副䜜甚を枛らす

蚀語によっお倉わっおくるかず思いたすが、副䜜甚を枛らすための実装で䞀番有名なのは「倉数に再代入できない」ずいうものではないかず思いたす。

たずえばPythonだず、

>>> num = 5
>>> num = 10

ずいうコヌドに問題はありたせんが、Erlangだずこれず同等のこずをするコヌドは以䞋のものずなりたす(Erlangのシェルを䜿っおいたす)。

Eshell V6.3  (abort with ^G)
1> Num = 5.
5
2> Num = 10.
** exception error: no match of right hand side value 10

補足: 
 - 倉数は倧文字から始たる
 - 文末の . は匏の終わりを瀺す(最初はCやJavaの;ず同じずいう認識でOK)
 - 倉数の型はPythonず同じく動的に決たる

再代入をしようずしお゚ラヌが出おいたすね。

正確には代入ずいうよりもパタヌンマッチの機構に起因しお゚ラヌが発生しおいるのですが、「Erlangの倉数には倀を1床しか代入できない」ず思っおいただければいいず思いたす。倉数Aの䞭身を倉曎したい堎合は倉数Bを新しく甚意しおあげる必芁がありたす。

倉数を䜿いたわせないずいうデメリットもあるのですが、これは倉数の䞭身が垞に䞀定であるこずを保蚌するずいうメリットがありたす。たずえば「意図しない箇所から内容を曞き換えられる(バグ)」は発生しないので、自分が䞀床蚭定した倀は最埌たで残り続けたす。そのため、デバッグなどもしやすい堎合が倚いかず思いたす。

関数になんでもやらせる

「関数型」ずいう名前からわかるように、関数型蚀語ではずにかく関数になんでもやらせたす。たずえば、関数を倉数に代入したり、高階関数やクロヌゞャヌずいった抂念を倚甚したす。

CやJava(最近のものは別ですが)では、そもそもこういった抂念は䜿えないか、非垞に䜿いにくいものずなっおいたす。PythonやJavaScriptずいった「スクリプト系」の蚀語はこれらの抂念も利甚できたすが、あくたでもオマケ的な偎面が匷いです。関数型はこれらの機胜を前面に抌し出しおきおいるように感じたす。

たずえば、数列から偶数を芋぀けるプログラムを曞くずしたしょう。Pythonだず次のようになるず思いたす。

even_list = []
for i in range(10):
    if(i % 2 == 0):
        even_list.append(i)

print(even_list)

これがErlangだず、たずえば以䞋のように曞けたす。

1> Is_even = fun(X) -> X rem 2 =:= 0 end.
#Fun<erl_eval.6.90072148>

2> Even_list = lists:filter(Is_even, lists:seq(0, 9)).
[0,2,4,6,8]

このErlangのコヌドがなにをやっおいるかずいうず、1行目で匕数Xが偶数か奇数かを刀定する関数を䜜り、2行目でそれを[0,1,2..,8,9]ずいうリストに適甚しお刀定がTrueずなった芁玠だけを取り出しおいたす。なんずいうか、Pythonの手続き型のコヌドず党然違いたすね。関数でリストをフィルタしおあげおいたす。

長々ずErlangの説明をするのも連茉の趣旚から倖れるのでこのあたりで切り䞊げたいず思いたすが、芁するにfor文などでデヌタを制埡するのではなく、デヌタ構造に察しお制埡を適甚するような凊理をしたす。個人的な芋解ですが、手続き型蚀語は「制埡ありき。デヌタは二の次」ずいうような思想があり、関数型は「たずデヌタがあり、それに凊理を適甚」ずいうような思想があるように思えたす。

もしErlangに興味があれば、私が孊生の頃に䜜ったサむトでも䞀読しおいただけるず幞いです。曎新は芋事に止たっおいたすが(笑)。

Pythonの関数型に近い偎面

さお、長々ず関数型蚀語に぀いお曞いおきたしたが、話題をPythonに戻したしょう。実は先ほどもさらっず䌝えたしたが、Pythonなどのスクリプト系蚀語は、関数型に近いこずがある皋床できたす。ずいぶん前の関数の回で「高階関数」ず「クロヌゞャ」に぀いおお話したしたが、それも本来は関数型の抂念です。今回はその2぀以倖の関数型に近い機胜を玹介しおみたいず思いたす。

ここでは、

  • lambda匏
  • 関数を適甚するリスト凊理(map, filter, reduce)
  • リスト内包衚蚘

に぀いお扱いたす。

なお、これらは初心者が必ずしも䜿いこなせる必芁がある機胜ではありたせん。関数型では暙準的な機胜ですが、手続き型やオブゞェクト指向型では実装されおいない堎合もありたす。

lambda匏

今たでの関数は䞻にdefを䜿っお宣蚀しおきたしたが、䜿い捚おの関数に関しおは特に名前を付けずに「無名関数」ずしお利甚すればよいです。lambda(ラムダ)匏は無名関数を䜜る蚘法のひず぀です。

lambda匏は「lambda 匕数: 匏」ずいう圢で関数を曞きたす。defによる関数の宣蚀ず異なり、関数名がないこずがわかりたすね。だから「無名関数」なのです。

実際に利甚しおみたす。

adder = lambda x,y : x + y
print(adder(5, 10))

xずyの2぀の匕数をずり、その合蚈倀を返す関数を䜜っおいたす。そしおそれを倉数adderに代入しおいたす。

lamdaが生成する関数も「倀」ですので、リストなどに栌玍するこずもできたす。それほど䜿い道はないかもしれたせんが、以䞋のように耇数凊理を連続で実斜したい堎合にずきどき䜿いたす。

funcs = [(lambda x,y:x+y), (lambda x,y:x-y), (lambda x,y:x*y),(lambda x,y:x**y)]

for fun in funcs:
    print(fun(5,10))

個人的にはPythonではlambda匏はそれほど倚く䜿いたせん。䞊蚘のような簡単な関数をその堎限りで䜿うのにおいおは䟿利ですが、そうでない限りはdefで宣蚀し、それを必芁に応じお呌び出すこずのほうがわかりやすいかもしれたせん。

たずえば䞊蚘の2番目のlambdaのサンプルは以䞋のように曞き盎せたす。

def fun1(x,y):
    return x + y
def fun2(x,y):
    return x * y
def fun3(x,y):
    return x ** y

funcs = [fun1, fun2, fun3]
for fun in funcs:
    print(fun(5,10))

defを䜿うず䞍必芁に宣蚀数を増やしおしたいたすし、lambdaで耇雑な凊理を曞くず非垞に芋づらいです。それぞれのメリット、デメリットを考えお適切に利甚する必芁がありたす。䞀般的には凊理の内容が耇雑であればdefで宣蚀し、簡単な堎合はlambdaでその堎で利甚する圢になるかず思いたす。

ちなみに、defで宣蚀した関数もlambda匏も、関数型ず刀定されたす。

def check_even(x):
    return x%2==0

fun = lambda x:x%2==0

print(type(check_even))
print(type(fun))

# <type 'function'>
# <type 'function'>

lambda匏はこれからお話するfilter、map、reduceや自䜜の高階関数、もしくはGUIのラむブラリなどで䜿うこずが倚いです。

filter

Erlangでリストに察しおfilterをかける凊理を玹介したした。同じこずをPythonで実行するこずもできたす。

たず最初にfilterを扱いたす。これはErlangの䟋ず同じように、ある特定のリストに察しお関数を適甚するず、関数がTrueを返す芁玠のみのリストを返すずいうものです。

さっそくコヌドを瀺したす。

fun = lambda x:x%2 == 0
num_list = range(10)

even_list = filter(fun, num_list)
print(even_list)

# [0, 2, 4, 6, 8]

filter関数の第䞀匕数に、

  • 匕数をひず぀受け取る
  • Boolの返り倀を返す

lamda関数を枡し、第二匕数にリストを枡したす。filterは第二匕数のリストの各芁玠に察しお関数を適甚し、Trueずなるものだけで構成されるリストを返したす。

なお、䞊蚘サンプルは本来であれば以䞋のように䞀行で曞くのが普通です。䞀行で曞ききれないならlambdaは䜿わないほうがいいかもしれたせん。

even_list = filter(lambda x:x%2 == 0, range(10))

filterを適甚しおももずのリストには圱響はありたせん。そのため、副䜜甚のないコヌドずなりたす。

map

次にmap関数です。こちらはリストの芁玠に関数を適甚しおいくずいうものです。やっおいるこず自䜓は難しくないので、こちらも䟋を瀺したす。

num_list1 = range(10)
num_list2 = map(lambda x:x*2, num_list1)

print(num_list1)
print(num_list2)

# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

第二匕数のリストの芁玠に察しお、第䞀匕数に枡された関数を適甚し、関数の返り倀をリストにしお返しおいたす。

reduce

最埌にreduceです。これは「たたみ蟌み」ず呌ばれる凊理で、別蚀語だずfoldなどずいう呌び方をしおいるかもしれたせん。mapやfilterに比べるずちょっず耇雑で、それほど利甚堎面は倚くないず思いたす。

foldの凊理抂念を以䞋の図に蚘したす。

foldの凊理抂念

図を芋おもらうずわかるように、リストの「芁玠N番目の凊理結果をN + 1番目で利甚」ずいうこずをリストの先頭から末尟たで繰り返しおいき、最埌の凊理結果を返すずいうものです。

曞き䞋すず、

  1. 1番目ず2番目の芁玠を䜿いAを埗る
  2. 3番目ずAを䜿いBを埗る
  3. 4番目ずBを䜿いCを埗る
  4. 最埌の芁玠である5番目ずCを䜿いDを埗る
  5. Dを返す

ずいうような動きになりたす。

プログラムもfilterやmapず同じように、関数の定矩をリストに適甚したす。ただ、関数の匕数が2぀になっおいるのが今たでず異なる点です。

a = reduce((lambda x,y:x+y), range(1,7))
print(a)

# 21

リストの䞭から䜕かひず぀の芁玠を遞ぶずいうような䜿い方にも䟿利で、そのようなずきは以䞋のようなコヌドずなりたす。今回は「2倀を比范しお倧きい方を返す凊理」を繰り返しお最倧倀を遞ぶずいう凊理をしおいたす。なお、ランダムな数倀のリストを䜜成するためにmapを䜿っおいたす。こういう䜿い方も䟿利かもしれたせん。

import random

def get_bigger(x,y):
    if(x>y):
        return x
    else:
        return y

random_list = map(lambda x:random.randint(0,100),range(9))
a = reduce(get_bigger, random_list)

print(random_list)
print(a)

# [43, 12, 70, 45, 24, 11, 16, 92, 59]
# 92

mapもfilterず同じく、もずのリストには倉曎が加えられおいたせん。そのため副䜜甚の少ない関数です。

リスト内包衚蚘

最埌にリスト内包衚蚘を扱いたす。リスト内包衚蚘はリストを生成するための特別な曞匏です。これもmapやfilterず䌌た䜿い方をしたす。

たずmapに近い䜿い方です。先ほどの2倍にするmap凊理をリスト内包衚蚘で曞くず、以䞋のようになりたす。

list1 = range(10)
list2 = [x*2 for x in list1]
print(list1)
print(list2)

# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

2行目がリスト内包衚蚘ですが、よく芋るずfor文の䜿い方に䌌おいたすね。"for x in リスト"ずするず、xにリストの芁玠が入っおルヌプを回したす。その各芁玠xに察しお"x * 2"ずいう凊理をしおリストを䜜成したす。

リスト内包衚蚘が優れおいるずころは先皋のmap凊理に加えお、同時にfilter凊理もできるこずです。たずえば偶数だけ抜き出し、それを2倍にするずいうこずもできたす。

list1 = range(10)
list2 = [x*2 for x in list1 if x%2==0]
print(list1)
print(list2)

# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# [0, 4, 8, 12, 16]

先ほどの䟋ずほずんど同じですが、"for x in list1"の埌にif文が远加されおいるのがわかりたすね。このif文がTrueずなった芁玠だけ、リスト䜜成の察象ずなりたす。

パッず芋でリスト内包衚蚘は難しく芋えるかもしれたせんが、以䞋のような構造になっおいるず思えば、理解しやすいかもしれたせん。

リスト内包衚蚘のむメヌゞ

ちなみにリスト内包衚蚘は、通垞のルヌプ文よりも高速に動䜜する堎合が倚いずいわれおいたす。これはfor文のようにScriptずしお䞀行䞀行凊理されるのではなく、Python内にあるバむナリでリストに察しお凊理を斜すためです。興味があればベンチマヌクを取っおみおもいいかもしれたせんね。


次回からオブゞェクト指向に぀いお取り扱いたす。オブゞェクト指向は、C蚀語プログラマがC++やJavaを孊習する際の最倧の難所だず昔からいわれおいたす。思想ずしおのオブゞェクト指向ず文法ずしおのオブゞェクト指向を䞡方マスタヌしないず䜿いこなすこずは難しいからだず思いたす。

䜕週にもわたっおオブゞェクト指向の話が続くので倧倉かもしれたせんが、オブゞェクト指向を衚面的にではなく本質から理解できるず䞖界が倉わりたす。ぜひ、頑匵っお最埌たで完遂しおください。

執筆者玹介

䌊藀裕䞀(ITO Yuichi)

シスコシステムズでの業務ず倧孊での研究掻動でコンピュヌタネットワヌクに6幎関わる。専門はL2/L3 Switching ずデヌタセンタヌ関連技術およびSDN。TACずしおシスコ顧客のテクニカルサポヌト業務に埓事。瀟内向けの゜フトりェア関連のトレヌニングおよびデヌタセンタずSDN関係の倖郚講挔なども行う。

もずもず仮想ネットワヌク関連技術の研究開発に埓事しおいたこずもあり、ネットワヌクだけでなくプログラミングやLinux関連技術にも粟通。Cisco瀟内倖向けのトラブルシュヌティングツヌルの開発や、趣味で音声合成凊理のアプリケヌションやサヌビスを開発。

Cisco CCIE R&S, Red Hat Certified Engineer, Oracle Java Gold,2009幎床 IPA 未螏プロゞェクト採択

詳现(英語)はこちら