私は関数型プログラミングにもっと慣れたいと思っており、自分自身に設定した最初の教育タスクは、オーディオ周波数を計算するプログラムを C# から F# に変換することです。元のアプリケーションの本質は、大きな配列内の値のサブセットを選択する大きな "for" ループです。どの値が取得されるかは、最後に受け入れられた値と、それ以降に表示された値のランク付けされたリストによって異なります。次の値の決定に向けた進行状況を追跡するために、反復間で持続する変数がいくつかあります。
このループをより「機能的」にするための最初の試みは、引数に配列、これまでの結果セット、最近表示された値のランク付けされたリスト、および実行間で保持する必要があるその他のいくつかの項目を含む末尾再帰関数を使用しました。これは不格好に思えます。また、変数であったものをすべてこの再帰関数のパラメーターに変換しても、何も得られた気がしません。
関数型プログラミングのマスターは、この種のタスクにどのようにアプローチしますか? これは、「純粋な」機能的アプローチがうまく適合しない例外的な状況ですか?関数の「純粋性」を低下させると感じたからといって、変更可能な変数を避けるのは間違っていますか? それらはその関数のスコープ内にのみ存在するため、純粋性を低下させない可能性があります。その感覚はまだありません。
以下は、いくつかの "let" ステートメントと状態の実際のコンポーネントを削除した、コードの蒸留を試みたものです ("temp" は、処理する必要がある中間結果の配列です)。
let fif (_,_,_,_,fif) = fif
temp
|> Array.fold (fun (a, b, c, tentativeNextVals, acc) curVal ->
if (hasProperty curVal c) then
// do not consider current value
(a, b, c, Seq.empty, acc)
else
if (hasOtherProperty curVal b) then
// add current value to tentative list
(a, b, c, tentativeNextVals.Concat [curVal], acc)
else
// accept a new value
let newAcceptedVal = chooseNextVal (tentativeNextVals.Concat [curVal])
(newC, newB, newC, Seq.empty, acc.Concat [newAcceptedVal])
) (0,0,0,Seq.empty,Seq.empty)
|> fif