17

s をs にData.Mapマップする構造があります。なんらかの理由で、次のように、マップの内容をを使用した形式で印刷したいと思います。StringStringskey: valuefoldrWithKey

M.foldrWithKey (\k v b -> putStrLn (k++": "++v++"\n")) (return ()) data

ただし、(マップに複数の要素がある場合でも) マップの最初の要素のみが出力に表示されます。しかし、リストを作成してfoldrWithKey印刷しようとすると、すべての要素が表示されます。

print (M.foldrWithKey (\k v b -> k:b) [] data)

では、I/O を実行しようとしたときに他の要素が表示されないのはなぜでしょうか? それはfoldrが機能する方法ですか、それとも私が見逃している微妙なlazy-io関連の癖がありますか?

4

4 に答える 4

22

それは、正しい折り方がどのように機能するかによるものです。フォールド関数は累積です。各ステップで、1 つの要素 (この場合はキーと値) と残りのデータの累積結果が与えられると、それらを 1 つの結果に結合します。折り畳みは全体としてそれを再帰的に行い、データのセット全体を要約します。

あなたの場合、アキュムレータ関数の「結果」入力を破棄していますb。引数は決して使用されないことに注意してください。IO aは、余分なジャンクが付加された単なる type の値ではなく、実際aに表すのは を生成する計算aであり、その計算は、の最終値の一部として他の計算と組み合わせることによってのみ実行されることに注意してください。main関数 (または、GHCi では、評価される式の)。

蓄積された値を破棄することにより、他の計算が最終結果の一部になることはなく、したがって値が出力されることはありません。

あなたの質問の言い方から、あなたは Haskell の関数型スタイルよりも命令型スタイルのプログラミングに慣れていると思います。明らかに、折り畳みの間に意味のある意味で印刷が実際に「起こっている」命令型言語では、累積された値は役に立たないと仮定するのが合理的です。それが役立つ場合は、これを一種のメタプログラミングと考えてください。値を出力するループを実際に書いているのではなく、実際の出力を行う命令型プログラムを作成していて、累積された値を破棄することで、展開されたループの最初の行以外を基本的にすべて破棄して、類推が悪い。

いずれにせよ、この場合におそらく必要なのは、「残りのデータを出力する」アクションであるbパラメーターを取り、それをアクションと結合することputStrLn ...です。これは(>>)、基本的に「最初のアクションを実行し、結果を無視する」ことを意味する演算子です。 、2番目を実行します」。これは、命令型スタイルの「ループ内の print ステートメント」をかなり直接的に翻訳したものです。


また、それがまったく的外れであることは理解していますが、フォーマットと印刷をそのように混在させることはおそらく避けたいと思います。私の目には、各キーと値のペアを個別にリストにフォーマットしてから、その上でフォーマットする方が整然としているように見えますmapM_ putStrLn

mapM_ここで行っていることの本質を説明する高階関数です。あるタイプのリストと、 を何らかのアクションに変換するa関数が与えられると、関数を各アイテムに適用し、アクションの結果リストを順番に実行します。の typeは一見不可解に見えますが、Haskell の優れた点の 1 つは、型シグネチャの読み取りに慣れると、の型が一目で理解できるだけでなく、実用的なものが 1 つしかないという点でほぼ自己文書化できることです。そのタイプの関数が行うことであり、それはまさにそれ自体が行うことです。aIOmapM_Monad m => (a -> m b) -> [a] -> m ()mapM_mapM_

于 2011-05-16T21:07:03.460 に答える
11

これは、I/O を使用しない場合のより明確な例です。

foldr (\x b -> x) 9 [8,7,6,5,4,3,2,1,0]

8この式は、リストの先頭である を返します。リストの残りの部分はどこに行きますか? リストの残りを処理した結果は 'b' に渡されますが、これは使用されないため、リストの残りは単純に無視されます。

あなたの場合も同じことが起こっています。アキュムレータ 'b' を無視することで、マップの 1 つの要素のみを使用する I/O アクションを構築しています。基本的に、「マップを印刷するには、その最初のキーと値を印刷する」と言われました。あなたが言うべきことは、「マップを印刷するには、その最初のキーと値を印刷し、次にマップの残りを印刷する」ということです。putStrLnこれを行うには、 runsの呼び出し後に変数 'b' の内容が実行されるように調整する必要があります。

M.foldrWithKey (\k v b -> do {putStrLn (k ++ ": " ++ v ++ "\n"); b}) (return ()) d
于 2011-05-16T21:08:09.783 に答える
6

IO とそのようなきれいな印刷を混在させるのはやや悪い形式なので、IO をフローティングアウトするのはどうですか:

> putStr $ foldrWithKey (\k v b -> b ++ k ++ ": "++v++"\n") [] m

さて、コードが機能しない理由については、折り畳みが何を構築しているかを考えてみてください:bパラメータ内の一連の print ステートメントです。ただし、bループのたびに破棄します。

だからそれを追跡してください:

> foldrWithKey (\k v b -> putStrLn (k++": "++v) >> b) (return ()) m   

レッスン、アキュムレータを捨てないでください。

于 2011-05-16T21:10:19.340 に答える
3

(\kvb -> putStrLn (k++": "++v++"\n")) で折りたたむと、 b はどこにも使用されないため、残っているのは折りたたみに残っている最後の IO () だけです。 . したがって、最初の値が出力されます。(\kvb -> putStrLn (k++": "++v++"\n") >> b) で折りたたむことでこれを防ぐことができます。

于 2011-05-16T21:07:37.723 に答える