Pythonの「機能的な」ルーツはどこから来たのですか?

Pythonが、人々の発言や考えに関係なく、関数型言語の影響を受けるとは信じていません。 私はCやAlgol68などの命令型言語に精通しており、関数を「ファーストクラス」オブジェクトにしましたが、Pythonを関数型プログラミング言語とは見なしませんでした。 しかし、ユーザーがリストと機能からより多くのものを望んでいたことは明らかでした。



リスト操作が各リスト項目に適用され、新しいリストが作成されました。 例:

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





LispやSchemeなどの関数型言語では、このような操作は言語の組み込み関数として設計されました。 したがって、このような言語に精通しているユーザーは、Pythonで同様の機能を実装しました。 例:

 def map(f, s): result = [] for x in s: result.append(f(x)) return result def square(x): return x*x vals = [1, 2, 3, 4] newvals = map(square,vals)
      
      





上記のコードの微妙さは、多くの人々が、関数が個別の関数のセットとしてリスト項目に適用されるという事実を好まなかったことです。 Lispなどの言語では、マッピング時に関数を「オンザフライ」で定義できました。 たとえば、Schemeでは、以下のように、ラムダを使用して匿名関数を作成し、単一の式でマッピングを実行できます。

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





関数はPythonの「ファーストクラス」オブジェクトでしたが、匿名関数を作成するためのそのようなメカニズムはありませんでした。



1993年後半、ユーザーは匿名機能を作成するためのさまざまなアイデアを思いついた。 たとえば、Mark Lutzは、execを使用して関数を作成する関数のコードを作成しました。

 def genfunc(args, expr): exec('def f(' + args + '): return ' + expr) return eval('f') # Sample usage vals = [1, 2, 3, 4] newvals = map(genfunc('x', 'x*x'), vals)
      
      





Tim Petersは、構文を簡素化するソリューションのソリューションを作成し、ユーザーに次のことを許可しました。

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







そのような機能が本当に必要であることが明らかになりました。 同時に、匿名関数をプログラマがexecで手動で処理する必要があるコード行として定義するのは魅力的でした。 したがって、1994年1月に、map()、filter()、reduce()関数が標準ライブラリに追加されました。 さらに、より直接的な構文で匿名関数(式など)を作成するために、ラムダ演算子が導入されました。 例:

 vals = [1, 2, 3, 4] newvals = map(lambda x:x*x, vals)
      
      





これらの追加は、開発の初期段階で重要でした。 残念ながら、著者を思い出せず、SVNログには記録されませんでした。 これがあなたのメリットであるなら、コメントを残してください!



ラムダの用語を使用することは好きではありませんでしたが、より優れた明白な代替手段がないため、ラムダはPythonで採用されました。 結局、それは現在匿名の著者の選択であり、その時点での大きな変更は現在よりもはるかに少ない議論を必要としました。



ラムダは、実際には匿名関数を定義するための構文上の砂糖としてのみ意図されていました。 ただし、この用語の選択は、多くの意図しない結果をもたらしました。 たとえば、関数型言語に精通しているユーザーは、ラムダセマンティクスが他の言語のものと一致することを期待していました。 その結果、彼らはPythonの実装には高度な機能が非常に欠けていることを発見しました。 たとえば、ラムダの問題は、提供された式が周囲のコンテキストの変数にアクセスできなかったことです。 たとえば、このコードがある場合、map()は、ラムダ関数が変数 'a'への未定義の参照で機能するため、中止されます。

 def spam(s): a = 4 r = map(lambda x: a*x, s)
      
      





この問題には回避策がありましたが、「デフォルト引数」を使用し、ラムダ式に隠されたパラメーターを渡すことで構成されていました。 例:

 def spam(s): a = 4 r = map(lambda x, a=a: a*x, s)
      
      







内部関数のこの問題に対する「正しい」解決策は、すべてのローカル変数への参照を関数コンテキストに暗黙的に転送することでした。 このアプローチはクロージャと呼ばれ、関数型言語の重要な側面です。 ただし、この機能はバージョン2.2のリリースまでPythonに導入されていませんでした。



不思議なことに、元々ラムダやその他の機能の導入を動機付けていたmap、filter、reduce関数は、「リスト内包表記」とジェネレーターに大きく置き換えられました。 実際、reduce関数はPython 3.0の組み込み関数のリストから削除されました。



Pythonを関数型言語とは考えていませんでしたが、クロージャーを導入することは、言語の他の多くの機能を開発するのに役立ちました。 たとえば、新しいスタイルのクラス、デコレーター、およびその他の最新機能の特定の側面では、クロージャーが使用されます。



最後に、長年にわたって多くの関数型プログラミング機能が導入されてきましたが、Pythonには「実際の」関数型言語の特定の機能が欠けています。 たとえば、Pythonは特定の種類の最適化(テール再帰など)を実行しません。 Pythonの動的な性質により、関数型言語でHaskellまたはMLとして知られるコンパイル時の最適化を導入することは不可能になりました。



All Articles