F#に飛び込みます。 C#開発者向けハンドブック

この投稿では、コードをC#からF#に「翻訳」する方法については説明しません。異なるパラダイムにより、これらの各言語がそのタスクの範囲で最適になります。 ただし、コードをあるパラダイムから別のパラダイムに変換することを考えなければ、関数型プログラミングのすべての利点をより速く理解することができます。 好奇心、盛で好奇心and盛で、まったく新しいことを学ぶ準備ができました。 さあ始めましょう!













F# -developersロシア語コミュニティの翻訳シリーズのすべての資料は、タグ#fsharplangruで見つけることができます。


前の記事「 なぜF#を使用する必要があるか」で、F#を今すぐ試す価値がある理由を説明しました。 次に、アプリケーションの成功に必要な基本を分析します。 この投稿は、C#、Java、またはその他のオブジェクト指向言語に精通している人を対象としています。 すでにF#で記述している場合、これらの概念はおなじみのはずです。







違いにまっすぐに



関数型プログラミングの概念を勉強する前に、小さな例を見て、F#がC#とどのように異なるかを判断しましょう。 これは、2つの機能と結果を表示する基本的な例です。







let square x = x * x let sumOfSquares n = [1..n] //      1  n |> List.map square //      |> List.sum //  ! printfn "   5    %d" (sumOfSquares 5)
      
      





型の明示的な指示はなく、セミコロンや中括弧はありません。 括弧は1か所で使用されます。入力値として数値5



を使用してsumOfSquares



関数を呼び出し、結果を画面に表示します。 パイプライン演算子|>



(パイプライン演算子)は、Unixのパイプライン(パイプ、パイプ)と同じ方法で使用されます。 square



は、 List.map



関数にパラメーターとして直接渡される関数です(F#の関数は値、ファーストクラス関数として扱われます)。







実際にはさらに多くの違いがありますが、最初に基本的なことに対処する必要があります。それらはF#を理解するための鍵だからです。







C#およびF#:主要概念の一致



次の表は、C#とF#の重要な概念のいくつかの対応を示しています。 これは意図的に短く不完全な説明ですが、F#の学習の始めに覚えておくと簡単です。







C#およびオブジェクト指向プログラミング F#と関数型プログラミング
変数 不変の値
説明書 表現
メソッドを持つオブジェクト 種類と機能


いくつかの用語のクイックチートシート:









C#列に示されているものはすべてF#でも可能である(そして非常に簡単に実装できる)ことは注目に値します。 F#列には、はるかに複雑ではありますが、C#でできることもあります。 左の列の要素はF#で「不良」ではなく、その逆も同様です。 メソッドを備えたオブジェクトはF#での使用に最適であり、状況に応じて最適なソリューションであることがよくあります。







変数ではなく不変の値



関数型プログラミングで最も珍しい概念の1つは不変性(不変性)です。 彼は、関数型プログラミング愛好家のコミュニティで十分な注目を浴びていないことがよくあります。 ただし、デフォルトで値が変更されていない言語を使用したことがない場合、これは多くの場合、これ以上の学習を行うための最初で最も重要な障害となります。 不変性は、ほとんどすべての関数型言語の基本概念です。







 let x = 1
      
      





前の式では、値1



が名前x



関連付けられています。 その存在を通じて、名前x



は値1



を参照し、変更できません。 たとえば、次のコードはx



の値を再割り当てできません。







 let x = 1 x = x + 1 //     !
      
      





代わりに、2行目は、 x



x + 1



等しいかどうかを決定する比較です。 <-



演算子とmutable



修飾子を使用してmutate x



を変更(変更)する方法があります(詳細については、 可変変数を参照してください)。 F#を別の必須プログラミング言語と見なさない場合、その最強点を使用できます。







不変性は基本的に、問題を解決するための通常のアプローチを変えます。 たとえば、 for



ループおよびその他の命令型プログラミングの基本操作は、F#ではあまり使用されません。







より具体的な例を考えてみましょう。入力リストの数値を2乗したいとします。 F#で行う方法は次のとおりです。







 //  ,     let square x = x * x let getSquares items = items |> List.map square let lst = [ 1; 2; 3; 4; 5 ] //    F# printfn "  %A  %A" lst (getSquares lst)
      
      





この例ではfor



ループがないことに注意しfor



ください。 概念レベルでは、これは命令型コードとは大きく異なります。 リスト内の各アイテムを二乗しません。 入力リストにsquare



関数を適用し、二乗値を取得します。 これは非常に微妙な違いですが、実際には大幅に異なるコードにつながる可能性があります。 まず、 getSquares



関数は実際に完全に新しいリストを作成します。







不変性は、リスト内のデータを管理する単なる別の方法よりもはるかに広い概念です。 参照の透過性の概念はF#にとって自然であり、システム設計とこれらのシステムの一部がどのように組み合わされるかに大きな影響を与えます。 システムの機能特性は、予期しない場合に値が変更されない場合により予測可能になります。







さらに、値が不変の場合、同時プログラミングが容易になります。 可変状態のためにC#で発生するいくつかの複雑な問題は、F#ではまったく発生しません。 F#は、すべてのマルチスレッドと非同期の問題を魔法のように解決することはできませんが、多くのことが簡単になります。







命令ではなく式



前述のように、F#は式を使用します。 これは、ステートメントがほとんどすべてに使用されるC#とは対照的です。 両者の違いは一見取るに足らないように思えるかもしれませんが、留意すべきことが1つあります。表現は意味を生み出します。 手順-いいえ。







 // 'getMessage' --  ,  `name` -   . let getMessage name = if name = "Phillip" then // 'if' -  . "Hello, Phillip!" //     .    else "Hello, other person!" //      . let phillipMessage = getMessage "Phillip" // getMessage,  ,  .      'phillipMessage'. let alfMessage = getMessage "Alf" //      'alfMessage'!
      
      





前の例では、F#をC#のような命令型言語と区別するいくつかのポイントを見ることができます。









このアプローチはC#とは非常に異なりますが、ほとんどの場合、F#でコードを記述するときに自然に思えます。







少し深く掘り下げると、F#で命令さえ式を使用して記述されます。 このような式は、タイプunit



値を返します。 unit



C#のvoid



に少し似ていvoid









 let names = [ "Alf"; "Vasily"; "Shreyans"; "Jin Sun"; "Moulaye" ] //  `for`.   'do' ,          `unit`. //    ,     . for name in names do printfn "My name is %s" name // printfn  unit.
      
      





前のfor



ループの例では、すべてがタイプunit



です。 unit



式は、戻り値を持たない式です。







F#:配列、リスト、およびシーケンス



前のコード例ではF#配列とリストを使用していました。 このセクションでは、いくつかの詳細について説明します。







F#はいくつかのタイプのコレクションを提供し、最も一般的なものは配列、リスト、およびシーケンスです。









F#の配列、リスト、およびシーケンスには、式の特別な構文もあります。 これは、プログラムでデータを生成する必要があるさまざまなタスクに非常に便利です。







 //     100   let first100Squares = [ for x in 1..100 -> x * x ] //   ,  ! let first100SquaresArray = [| for x in 1..100 -> x * x |] // ,       // //    Seq.take! let odds = let rec loop x = //     seq { yield x yield! loop (x + 2) } loop 1 printfn " 3  : %A" (Seq.take 3 odds) // : " 3  : seq [1; 3; 5]
      
      





F#関数とLINQメソッドの対応



LINQメソッドに精通している場合、次の表はF#の同様の関数を理解するのに役立ちます。







LINQ F#関数
Where



filter



Select



map



GroupBy



groupBy



SelectMany



collect



Aggregate



fold



またはreduce



Sum



sum





また、 Seq



List



およびArray



モジュールにも同じ関数セットが存在することに気付くかもしれません。 Seq



モジュール関数は、シーケンス、リスト、または配列に使用できます。 配列とリストの関数は、それぞれF#の配列とリストにのみ使用できます。 また、F#のシーケンスは遅延型であり、リストと配列はエネルギッシュです。 リストまたは配列でSeq



モジュール関数を使用すると、遅延計算が必要になり、戻り値の型はシーケンスになります。







前のセクションには多くの情報が含まれていますが、F#でプログラムを作成すると、直感的になります。







機能コンベヤ



前のコード例で|>



演算子が使用されていることに気づいたかもしれません。 UNIXのパイプラインに非常に似ています。左に何かを取り、右に何かの入力に渡します。 この演算子(「パイプ」または「パイプライン」と呼ばれる)は、 機能的なパイプラインを作成するために使用されます。 以下に例を示します。







 let square x = x * x let isOdd x = x % 2 <> 0 let getOddSquares items = items |> Seq.filter isOdd |> Seq.map square
      
      





この例では、 items



は最初にSeq.filter



関数の入力に渡されます。 次に、 Seq.filter



(sequence)の戻り値がSeq.map



関数の入力に渡されます。 Seq.map



の実行結果は、 getOddSquares



関数の出力です。







コンベヤーのオペレーターは非常に使いやすいので、それを使用しない人はほとんどいません。 おそらくこれはF#の最もお気に入りの機能の1つです!







F#:タイプ



F#は.NETプラットフォームの言語であるため、C#と同じプリミティブ型: string



int



を持っています。 .NETオブジェクトを使用し、オブジェクト指向プログラミングの4つの主要な柱をサポートします。 F#はタプル 、およびC#にはない2つの主要なタイプ、レコードと識別された共用体を提供します。







レコードは、最も文字通りの意味で、比較操作を自動的に実装する順序付けられた名前付き値のグループです。 比較がどのように発生するかを考える必要はありません。リンクの等価性を通じて、または2つのオブジェクト間の等価性のカスタム定義を通じて。 レコードは値であり、値を比較できます。 カテゴリ理論の言語を話すタイプワークです。 それらには多くの用途がありますが、最も明白なものの1つは、POCOまたはPOJOとして使用できることです。







 open System //      -. //       type Person = { Name: string Age: int Birth: DateTime } //    `Person`   . //      ,      let p1 = { Name="Charles"; Age=27; Birth=DateTime(1990, 1, 1) } //         let p2 = { Name="Moulaye" Age=22 Birth=DateTime(1995, 1, 1) } //     .     Equals()  GetHasCode(). printfn " ? %b" (p1 = p2) //   `false`,     .
      
      





F#のもう1つの基本的なタイプは、英語文学ではunions、PO 、またはDUとラベル付けされています。 ROは、多くの名前付きバリアントを表すタイプです。 カテゴリー理論の言語では、これは和型と呼ばれます。 また、再帰的に定義することもできるため、階層データの記述が大幅に簡素化されます。







 //     . // // ,   -  '  . type BST<'T> = | Empty | Node of 'T * BST<'T> * BST<'T> //       BST<'T> //  BST     ! let rec flip bst = match bst with | Empty -> bst | Node(item, left, right) -> Node(item, flip right, flip left) //   BST let tree = Node(10, Node(3, Empty, Node(6, Empty, Empty)), Node(55, Node(16, Empty, Empty), Empty)) //  ! printfn "%A" (flip tree)
      
      





タダム! タグ付けされたアソシエーションとF#の力を活用して、バイナリ検索ツリーを展開する任意のインタビューを実行できます。







Node



バリアントの定義に奇妙な構文を見たことは確かです。 これは実際にはタプル署名です。 これは、定義するBSTが空またはタプル(, , )



いずれかであることを意味します。 これは署名に関するセクションでより詳細に説明されます







すべてをまとめる:F#60秒で構文



次のコード例は、F#構文のこの優れたレビューを書いたF#コミュニティのヒーローであるScott Vlashinの許可を得て提供されています。 約1分で読むことができます。 この例はわずかに編集されています。







 //      ,  .    . //      . (*       (    ). *) // ======== "" (   ) ========== //   "let"   ()  let myInt = 5 let myFloat = 3.14 let myString = "" //   -     // ========  ============ let twoToFive = [ 2; 3; 4; 5 ] //      , //       . let oneToFive = 1 :: twoToFive //  ::       // : [1; 2; 3; 4; 5] let zeroToFive = [0;1] @ twoToFive //  @    // :       ,    ! // ========  ======== //   "let"    . let square x = x * x //   -   . square 3 //    .   . let add xy = x + y //   add (x,y)!   //   . add 2 3 //  . //    ,   . //     . let evens list = let isEven x = x % 2 = 0 //  "isEven"   ("")  List.filter isEven list // List.filter -    //   :  //  ,    evens oneToFive //   //    ,   . //   ,   "map"   , //    "sum"  . //   "List.map"       "List.sum" let sumOfSquaresTo100 = List.sum (List.map square [ 1 .. 100 ]) //           "|>" //      sumOfSquares,     let sumOfSquaresTo100piped = [ 1 .. 100 ] |> List.map square |> List.sum // "square"   //    - ( ) //     "fun" let sumOfSquaresTo100withFun = [ 1 .. 100 ] |> List.map (fun x -> x * x) |> List.sum //  F#    -   "return"   //          // ========    ======== // Match..with.. -  case/switch  " ". let x = "a" match x with | "a" -> printfn "x -  a" | "b" -> printfn "x -  b" | _ -> printfn "x -  - " //   " " // Some(..)  None    Nullable<T> let validValue = Some(99) let invalidValue = None //    match..with   "Some"  "None" //         "Some". let optionPatternMatch input = match input with | Some i -> printfn "  %d" i | None -> printfn "  " optionPatternMatch validValue optionPatternMatch invalidValue // =========    ========= //  -  ,     . //   . let twoTuple = (1, 2) let threeTuple = ("a", 2, true) //    .     . type Person = { First: string; Last: string } let person1 = { First="John"; Last="Doe" } //         //    . let person2 = { First="Jane" Last="Doe" } //   .  -  . type Temp = | DegreesC of float | DegreesF of float let temp = DegreesF 98.6 //      . // ,  -,    //    : type Employee = | Worker of Person | Manager of Employee list let jdoe = { First="John"; Last="Doe" } let worker = Worker jdoe // =========    ========= //  printf/printfn    Console.Write/WriteLine  C#. printfn "     int %i, float %f, bool %b" 1 2.0 true printfn " %s,  -  %A" "hello" [ 1; 2; 3; 4 ] //        printfn "twoTuple=%A,\nPerson=%A,\nTemp=%A,\nEmployee=%A" twoTuple person1 temp worker
      
      





さらに、.NETおよびサポートされている言語の公式ドキュメントには、 「F#Tour」という資料があります。







次にすること



この投稿で説明するものはすべて、F#の表面的な機能にすぎません。 この記事を読んだ後、F#と関数型プログラミングに没頭できることを願っています。 F#をさらに調査するための演習として記述できる例をいくつか示します。









F#を使用できるタスクは他にもたくさんあります。 前のリストは決して完全なものではありません。 F#はさまざまなアプリケーションで使用されます:単純なビルドスクリプトから数十億の収益を上げるオンラインストアのバックエンドまで。 F#を使用できるプロジェクトに制限はありません。







追加のリソース



F#には、C#またはJavaの経験がある人向けの資料など、多くのチュートリアルがあります。 次のリンクは、F#の詳細を説明するのに役立ちます。









F#の学習を開始する他のいくつかの方法についても説明します。







最後に、F#コミュニティは非常に初心者に優しいです。 Slackには、F#Software Foundationがサポートする非常に活発なチャットがあり、 自由に参加できる初心者ルームがあります。 行うことをお勧めします!







ロシア語を話すコミュニティF#のサイトを訪れることを忘れないでください! 言語の学習について質問がある場合は、チャットルームでお気軽にご相談ください。









翻訳著者について



この記事は、 ロシア語を話すF#開発者のコ​​ミュニティの努力によって翻訳されました。

@schvepsss .








All Articles