関数型プログラミングの核となるアイデアの 1 つは、アルゴリズムをデータ変換として表現することです。Haskell のような遅延言語では、さらに一歩進んで、遅延データ構造を具体化された計算と考えることができます。非常に現実的な意味で、Haskell のリストは通常のリンクされたリストよりもループに似ています: それらは段階的に計算でき、一度にメモリに存在する必要はありません。同時に、データ型を渡してパターン マッチングで検査する機能のようなデータ型を持つことの多くの利点も得られます。
これを念頭に置いて、インデックスを使用して for ループを表現するための「トリック」は、取り得るすべての値のリストを作成することです。あなたの例はおそらく最も単純なケースです:からまでi
のすべての値を取るので、Haskell の組み込み表記を範囲に使用できます:0
255
[0..255]
大まかに言うと、これは Haskell のfor (i = 0 to 255)
;に相当するものです。次に、再帰関数または標準ライブラリの高階関数のいずれかによってこのリストをトラバースすることにより、ループ内の実際のロジックを実行できます。(2 番目のオプションを強くお勧めします。)
この特定のロジックは、fold
. 折り畳みにより、リスト項目を項目ごとに取り込んで、何らかの結果を構築できます。各ステップで、リスト項目と、これまでに構築された結果の値を取得します。この特定のケースでは、インデックスをインクリメントしながら左から右にリストを処理したいので、foldl
;を使用できます。1 つのトリッキーな部分は、リストが逆向きに生成されることです。
のタイプは次のfoldl
とおりです。
foldl :: (b -> a -> b) -> b -> [a] -> b
したがって、関数は中間値とリスト要素を受け取り、更新された中間値を生成します。リストを作成してインデックスを追跡しているため、中間値は両方を含むペアになります。次に、最終結果が得られたら、idx
値を無視して、取得した最終リストを逆にすることができます。
a = let (result, _) = foldl step ([], 1) [0..255] in reverse result
where step (a, idx) i
| someCondition i = (idx:a, idx + 1)
| otherwise = (0:a, idx)
実際、何らかの中間状態 (この場合) を追跡しながらリストを変換するパターンはidx
、型に関して独自の機能を持つほど一般的State
です。コアの抽象化はもう少し複雑です (["You Could Have Invented Monads"][you] を読んで素晴らしい紹介を読んでください) が、結果として得られるコードは実際には非常に読みやすくなっています (インポートを除いて、私は推測します:P) :
import Control.Applicative
import Control.Monad
import Control.Monad.State
a = evalState (mapM step [0..255]) 1
where step i
| someCondition i = get <* modify (+ 1)
| otherwise = return 0
バックグラウンド[0..255]
で何らかの状態 ( の値) を追跡しながらマッピングするという考え方です。すべての配管をまとめて最終結果を得る方法です。関数は各入力リスト要素に適用され、状態にアクセスまたは変更することもできます。idx
evalState
step
関数の最初のケースstep
は興味深いものです。<*
演算子は、最初に左の処理を実行し、次に右の処理を実行するように指示しますが、左の値を返します。これにより、現在の状態を取得してインクリメントしますが、インクリメントする前に取得した値を返すことができます。私たちの状態の概念は第一級のエンティティであり、次のようなライブラリ関数を持つことができるという事実<*
は非常に強力です。この特定のイディオムはツリーをトラバースするのに非常に役立ち、他の同様のイディオムは他のコードに非常に役立ちます。