次の Haskell ステートメントを検討してください。
mapM print ["1", "2", "3"]
実際、これは「1」、「2」、「3」を順番に出力します。
質問:最初に "1" を出力し、次に"2" を出力し、最後に"3"を出力することをどうやって知ることmapM
ができますか? これを行うという保証はありますか?それとも、GHC の奥深くで実装されているのは偶然ですか?
次の Haskell ステートメントを検討してください。
mapM print ["1", "2", "3"]
実際、これは「1」、「2」、「3」を順番に出力します。
質問:最初に "1" を出力し、次に"2" を出力し、最後に"3"を出力することをどうやって知ることmapM
ができますか? これを行うという保証はありますか?それとも、GHC の奥深くで実装されているのは偶然ですか?
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 でどのように実装されるかは、まったく別の問題であり、長い間心配する必要はありません。
評価の順番は保証しませんが、効果の順番は保証します。詳細については、について説明しているこの回答を参照してくださいforM
。
次のトリッキーな区別をすることを学ぶ必要があります。
- 評価の順序
- 効果の順序 (別名「アクション」)
フォーム、シーケンス、および同様の関数が約束するのは、効果が左から右に順序付けられるということです。たとえば、次の例では、文字列内で発生するのと同じ順序で文字を出力することが保証されています...
注: "forM
はmapM
引数が反転しています。結果を無視するバージョンについては、 を参照してくださいforM_
。"
予備的な注意: 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
(<**>)
esrevart
traverse
Traversable
Backwards f
f
esrevart
traverse
Traversable
Reverse
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)
ここで によって適用される効果の順序付けは、(>>=)
左から右へと行われます。これは、それが賢明なことであること以外の理由はありません。
mapM
PS: 操作に関して右から左に書くことは可能ですが、強調する価値がありMonad
ます (たとえば、ここで引用されているレポート 1.3 の実装では、p
とq
の右側を交換するだけで済みますmcons
) 。 、Backwards
モナドの一般的なものはありません。f
inx >>= f
はMonad m => a -> m b
値から効果を作成する関数であるため、 に関連付けられた効果は に依存f
しx
ます。結果として、 で可能なような順序付けの単純な反転は、(<*>)
正当であるどころか、意味があることさえ保証されません。