過去数年間で、匿名関数(別名ラムダ関数)は非常に人気のある言語構造になり、ほとんどすべての主要な/主流のプログラミング言語がそれらを導入したか、または今後の標準の改訂で導入する予定です。
しかし、匿名関数は非常に古く、よく知られている数学およびコンピュータサイエンスの概念です(1936年頃に数学者アロンゾ教会によって発明され、1958年以来LISPプログラミング言語によって使用されています。例 here を参照) 。
それでは、なぜ今日の主流のプログラミング言語(その多くは15〜20年前に開発されたもの)が最初からラムダ関数をサポートしていなかったのですか?
そして、過去数年間に何が匿名関数の大量採用を引き起こしたのでしょうか?この現象を開始した特定のイベント、新しい要件、またはプログラミング手法はありますか?
重要な注意事項
この質問の焦点は、無名関数の近代的な主流(したがって、おそらくいくつかの例外を除き、非機能的)言語での導入です。また、関数型言語ではないSmalltalkには無名関数(ブロック)が存在し、CやPascalなどの手続き型言語にも通常の名前付き関数が古くから存在しています。
「機能的パラダイムの採用とその利点」については、質問のトピックではないため、過度に一般化しないでください。
確かに関数型プログラミング、または少なくともその特定の側面に向かう注目すべき傾向があります。ある時点で匿名関数を採用した人気のある言語には、C++( C++ 11 )、PHP( PHP 5.3. )、C#があります。 ( C#v2. )、Delphi(2009年以降)、Objective C( blocks )while Java 8はラムダを言語にサポートします 。そして、一般的には機能的とは見なされないが、最初から、または少なくとも早い段階から匿名関数をサポートしている人気のある言語があり、その好例はJavaScriptです。
すべての傾向と同様に、それらを引き起こした単一のイベントを探すことはおそらく時間の無駄です。それは通常、要素の組み合わせであり、そのほとんどは定量化できません。 Practical Common LISP は2005年に公開され、LISPをpractical言語として新たに注目させる上で重要な役割を果たした可能性があります。かなり長い間、LISPはほとんど学術的な環境で出会う言語、または非常に特定のニッチ市場でした。 JavaScriptの人気は、匿名が 彼の回答 で説明しているように、匿名関数に新たな注目を集める上で重要な役割を果たした可能性もあります。
多目的言語からの関数型概念の採用以外に、関数型(またはほとんど関数型)言語への顕著なシフトもあります。 Erlang(1986)、Haskell(1990)、OCaml(1996)、Scala(2003)、F#(2005)、Clojure(2007)などの言語、さらにR(1993)などのドメイン固有の言語も紹介されてから強い支持を得ています。一般的な傾向により、Scheme(1975)などの古い関数型言語、そして明らかにCommon LISPに新たな注目が集まっています。
もう1つの重要なイベントは、業界での関数型プログラミングの採用だと思います。なぜそれが事実でなかったのかはまったくわかりませんが、90年代前半から中頃のある時点で、関数型プログラミングが(おそらく)で始まる業界でその場所を見つけ始めたように思えます 電気通信におけるアーランの急増 および 航空宇宙およびハードウェア設計におけるハスケルの採用 。
Joel Spolskyは、非常に興味深いブログ投稿 The Perils of JavaSchools を書いています。そこで、彼は、大学の(当時)の傾向に反対し、他よりもJavaを好むと主張しています。言語。ブログの投稿は関数型プログラミングとはほとんど関係ありませんが、重要な問題を特定しています。
そこに議論があります。私のような怠惰なCSの学部生が何年にもわたって、アメリカの大学を卒業しているCS専攻の数が少ないという業界からの苦情と相まって、過去10年間で、他の点では完全に優れた多くの学校が100%Javaに移行しました。 「grep」を使用して履歴書を評価する採用担当者はそれが気に入っているようです。何よりも、Javaについては、プログラマの頭脳を排除するのに十分な困難はありません。ポインタまたは再帰。ドロップアウト率は低く、コンピュータサイエンス学部にはより多くの学生がおり、予算も大きく、すべてが順調です。
大学時代に初めて彼女に会ったとき、LISPがいかに嫌いだったかを今でも覚えています。それは間違いなく過酷な愛人であり、すぐに生産的になることができる言語ではありません(まあ、少なくとも私はできませんでした)。 LISPと比較すると、Haskell(たとえば)は非常に親しみやすく、それほどの労力がなくても、完全なバカのように感じることなく生産性を上げることができます。これは、関数型プログラミングへの移行における重要な要素でもあります。
全体として、これは良いことです。いくつかの多目的言語は、これまでほとんどのユーザーにとって難解であったと思われるパラダイムの概念を採用しており、主要なパラダイム間のギャップは狭くなっています。
関連する質問:
参考文献:
関数型プログラミングの人気がJavascriptの成長と急増にどれだけ似ているかは興味深いと思います。 Javascriptには、関数型プログラミングの範囲に沿って多くの根本的な機能があり、その作成時(1995年)には主流のプログラミング言語(C++/Java)の間ではあまり人気がありませんでした。それは、唯一のクライアント側Webプログラミング言語として突然主流に取り入れられました。突然、多くのプログラマーは単にJavascriptを知る必要があったため、関数型プログラミング言語の機能について何かを知らなければなりませんでした。
Javascriptの急上昇がなければ、関数型言語や機能はどれほど人気があるのでしょうか。
JavaScriptとDOMのイベントハンドラーは、何百万ものプログラマhadがファーストクラスの関数について少なくとも少し学ぶためにany Webでのインタラクティブ性を実現することを意味しました。
そこから、それはanonymous関数への比較的短いステップです。 JavaScriptはthis
を閉じないため、クロージャーについても学ぶことを強くお勧めします。そして、あなたは黄金色です。あなたは、語彙スコープを閉じて閉じる匿名のファーストクラス関数を理解しています。
慣れてきたら、使用するすべての言語でそれを求めます。
それは確かにonly要素ではありませんが、Rubyの人気を指摘しておきます。これは、すでにボード上にある6つの回答のどれよりも重要であるとは言いませんが、多くのことが一度に起こったので、それらをすべて列挙すると便利だと思います。
Rubyは関数型言語ではなく、MLのようなものを使用した場合、そのラムダ、プロッド、ブロックは不格好に見えますが、実際には、マッピングの概念が普及し、JavaおよびPHP=牧草地の牧草地向け。いくつかの言語のラムダは、他の何よりも防御的な動きのようです( "固執!私たちも持っています!!))
しかし、ブロック構文と、.each、.map、.reduceなどとの統合方法は、たとえそれが実際にコルーチンのように動作する構文構造であっても、匿名関数のアイデアを普及させました。 &を介してprocに簡単に変換できるため、関数型プログラミングのゲートウェイドラッグになります。
私は、Ruby on Rails JavaScriptを書くプログラマーは、軽量の関数型スタイルで何かをすることにすでにオンになっていると主張しています。それをプログラマーのブログと組み合わせて、Redditの発明、ハッカーニュース、およびスタックオーバーフローがほぼ同時期に発生し、アイデアはニュースグループの時代よりもインターネット上でより速く広まりました。
TL; DR:Ruby、Rails、JavaScript、ブログ、およびReddit/Hacker News/Stack Overflowが機能的なアイデアを大衆市場に押し出したため、誰もが既存の言語でそれらを考えて、さらなる欠陥を防ぎました。
Yannis が指摘するように、以前は存在しなかった言語での高次関数の採用に影響を与えたいくつかの要因があります。彼が軽く触れただけの重要な項目の1つは、マルチコアプロセッサの急増であり、それに伴って、より多くの並列処理と同時処理が望まれます。
関数型プログラミングのmap/filter/reduceスタイルは並列化に非常に友好的であり、プログラマーは明示的なスレッド化コードを記述せずに複数のコアを簡単に利用できます。
Giorgioが指摘するように、関数型プログラミングには、高階関数だけではありません。関数と、map/filter/reduceプログラミングパターン、および不変性が関数型プログラミングの中核です。これらを組み合わせると、並列および並行プログラミングの強力なツールになります。ありがたいことに、多くの言語はすでに不変性の概念をサポートしており、そうでない場合でも、プログラマーは不変のものとして扱うことができ、ライブラリーとコンパイラーが非同期または並列操作を作成および管理できるようにします。
高次関数を言語に追加することは、並行プログラミングを単純化するための重要なステップです。
更新
Lokiが指摘した懸念に対処するために、さらに詳細な例をいくつか追加します。
ウィジェットのコレクションをトラバースし、ウィジェット価格の新しいリストを作成する次のC#コードを考えます。
List<float> widgetPrices;
float salesTax = RetrieveLocalSalesTax();
foreach( Widget w in widgets ) {
widgetPrices.Add( CalculateWidgetPrice( w, salesTax ) );
}
ウィジェットの大規模なコレクション、または計算量の多いCalculateWidgetPrice(Widget)メソッドの場合、このループは使用可能なコアを十分に活用しません。異なるコアで価格計算を行うには、プログラマーがスレッドを明示的に作成および管理し、作業を渡し、結果をまとめて収集する必要があります。
高次関数がC#に追加されたら、解決策を検討してください。
var widgetPrices = widgets.Select( w=> CalculateWidgetPrice( w, salesTax ) );
ForeachループはSelectメソッドに移動され、実装の詳細が隠されています。プログラマが残しているのは、各要素に適用する関数を選択に指示することだけです。これにより、Select実装が並列で計算を実行できるようになり、プログラマーの関与なしにすべての同期とスレッド管理の問題を処理できます。
ただし、当然のことながら、Selectは並行して動作しません。これが不変性の出番です。Selectの実装は、提供された関数(上記のCalculateWidgets)に副作用がないことを認識していません。この関数は、Selectとその同期のビューの外でプログラムの状態を変更し、すべてを壊す可能性があります。たとえば、この場合、salesTaxの値が誤って変更される可能性があります。純粋な関数型言語は不変性を提供するため、選択(マップ)関数は状態が変化していないことを確実に知ることができます。
C#は、Linqの代替としてPLINQを提供することでこれに対処しています。それは次のようになります:
var widgetPrices = widgets.AsParallel().Select(w => CalculateWidgetPrice( w, salesTax) );
これは、それらのコアを明示的に管理することなく、システムのすべてのコアを最大限に活用します。
ここで最近の歴史に少し関わってきたので、Javaと.NETにジェネリックを追加したことが1つの要因だと思います。それは自然にFunc <、>および他の強く型付けされた計算の抽象化(Task <>、Async <>など)につながります
.NETの世界では、FPをサポートするためにこれらの機能を正確に追加しました。これにより、関数型プログラミング、特にC#3.0、LINQ、RxおよびF#に関連する一連の言語作業がカスケードされました。その進展は他のエコシステムにも影響を与え、現在でもC#、F#、TypeScriptで継続されています。
もちろん、HaskellをMSRでも動作させるのに役立ちます:)
もちろん、他にも多くの影響があり(JSは確かです)、これらのステップは他の多くの影響も受けましたが、これらの言語にジェネリックを追加すると、ソフトウェアの大部分で90年代後半の厳格なOO正統性を破るのに役立ちました世界とFPの扉を開くのに役立ちました。
ドン・サイム
pS F#は2005年ではなく2003年でした。ただし、2005年まで1.0に達しなかったと言えます。2001-02年にはHaskell.NETプロトタイプも作成しました。
ここで多くの答えに同意しますが、興味深いのは、ラムダについて学び、それらに飛びついたときに、他の人が言及した理由のいずれでもないということです。
多くの場合、ラムダ関数はコードの読みやすさを向上させるだけです。ラムダの前に、関数ポインター(または関数、デリゲート)を受け入れるメソッドを呼び出すときは、その関数の本体を別の場所に定義する必要があったため、「foreach」構造がある場合、リーダーは別の場所にジャンプする必要がありました。コードの一部で、各要素を使用して計画していることを正確に確認できます。
要素を処理する関数の本体が数行しかない場合は、無名関数を使用します。コードを読み取っているとき、機能は変更されないままですが、リーダーが前後にジャンプする必要がないため、実装全体が彼の目の前です.
関数型プログラミング手法と並列化の多くは、匿名関数なしで実現できます。通常のものを宣言し、必要に応じて参照を渡します。しかし、ラムダにより、コードの書きやすさとコードの読みやすさが大幅に改善されます。
ほとんどの答えは、関数型プログラミングが一般的にそれを復活させ、主流にした理由の説明に集中しています。しかし、これは特に無名関数に関する質問、そしてなぜそれらが突然それほど人気になったのかという質問には本当に答えていないように感じました。
本当に人気を得ているのは closures です。ほとんどの場合、クロージャは変数を渡される使い捨ての関数であるため、これらに無名関数構文を使用することは明らかに理にかなっています。実際、一部の言語では、クロージャーを作成する唯一の方法です。
閉鎖が人気を博したのはなぜですか?イベント駆動型プログラミングで便利なので、コールバック関数を作成するとき。これは現在、JavaScriptクライアントコードを記述する方法です(実際には、GUIコードを記述する方法です)。イベント駆動型のパラダイムで記述されたコードは通常非同期で非ブロッキング。バックエンドでは、これが C10K問題 の解決策として一般的になりました。
これは真剣な回答を意味するものではありませんが、この質問はJames Iryによるクールでユーモラスな投稿を思い出させました- プログラミング言語の簡単な、不完全な、そしてほとんど間違った歴史 これには次のフレーズが含まれます。
「ラムダは、Javaを持たないことで人気が高まるまで、比較的曖昧に追いやられています。」
0.02ユーロを追加するとしたら、JavaScriptの概念を導入することの重要性には同意しますが、同時プログラミングよりも、現在の非同期プログラミングのせいだと思います。非同期呼び出し(Webページで必要)を行う場合、単純な匿名関数は非常に有用であるため、すべてのWebプログラマー(つまり、すべてのプログラマー)がこの概念に精通する必要がありました。
その理由は、オブジェクト指向プログラミングのコア(変化する状態をオブジェクトでカプセル化する)がもはや適用されない、同時および分散プログラミングの普及が高まっているためだと思います。分散システムの場合is共有状態がないため(およびその概念のソフトウェアの抽象化は漏洩しやすい)、並行システムの場合、共有状態へのアクセスを適切に同期することが証明されているため面倒でエラーが発生しやすい。つまり、オブジェクト指向プログラミングの主要な利点の1つは、多くのプログラムには適用されなくなり、オブジェクト指向パラダイムは以前よりもはるかに役に立たなくなりました。
対照的に、機能的パラダイムは可変状態を使用しません。したがって、関数型のパラダイムとパターンで得られた経験は、同時分散コンピューティングにすぐに転送できます。そして、車輪を再発明するのではなく、業界は現在、そのニーズに対処するためにそれらのパターンと言語機能を借用しています。
匿名関数/ラムダのようなもののもう1つの本当に古い例は、ALGOL 60の call-by-name です。結果として、より壊れやすく/理解するのが難しくなります。
ここで私の知る限りの祖先。