機能的思考。 パート5

カリー化に関する以前の投稿で、複数のパラメーターを持つ関数が、1つのパラメーターを持つ小さな関数に分割される方法を見ました。 これは数学的に正しい解決策ですが、そうする他の理由があります-それはまた、関数の部分適用と呼ばれる非常に強力な技術につながります 。 このスタイルは関数型プログラミングで非常に広く使用されており、理解することが非常に重要です。




関数の部分的な使用


部分的なアプリケーションの考え方は、関数の最初のN個のパラメーターを修正すると、残りのパラメーターを持つ新しい関数が得られるということです。 カリー化の議論から、部分的な適用がどのように自然に発生するかがわかります。
説明するためのいくつかの簡単な例:


//  ""       +  42 let add42 = (+) 42 //    add42 1 add42 3 //       //      [1;2;3] |> List.map add42 //          "" let twoIsLessThan = (<) 2 //   twoIsLessThan 1 twoIsLessThan 3 //      twoIsLessThan [1;2;3] |> List.filter twoIsLessThan //   ""       printfn let printer = printfn "printing param=%i" //      printer    [1;2;3] |> List.iter printer 

いずれの場合も、さまざまな状況で再利用できる部分的に適用された関数を作成します。


そしてもちろん、部分適用により、関数パラメーターの修正も同様に簡単になります。 以下に例を示します。


 //    List.map let add1 = (+) 1 let add1ToEach = List.map add1 //   "add1"  List.map //  add1ToEach [1;2;3;4] //    List.filter let filterEvens = List.filter (fun i -> i%2 = 0) //    //  filterEvens [1;2;3;4] 

次のより複雑な例は、同じ方法を使用して「埋め込み」動作を透過的に作成する方法を示しています。



 //      - let adderWithPluggableLogger logger xy = logger "x" x logger "y" y let result = x + y logger "x+y" result result //  -      let consoleLogger argName argValue = printfn "%s=%A" argName argValue //           let addWithConsoleLogger = adderWithPluggableLogger consoleLogger addWithConsoleLogger 1 2 addWithConsoleLogger 42 99 //  -      let popupLogger argName argValue = let message = sprintf "%s=%A" argName argValue System.Windows.Forms.MessageBox.Show( text=message,caption="Logger") |> ignore //    -       let addWithPopupLogger = adderWithPluggableLogger popupLogger addWithPopupLogger 1 2 addWithPopupLogger 42 99 

これらのクローズドロガー関数は、他の関数と同様に使用できます。 たとえば、単純なadd42関数の場合と同様に、42を追加するための部分的なアプリケーションを作成し、それをリスト関数に渡すことがadd42ます。


 //         42 let add42WithConsoleLogger = addWithConsoleLogger 42 [1;2;3] |> List.map add42WithConsoleLogger [1;2;3] |> List.map add42 //     

部分的に適用された関数は非常に便利なツールです。 柔軟な(複雑ではありますが)ライブラリ関数を作成できます。デフォルトで再利用可能にするのは簡単です。そのため、クライアントコードから複雑さが隠されます。


部分関数設計


明らかに、パラメーターの順序は、部分的な使用のしやすさに重大な影響を与える可能性があります。 たとえば、 List.mapList.filterなどのListほとんどの関数には、次のような形式があります。


 List-function [function parameter(s)] [list] 

リストは常に最後のパラメーターです。 完全な形式のいくつかの例:


 List.map (fun i -> i+1) [0;1;2;3] List.filter (fun i -> i>1) [0;1;2;3] List.sortBy (fun i -> -i ) [0;1;2;3] 

同じ部分的な適用例:


 let eachAdd1 = List.map (fun i -> i+1) eachAdd1 [0;1;2;3] let excludeOneOrLess = List.filter (fun i -> i>1) excludeOneOrLess [0;1;2;3] let sortDesc = List.sortBy (fun i -> -i) sortDesc [0;1;2;3] 

ライブラリ関数が異なる引数の順序で実装された場合、部分的なアプリケーションはあまり便利ではありません。


多くのパラメーターを使用して関数を作成すると、それらの最適な順序を考えることができます。 すべての設計問題と同様に、「正しい」答えはありませんが、一般に受け入れられている推奨事項がいくつかあります。


  1. 最初に静的である可能性が高いパラメーターを配置する
  2. データ構造またはコレクション(またはその他の変化するパラメーター)を設定する最後の人になる
  3. 減算などの操作をよりよく理解するには、期待される順序を確認することをお勧めします

最初のヒントは簡単です。 上記のロガーの例のように、部分的なアプリケーションによって「固定」される可能性が高いパラメーターを最初に配置する必要があります。


2番目のヒントに従うと、パイプライン演算子と構成を使用しやすくなります。 上記の関数を使用した例で、これを何度も見てきました。


 //          let result = [1..10] |> List.map (fun i -> i+1) |> List.filter (fun i -> i>5) 

同様に、リストに部分的に適用された関数は簡単に作成できます。 リストパラメータは省略できます。


 let compositeOp = List.map (fun i -> i+1) >> List.filter (fun i -> i>5) let result = compositeOp [1..10] 

部分的なBCL関数のラッピング


.NETの.NET基本クラスライブラリ(BCL)関数はF#から簡単にアクセスできますが、F#などの関数型言語で使用するようには設計されていません。 たとえば、ほとんどの関数では先頭にデータパラメーターが必要ですが、F#では一般にデータパラメーターが最後になります。


ただし、これらの関数をより慣用的にするラッパーを作成するのは簡単です。 次の例では、ターゲット文字列が最初ではなく最後に使用されるように、.NET文字列関数が書き直されています。


 //     .NET  let replace oldStr newStr (s:string) = s.Replace(oldValue=oldStr, newValue=newStr) let startsWith lookFor (s:string) = s.StartsWith(lookFor) 

文字列が最後のパラメータになったら、通常どおり、これらの関数をパイプラインで使用できます。


 let result = "hello" |> replace "h" "j" |> startsWith "j" ["the"; "quick"; "brown"; "fox"] |> List.filter (startsWith "f") 

または機能の構成において:


 let compositeOp = replace "h" "j" >> startsWith "j" let result = compositeOp "hello" 

コンベヤーオペレーターを理解する


ビジネスで部分的なアプリケーションを見た後、パイプライン機能がどのように機能するかを理解できます。


パイプライン機能は次のように定義されます。


 let (|>) xf = fx 

彼女がすることは、関数の後にではなく、関数の前に引数を置くことです。


 let doSomething xyz = x+y+z doSomething 1 2 3 //     

関数fにいくつかのパラメーターがあり、関数f最後のパラメーターがパイプラインの入力値xとして機能する場合。 実際、転送された関数fはすでに部分的に適用されており、1つのパラメーター、つまりパイプライン化の入力値(te x )のみを想定しています。


部分的に使用するために書き直された同様の例を次に示します。


 let doSomething xy = let intermediateFn z = x+y+z intermediateFn //  intermediateFn let doSomethingPartial = doSomething 1 2 doSomethingPartial 3 //       3 |> doSomethingPartial //    ,        

これまで見てきたように、パイプライン演算子はF#で非常に一般的であり、データの自然な流れを維持したいときにいつでも使用されます。 あなたが出会ったかもしれないいくつかのより多くの例:


 "12" |> int //   "12"  int 1 |> (+) 2 |> (*) 3 //   

逆コンベヤーオペレーター


時々、逆パイプライン演算子「<|」に遭遇することがあります。


 let (<|) fx = fx 

この関数は何もしないようですが、なぜ存在するのですか?


その理由は、逆パイプライン演算子を中置スタイルのバイナリ演算子として使用すると、括弧の必要性が減り、コードがよりきれいになるためです。


 printf "%i" 1+2 //  printf "%i" (1+2) //   printf "%i" <| 1+2 //    

パイプラインを同時に2方向に使用して、疑似挿入記法を取得できます。


 let add xy = x + y (1+2) add (3+4) //  1+2 |> add <| 3+4 //   

追加のリソース


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



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


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


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



翻訳著者について


@kleidemosによる翻訳
翻訳と編集上の変更は、F#開発者のロシア語コミュニティの努力によって行われました。 また、この記事を公開する準備をしてくれた@schvepsss@shwarsにも感謝します。



Source: https://habr.com/ru/post/J430622/


All Articles