2009年5月1日金曜日

Pythonの"関数型"の機能の起源

原文:http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html
原文投稿者:Guido van Rossum

私はPythonが関数型言語から強く影響を受けたことがあるとは一度も考えたことはなかったが、そのように言ったり思ったりする人はいるようである。私がそれまでに慣れ親しんできた言語は、C言語やAlgol 68のような命令的な言語である。Pythonの関数はファーストクラスオブジェクトとして作成されているが、Pythonを関数型のプログラミング言語として見たことはない。しかし、以前から、リストと関数を使用して、もっと多くのことをしてみたいと思うユーザがいたことは確かである。

リストに対して行う一般的な操作としては、リストのそれぞれの要素に対して関数を適用し、新しい配列を作成するというものがある。サンプルコードを上げると以下のような操作になる。

  1. def square(x):  
  2.     return x*x  
  3.   
  4. vals = [1234]  
  5. newvals = []  
  6. for v in vals:  
  7.     newvals.append(square(v))  

LispやSchemeのような関数型言語では、このような操作は言語の組み込み関数として用意されている。そのため、最初のころは、これらの言語に慣れていたユーザは同等の機能をPythonに実装していた。

  1. def map(f, s):  
  2.     result = []  
  3.     for x in s:  
  4.             result.append(f(x))  
  5.     return result  
  6.   
  7. def square(x):  
  8.     return x*x  
  9.   
  10. vals = [1234]  
  11. newvals = map(square,vals)  

上記のコードの微妙な部分としては、リストの要素に対して適用したい処理を、完全に独立した関数として別に定義しなければならないということを好まない人も少なからずいる、ということである。Lispのような言語では、map関数を呼ぶ時に簡単に関数を定義することができる。例えば、Schemeでは、無名の関数を作成することができるため、lambdaを使用することで1行で配列に対して処理を行うことができる。

  1. (map (lambda (x) (* x x)) '(1 2 3 4))    

Pythonは関数をファーストクラスオブジェクトとして作成するが、関数型言語と同じように無名関数を作成する機能は持っていなかった。

1993年の終わり頃になると、何人ものユーザが、無名関数を作成するためのさまざまなアイディアを投稿してきた。これと同時に、リストを操作する、map(), filter(), reduce()といった関数も一緒に提案があった。例えば、"Pythonプログラミング"の著者のMark Lutz氏はexecを使用して無名関数を作成するコードをいくつか投稿した。

  1. def genfunc(args, expr):  
  2.     exec('def f(' + args + '): return ' + expr)  
  3.     return eval('f')  
  4.   
  5. # サンプルの使用方法  
  6. vals = [1234]  
  7. newvals = map(genfunc('x''x*x'), vals)  

Tim Peters氏は以下のようにユーザが入力できるように、構文を多少簡単にするための解決策を補足として提案した。

  1. vals = [1234]  
  2. newvals = map(func('x: x*x'), vals)  

上記の例から、関数型のような機能が要望されているのは明らかであった。しかし同時に、文字列として書かれた無名関数をexecの中で手動で処理するのは極めてトリッキーなやり方に見えた。そのようなこともあり、1994年の1月には、map(), filter(), reduce()の3つの関数が標準ライブラリに追加されることとなった。これに加えて、より直感的な文法で無名関数を式として作成するための、lambda演算子も導入された。lambda演算子の使い方は以下の通りである。

  1. vals = [1234]  
  2. newvals = map(lambda x:x*x, vals)  

この機能追加は、初期のころに、ユーザの提供したコードによって行われたという意味で、Pythonのコミュニティにとっては意義深いものであった。しかし、残念なことに著者の名前を思い出せず、SVNのログにもこの記録がないのである。もしこの読者の中にこのコードの作者がいるのであれば、コメントを是非とも残して欲しい。

私は"lambda"という用語の使用については全面的に賛成ではなかったが、代替案もなかったために、Pythonに取り込まれることになった。その用語を選択したのは現在は無名の貢献者であるが、当時は良く悪くも、現在と比較して、少ない議論でどんどん大きな変化を行わなければならない時期であった。

lambdaはただ、無名関数を定義するための文法的なツールを実現する、という意図だけで作成されたものである。しかし、この用語の選択によって、予期していなかったような問題がいくつも起きることになった。例えば、関数型言語に通じたユーザは、他の言語のlambdaの意味と完全に一致していることを期待してPythonのlambdaを使用した。しかしPythonの実装には高度な機能が大きく欠けているという違いがあったのである。例えば、lambdaにまつわる微妙な問題として、lambdaの式がスコープ内の周りの変数を参照できなかったというものである。以下のコードはmap関数の中のlambda関数で未定義の変数'a'を参照したというエラーが発生して処理が止まってしまうのである。

  1. def spam(s):  
  2.     a = 4  
  3.     r = map(lambda x: a*x, s)  

直感的ではないが、この問題を回避する方法としては、デフォルト引数と隠されたパラメータとしてを通じてlambda式に値を渡すというものがある。

  1. def spam(s):  
  2.     a = 4  
  3.     r = map(lambda x, a=a: a*x, s)  

この問題に対する「正しい」解決策としては、内部関数に対して、暗黙のうちにスコープ内のすべてのローカル変数の参照を渡すようにするというものがある。これは「クロージャ」として知られており、関数型言語の重要な機能の一つである。しかし、この機能はPython 2.1では"from future"でimprotしたときのみの先行機能機能として、Python 2.2では正式な機能として、ようやく取り入れられた。

lambdaや他の関数型の機能導入の動機になっていたmap, filter, reduce関数は、リスト内包表記と、ジェネレータ表現に取って代わられようとしている。実際、Python 3.0ではreduce関数は組み込み関数のリストから除外されることになっている。(lambda, map, filterはそのまま残るので苦情を言う必要はありません :-)

訳注:reduceは組み込み関数からfunctoolsモジュールへ移動した。

私はPythonを関数型言語として見ていないが、クロージャの導入は価値があったと考えている。クロージャは他の多くのプログラミング機能の拡張の開発の役に立っているからである。例えば、新スタイルクラスや、デコレータなど、いくつかの新しい機能はクロージャを利用している。

最後になるが、Pythonは何年もかけて、いくつもの関数型言語機能を取り込んできたが、まだ「本当の」関数型プログラミング言語に見られる機能がいくつも欠けている。例えば、末尾再帰のような、最適化に関する部分にはまだ取り組んでいない。Pythonは極めて動的な性質があるため、HaskellやMLといった関数型言語で行われるような、コンパイル時の最適化を行うことはできないが、それはそれでかまわないと考えている。

0 件のコメント:

コメントを投稿