4

次の Haskell ステートメントを検討してください。

mapM print ["1", "2", "3"]

実際、これは「1」、「2」、「3」を順番に出力します。

質問:最初に "1" を出力し、次に"2" を出力し、最後に"3"を出力することをどうやって知ることmapMができますか? これを行うという保証はありますか?それとも、GHC の奥深くで実装されているのは偶然ですか?

4

3 に答える 3

10

mapM print ["1", "2", "3"]の定義を拡張して評価mapMすると、 (無関係な詳細を無視して) に到達します。

print "1" >> print "2" >> print "3"

andは、これ以上評価できない IO アクションの抽象コンストラクターprintと考えることができます。これは、 のようなデータ コンストラクターがこれ以上評価できないのと同じです。>>Just

の解釈はprint s印刷のアクションでsあり、の解釈はa >> b最初に実行aしてから実行するアクションですb。というわけで、の解釈は

mapM print ["1", "2", "3"] = print "1" >> print "2" >> print "3"

最初に 1 を印刷し、次に 2 を印刷し、最後に 3 を印刷します。

これが実際に GHC でどのように実装されるかは、まったく別の問題であり、長い間心配する必要はありません。

于 2016-11-03T22:34:01.080 に答える
7

評価の順番は保証しませんが、効果の順番は保証します。詳細については、について説明しているこの回答を参照してくださいforM

次のトリッキーな区別をすることを学ぶ必要があります。

  1. 評価の順序
  2. 効果の順序 (別名「アクション」)

フォーム、シーケンス、および同様の関数が約束するのは、効果が左から右に順序付けられるということです。たとえば、次の例では、文字列内で発生するのと同じ順序で文字を出力することが保証されています...

: "forMmapM引数が反転しています。結果を無視するバージョンについては、 を参照してくださいforM_。"

于 2016-11-03T22:17:50.747 に答える
4

予備的な注意: Reid Barton と Dair による回答は完全に正しく、実際の懸念事項を完全にカバーしています。この回答の途中で、それらと矛盾するという印象を受けるかもしれませんが、そうではなく、最後までたどり着くまでに明らかになるでしょう. それが明確になったので、言語の弁護士にふける時が来ました。

mapM print[ ] が [リスト要素を順番に出力する]という保証はありますか?

はい、他の回答で説明されているように、あります。ここでは、この保証を正当化する理由について説明します。

この時代でmapMは、デフォルトでは単にtraverseモナドに特化しています:

traverse
  :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)
mapM
  :: (Traversable t, Monad m) => (a -> m b) -> t a -> m (t b)

そのため、以下では主にtraverse、および効果の順序付けに関する期待がTraversableクラスにどのように関連するかに関心があります。

traverse効果の生成に関する限り、トラバースされたコンテナー内の各値に対して効果を生成し、関連するインスタンスApplicativeを介してそのようなすべての効果を結合します。Applicativeこの 2 番目の部分は、 の型によって明確に反映されsequenceAます。これにより、適用可能なコンテキストが、いわばコンテナーから取り出されます。

sequenceA :: (Traversable t, Applicative f) => t (f a) -> f (t a)

-- sequenceA and traverse are interrelated by:
traverse f = sequenceA . fmap f
sequenceA = traverse id

Traversableたとえば、リストのインスタンスは次のとおりです。

instance Traversable [] where
    {-# INLINE traverse #-} -- so that traverse can fuse
    traverse f = List.foldr cons_f (pure [])
      where cons_f x ys = (:) <$> f x <*> ys

効果の組み合わせ、つまり順序​​付けが によって行われることは明らかな(<*>)ので、少し注目してみましょう。わかりやすいIO例としてアプリカティブ ファンクターを取り上げると、(<*>)左から右への順序付け効果を確認できます。

GHCi> -- Superfluous parentheses added for emphasis.
GHCi> ((putStrLn "Type something:" >> return reverse) <*> getLine) >>= putStrLn
Type something:
Whatever
revetahW

(<*>)ただし、シーケンスは慣例により左から右に影響し、固有の理由によるものではありませんトランスフォーマーBackwardsラッパーで見られるように、原則として、(<*>)右から左への順序付けで実装し、合法的なApplicativeインスタンスを取得することは常に可能です。(<**>)ラッパーを使用せずに、 fromを利用Control.Applicativeしてシーケンスを反転することもできます。

(<**>) :: Applicative f => f a -> f (a -> b) -> f b
GHCi> import Control.Applicative
GHCi> (getLine <**> (putStrLn "Type something:" >> return reverse)) >>= putStrLn
Whatever
Type something:
revetahW

エフェクトのシーケンスを簡単に反転できることを考えると、ApplicativeこのトリックがTraversable. たとえば、実装するとしましょう...

esrevart :: Applicative f => (a -> f b) -> [a] -> f [b]

... 効果のシーケンスを使用または反転するtraverseためにリストを保存するのと同じようにします(これは読者の演習として残します)。の合法的な実装でしょうか? 保持の同一性と合成の法則を証明しようとすることによってそれを理解するかもしれませんが、実際にはそれは必要ではありません。トランスフォーマー一部でもあるラッパーは、この反転の一般的な実装を提供します。Backwards(<**>)esrevarttraverseTraversableBackwards ffesrevarttraverseTraversableReverse

Traversableしたがって、効果の順序が異なる法的事例が存在する可能性があると結論付けました。特に、traverse効果を末尾から先頭に並べたリストが考えられます。しかし、それは可能性をそれほど奇妙にするものではありません. 完全な当惑を避けるために、Traversableインスタンスは通常プレーンで実装され(<*>)、トラバース可能なコンテナーを構築するためにコンストラクターが使用される自然な順序に従います。これは、リストの場合、予想される効果の頭から尾への順序付けになります。この規則が現れる場所の 1 つはDeriveTraversable拡張機能によるインスタンスの自動生成です。

最後に、歴史的なメモ。mapMクラスの観点から、最終的には約であるこの議論を助長Traversableすることは、それほど遠くない過去に疑わしい関連性のある動きになるでしょう. mapM効果的に取り込まれたtraverseのは昨年だけでしたが、それはずっと前から存在していました。たとえば、1996 年のHaskell Report 1.3Applicativeは、何年も前に作成Traversableされました (実際にはありませんap) は、次の仕様を提供しますmapM

accumulate        :: Monad m => [m a] -> m [a]
accumulate        =  foldr mcons (return [])
                     where mcons p q = p >>= \x -> q >>= \y -> return (x:y)

mapM              :: Monad m => (a -> m b) -> [a] -> m [b]
mapM f as         =  accumulate (map f as)

ここで によって適用される効果の順序付けは、(>>=)左から右へと行われます。これは、それが賢明なことであること以外の理由はありません。

mapMPS: 操作に関して右から左に書くことは可能ですが、強調する価値がありMonadます (たとえば、ここで引用されているレポート 1.3 の実装では、pqの右側を交換するだけで済みますmcons) 。 、Backwardsモナドの一般的なものはありません。finx >>= fMonad m => a -> m b値から効果を作成する関数であるため、 に関連付けられた効果は に依存fxます。結果として、 で可能なような順序付けの単純な反転は、(<*>)正当であるどころか、意味があることさえ保証されません。

于 2016-11-04T07:33:51.433 に答える