web-dev-qa-db-ja.com

コンピューターのプロセスと比較した関数型プログラミング

関数型プログラミングでは、状態の変更を使用することは(少なくとも私の観察からは)悪い習慣と見なされます。コンピューターは命令言語のような問題(一度に1つの操作を実行し、RAMの状態を変更する)で動作するので、関数型プログラミングはコンピューターの動作と矛盾しませんか?

注:関数型言語は存在できないと言っているわけではありません。存在し、非常にうまく機能するものがいくつか存在するためです。

さらに注記:プロセスの違いに加えて、関数型言語は、関与する操作の量が原因で、本質的に非常に遅くなりませんか?

11
sneelhorses

すべてのプログラミング言語とプログラムは、一般化された抽象化です。これらの抽象化を、独自の方法で実行するマシンでシミュレートします。コンピューターの動作に制限されていれば、CやForthなどの「金属に近い」言語を使用することになります。コンピューターは汎用機です。私たちは、プロセッサの命令セットに対して適切と思われるパラダイムを自由に作成できます。これらのコンピューターを非常に強力にするのは、このコンピューターの品質です。問題のあるドメインにコンピューターを適応させる能力です。

言い換えれば、関数の抽象化は、それを実行する基本的なマシンではなく、使用されているプログラミング言語に適切に属しています。

1980年代のXeroxとMITビルド machines は、関数型言語であるLISPを直接実行できます。十分に高速なプロセッサを搭載しているため、これ以上は実行しません。ある言語(プログラムを作成する言語)から別の言語(プロセッサの命令セット)に変換するには、任意のプログラミング言語を実行できる汎用マシンを使用することをお勧めします。

23
Robert Harvey

プログラミング言語には2つの目的があります。 1つ目は見やすい、「プログラミング言語はコンピューターに何をすべきかを伝えなければならない」です。 2つ目はより微妙です。「プログラミング言語では、プログラマーがコンピューターに実行したいことを効率的に伝えることができなければなりません。」理論的には、常にアセンブリ(またはマシンコード)でプログラムし、コンピュータに正確に何をすべきかを伝えることができます。実際には、他の言語を選択します。それは、コンピュータに何をしてほしいかを表現するための優れたツールだからです。

LISPやHaskelなどの最も純粋な関数型プログラミング言語でさえa命令型言語のほんの一部somethingは、純粋な関数の1つを評価してから何かを行うようにコンピュータに指示します結果を画面に出力するなど、マシンの状態にプログラムの99.9999999%が純粋な関数型の世界で行われるように、言語の外側のエッジまでこれをすべてプッシュしますが、そのエッジは常に存在します。これは、プログラムをコンピューターで実行する必要があり、コンピューターが現在使用しているためです。命令論理。

ここで、コンパイルまたは解釈の変換ステップが行われます。ある言語で書かれたプログラムをコンピューターが実行できるものに変換する必要があります。コンピューターから得られる唯一のフックは、「コンピューターは、プログラムが実行すると、アドレスXXXXXから命令の実行が開始されます。」したがって、言語がC++、LISP、またはアセンブリであっても、コードは何かにマップされる必要があり、コンピューターがアドレスXXXXXで実行を開始すると、望ましい結果が発生します。

この翻訳ステップでは、言語の速度の違いが関係し、最適化が大きな役割を果たします。たとえば、誰もがC++ステートメントint x = 1 + 2;を見て、(スタックベースの疑似バイトコード)の行に沿ってオペコードを実行したいことがわかります。

load-constant 1
load-constant 2
add
store-as x

また、定数を変更できないことがわかるので、これを最適化することもできます。

load-constant 3
store-as x

次に、同等のLISP句(let x (+ 1 2))を考えます。私が最適化しないと、あなたが考えるべきほど正確に非効率に見えます:

load-constant '+'  # yes, the + operator is handled as a constant function in LISP
load-constant 1
load-constant 2
call-function 2 # call a 2 argument function, +, on the two arguments, 1 and 2
store-as x

明示的に関数を呼び出す必要があります。これは、単に加算を行うよりも桁違いにコストがかかることがよくあります。関数型言語は遅くなければならないようです!ただし、オプティマイザは+がプログラムのスコープ内で変更されない定数関数であることを認識できるため、動作を「インライン化」して取得できます。

load-constant 1
load-constant 2
add
store-as x

これはおなじみのように見えます。最適化を続行して、

load-constant 3
store-as x

したがって、非常に不自然な例では、関数型プログラミングは文字どおり命令型コーディングと同じくらい高速に実行されます。では、それらの違いは何ですか?なぜ関数型プログラミングに悩むのですか?

結局のところ、関数型プログラミングの理由は、プログラマーが意味を伝えることができるようにするプログラミング言語の2番目の目的です。関数型プログラミングが実際にはプログラマーとコンピューター間のより効果的な通信メカニズムである場合があります。これは、GUIに表示されるような非同期の状況で特に当てはまります。そのような場合、同等の機能的なフレージングが自然に流れる一方で、そのような動作を命令的なフレージングにマッピングするためのトリックや回避策に頼らざるを得ないことがよくあります。これは常にそうであるとは限りません(特にハードコア数学の場合)が、ますます多くのケースで、それはより効果的なツールです。これが、多くの命令型言語が関数プログラミングの小さなフラグメントを追加し始めて、意味をよりよく伝えるのに役立つ理由です(C++のラムダを参照)。

ここに微妙な点があります。 「間違った」言語を使用すると、トリックや回避策を見つける必要があることを私は述べました。これは、関数型言語と命令型言語の両方に当てはまります。使用しなければならない回避策が多いほど、コードを最適化することが難しくなります。多くの場合、関数型言語には、副作用を明示的に捕捉する「純粋な」関数の非常に高い基準があります。これにより、コンパイラーは、命令型で記述したかどうかを証明することが非常に困難であった抜本的な最適化を行うことができます。理論的には、どちらの場合にも最適化を適用できますが、手元の言語に適合しない場合、コンパイラが最適化を確認するのが難しくなる可能性があります。同様に、関数型言語で非常に命令的なコードをモデル化しようとすると、モナドのような奇妙なツールが必要になり、オプティマイザがすべてを評価するための最良の方法を見つけようとすると、負荷がかかります。

ケーススタディとして、コーダーではなくアナリストが使用するように設計された言語を使用しました。したがって、彼らがコードを最適化する方法について多くを学ぶことを彼らに期待することは不合理でした(彼らは彼らの頭脳能力を分析するのに費やす必要がありました!コンパイラは実際にそれを行う必要がありました。実際には、関数型プログラミング言語の方が適していることがわかりました。結果が変わらない場合、1万回以上「関数を呼び出す」ことがよくありました。彼らはそれを一度キャッシュすることができましたが、キャッシュの考え方はプロセスの流れにうまく合わなかったので、単にそうしませんでした。言語は、理想的な顧客ではなく、実際の顧客のために機能する必要があります。

したがって、私たちはそれらに、関数の厳密な純粋性規則を持つ関数型言語を与えました。これにより、言語を作成する開発者は、キャッシュできる値を特定し、変更されたときにキャッシュされた値を無効にすることができました。その結果、「機能的」であるにもかかわらず、メタルスピードから遠く離れた言語が実際に1000倍高速に実行されました。これは、オプティマイザーが純粋な関数の厳密さを利用して最適化の機会をより多く見ることができたためです。

11
Cort Ammon