F#に恋をする:Dose 1:The Spirit of Functional Programming

親愛なるHabrakollegi!



最後に、関数型プログラミングのアイデアとF#言語の基礎のいくつかのプレゼンテーションに進みます。 今日、私たちは最も重要なことをする必要があります-関数型プログラミングの基本原則を理解し、その精神を吹き込むためです。 より意味のあるレッスンを待っている機能の達人に前もって謝りますが、最初から始めたかったのです。 したがって、まず第一に、人生の物語:

私が若く、モスクワ航空研究所の応用数学部の最初の年にプログラミングを教えていたとき、生徒の一人はX:= X + 1の意味を理解できませんでした。 「どのように、XをX + 1に等しくすることができますか?」 私は彼にこれがどのように可能であるかを説明しなければならなかった、そしてその瞬間に機能プログラマーは彼で死んだ...







なんで? それを理解しましょう。 「関数型プログラミング」という名前は、「関数」という言葉に由来しています。 機能プログラムは、機能から構築されます。 実際、プログラム自体も入力データに適用して結果を取得できる関数です。







「だから何?」とあなたは言います。 -Pascalのプログラムも関数で構成されています(または構成されている場合があります)。 そうです、Pascalのプログラムでのみ、関数に加えて、ループなどの構造を制御する演算子 、そして最も重要なのは代入演算子もあります。 Pascalのプログラムは一連のステップを設定します。各ステップはコンピューターのメモリの特定の状態を変更し、式を計算して変数に割り当てます。







関数型プログラミングでは、そうではありません。 関数のみがあり、すべてのデータは不変です。 プログラム全体は、データを操作し、一部の値を他の値に処理する関数の組み合わせから構築されます。







例を考えてみましょう: 階乗計算関数。 C#などの命令型言語では、関数は次のようになります。







 public int fact(int n) { int f = 1; for (int i = 2; i <= n; i++) { f = f * i; } return f; }
      
      





バッテリーを起動し、サイクルで次の自然数を連続的に掛けます。 F#の場合、変数の概念はありません。関数の組み合わせのみを使用して定義を作成する必要があります。







 let rec fact n = if n = 1 then 1 else n*fact(n-1) in fact 5;;
      
      





この式は階乗5を計算します。ここで計算される主な式はファクト5であり、その前に記述されたすべてがファクトシンボルの値を決定します。







一般的に、 letコンストラクトの意味は、ある式に同義語の名前を付けることです。 例えば







 let x = 1 in let y = x+2 in x+5*y;;
      
      





ここで、名前xとyは、単語inの後にある式のコンテキストでのみ定義されます。したがって、そのような式は式1 + 5 *(1 + 2)と完全に類似しています。







私たちの場合、関数の定義が再帰的であることを示すrecあります。 関数自体は、他の関数の純粋な構成として定義されます:条件付き関数(実際、この場合、ifステートメントは、たとえばExcelで行われるように、3つの引数のif関数で置き換えることができます)、乗算、減算、およびファクト関数自体です。 実際、次のように定義を書き換えることができます(ここでは、わかりやすくするために、条件式の計算手順に関連するいくつかの点を省略しています)。







 let rec fact n = iff ((=) n 1) 1 ((*) n (fact ((-) n 1)));;
      
      





このような記録から、ここには機能の構成以外は何もないことがわかります。 実際、純粋な関数型プログラミングには、 アプリケーション (引数に関数を適用する)と抽象化 (式から関数を構築する機能)の2つの操作しかありません。 fguvという形式のいくつかの関数の連続的な書き込みは、((fg)u)v、つまり アプリケーションは左から右に実行され、最初にfがgに適用され、結果はuなどに適用される関数になります。 抽象化を使用すると、式で関数を取得できます。つまり、 関数定数を記述できます 。 たとえば、 fun x-> x * 2という表記は、整数の引数でdouble値を返す関数を意味します。







もう1つの重要な概念- カリー化を検討してください。 関数型プログラミングのすべての関数は単一です。 引数が1つあります。 この場合、加算などの操作がどのようになるのだろうか?







2つのオプションがあります。 加算は順序付きペアの関数として考えることができます、例えば







 let plus' (x,y) = x+y;;
      
      





(同時に、注意深い読者は、言語では順序付けられたペア、トリプルなどの構築が本格的なデータ型であることに気づきました)、しかし、次のようにプラス関数を記述して、関数型プログラミングの伝統で行動できます:







 let plus xy = x+y;;
      
      





最初のケースで関数型がint * int-> int (つまり、ペアから整数型へのマッピング)の場合、2番目の-int->(int-> int) 、つまり 整数から整数への関数を返す整数型の関数になります。 たとえば、式(プラス1)は増分関数を意味します。 引数を1ずつ増やします。レコードに1 2を加えたものは、(プラス1)2と見なされます。 最初にインクリメント関数を取得し、それを数値2に適用して、目的の結果を取得します。 プラス関数はカリー化された加算関数と呼ばれ、関数型プログラミングでこのような関数を使用するのが慣例です。 特に、すべての標準操作はカリー化された形式で使用できます。たとえば、次のように操作を角かっこで囲み、プレフィックス表記を使用します。







 (+) 1 2;; let incr = (+)1;;
      
      





したがって、関数型プログラミングの次の基本原則を理解しました。 すべては関数であり、関数のみが存在し代入演算子と変数はありません 。 もう1つの原則を理解しましょう。 関数は本格的なエンティティ (ファーストクラスの市民)であり、関数をパラメーターとして渡し、関数定数を記述し、関数を操作できます。 例:







 let double x = x*2.0;;
      
      





次のような2で乗算する機能について説明します。







 public double doub(double x) { return x * 2.0; }
      
      





素晴らしいこと-F#では、パラメーターと値のタイプを指定する必要はほとんどありません。それらは、タイプ推論システムの作業のおかげで自動的に取得されます。 特に、F#インタープリターで上記のdouble関数の定義を導入すると、次のようになります。







val double:float-> float







これは、タイプがfloatからfloatへの関数として自動的に検出されたことを意味します。







次の式を検討してください。







 let double x = x*2.0 in let quad = (double >> double) in quad 5.0;;
      
      





ここでは、最初にダブル関数について説明し、次に別のクアッド関数について説明します。これは、>>と表記される2つのダブル関数の合成です。







レッスンの最後に、振り返りの宿題としていくつかのタスクと質問を提供したいと思います。







宿題:










All Articles