回答:
traverse
fmap
はと同じですが、データ構造を再構築しているときにエフェクトを実行することもできます。
Data.Traversable
ドキュメントの例をご覧ください。
data Tree a = Empty | Leaf a | Node (Tree a) a (Tree a)
のFunctor
インスタンスは次のようにTree
なります。
instance Functor Tree where
fmap f Empty = Empty
fmap f (Leaf x) = Leaf (f x)
fmap f (Node l k r) = Node (fmap f l) (f k) (fmap f r)
f
すべての値に適用して、ツリー全体を再構築します。
instance Traversable Tree where
traverse f Empty = pure Empty
traverse f (Leaf x) = Leaf <$> f x
traverse f (Node l k r) = Node <$> traverse f l <*> f k <*> traverse f r
Traversable
コンストラクタは応用的なスタイルで呼ばれている以外のインスタンスは、ほぼ同じです。これは、ツリーの再構築中に(副次的な)影響がある可能性があることを意味します。適用はモナドとほとんど同じですが、効果が以前の結果に依存することはありません。この例では、たとえば、左のブランチを再構築した結果によっては、ノードの右のブランチとは異なる処理を実行できなかったことを意味します。
歴史的な理由により、このTraversable
クラスには、のモナディックバージョンも含まtraverse
れていmapM
ます。すべての意図と目的mapM
は同じです。後でのみのスーパークラスになったtraverse
ため、別のメソッドとして存在します。Applicative
Monad
不純な言語でこれを実装fmap
する場合、traverse
副作用を防ぐ方法がないため、と同じになります。データ構造を再帰的にトラバースする必要があるため、ループとして実装することはできません。これがJavaScriptでそれを行う方法の小さな例です:
Node.prototype.traverse = function (f) {
return new Node(this.l.traverse(f), f(this.k), this.r.traverse(f));
}
このように実装すると、言語が許可する効果に制限されます。非決定論(アプリケーションモデルのリストインスタンス)が必要で、言語に組み込まれていない場合は、運が悪いです。
Functor
パラメトリックではないパーツの構造情報を意味します。状態の値State
の故障、Maybe
およびEither
、内の要素の数[]
、及び内もちろん任意の外部の副作用IO
。私はそれを一般的な用語としては気にしません(Monoid
"空"と "追加"を使用する関数のように、概念はこの用語が最初に提案するよりも一般的です)。これはかなり一般的で、目的を十分に果たします。
ap
以前の結果に依存させるのと同じくらい厄介です。私はそれに応じてその発言を言い換えました。
traverse
内部に物事をオンTraversable
にTraversable
「内側」のもののApplicative
、与えられた関数になり、そのApplicative
事のsのうち。
Maybe
as Applicative
とlistを使ってみましょうTraversable
。まず、変換関数が必要です。
half x = if even x then Just (x `div` 2) else Nothing
そのため、数値が偶数の場合、その半分(a内Just
)を取得し、それ以外の場合はを取得しNothing
ます。すべてがうまくいくと、次のようになります。
traverse half [2,4..10]
--Just [1,2,3,4,5]
だが...
traverse half [1..10]
-- Nothing
その理由は、<*>
関数が結果を作成するために使用され、引数の1つがであるNothing
場合にNothing
戻るためです。
もう一つの例:
rep x = replicate x x
この関数x
は、内容を含む長さのリストを生成しますx
(例:rep 3
=)[3,3,3]
。の結果はtraverse rep [1..3]
何ですか?
の部分的な結果を取得し[1]
、[2,2]
を[3,3,3]
使用しrep
ます。リストのセマンティクスはそのままApplicatives
で、「すべての組み合わせを取る」、たとえば(+) <$> [10,20] <*> [3,4]
is [13,14,23,24]
です。
「すべての組み合わせ」[1]
とは、[2,2]
2回です[1,2]
。全2回の組み合わせ[1,2]
と[3,3,3]
6倍です[1,2,3]
。だから私たちは:
traverse rep [1..3]
--[[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3]]
fac n = length $ traverse rep [1..n]
liftA2 (,)
を使用したより一般的な形式よりも似ていtraverse
ます。
次のように定義できるのでsequenceA
、の観点から理解するのが最も簡単だと思いtraverse
ます。
traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)
traverse f = sequenceA . fmap f
sequenceA
構造の要素を左から右に順番に並べ、結果を含む同じ形状の構造を返します。
sequenceA :: (Traversable t, Applicative f) => t (f a) -> f (t a)
sequenceA = traverse id
sequenceA
アクションのリストから結果のリストを返すアクションに移動するなど、2つのファンクタの順序を逆にすることもできます。
したがってtraverse
、いくつかの構造を取り、構造f
内のすべての要素をいくつかのアプリケーションに変換するために適用され、それらのアプリケーションの効果を左から右に順序付け、結果を含む同じ形状の構造を返します。
Foldable
関連する関数を定義すると比較することもできますtraverse_
。
traverse_ :: (Foldable t, Applicative f) => (a -> f b) -> t a -> f ()
あなたが主な違いことを見ることができるようにFoldable
とは、Traversable
前者が他の値に結果を畳むする必要があり、一方、後者は、あなたが構造物の形状を維持することを可能にするということです。
その使用法の簡単な例は、リストを通過可能な構造IO
として、およびアプリケーションとして使用することです。
λ> import Data.Traversable
λ> let qs = ["name", "quest", "favorite color"]
λ> traverse (\thing -> putStrLn ("What is your " ++ thing ++ "?") *> getLine) qs
What is your name?
Sir Lancelot
What is your quest?
to seek the holy grail
What is your favorite color?
blue
["Sir Lancelot","to seek the holy grail","blue"]
この例はややエキサイティングですが、traverse
他のタイプのコンテナで使用したり、他のアプリケーションを使用したりすると、さらに興味深いものになります。
sequenceA . fmap
リストはと同等でsequence . map
はありませんか?
IO
タイプは副作用を表すために使用できます。(2)IO
たまたまモナドになっているので、とても便利です。モナドは本質的に副作用にリンクされていません。また、通常の意味での「副作用」よりも広い「効果」の意味があることにも注意してください-純粋な計算を含むもの。この最後の点については、「効果的な」とは正確にはどういう意味かを参照してください。
fmap
結果のタイプも変更するマッパー関数内でエフェクトを実行できることを除けば、のようなものです。
データベース内のユーザーIDを表す整数のリストを想像してください[1, 2, 3]
。あなたがしたい場合はfmap
、ユーザ名にこれらのユーザーIDには、伝統的に使用することはできませんfmap
( -使用して、この場合の効果を必要とする関数の内部で使用すると、ユーザ名を読み取るためにデータベースにアクセスする必要があるため、IO
モナドを)。
の署名traverse
は次のとおりです。
traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)
を使用するとtraverse
、効果を実行できるため、ユーザーIDをユーザー名にマッピングするためのコードは次のようになります。
mapUserIDsToUsernames :: (Num -> IO String) -> [Num] -> IO [String]
mapUserIDsToUsernames fn ids = traverse fn ids
と呼ばれる関数もありますmapM
:
mapM :: (Traversable t, Monad m) => (a -> m b) -> t a -> m (t b)
の使用はmapM
に置き換えることができますtraverse
が、その逆はできません。mapM
モナドでのみ機能しますが、traverse
より一般的です。
効果を達成したいだけで有用な値を返さない場合はtraverse_
、mapM_
これらの関数のバージョンがあり、どちらも関数からの戻り値を無視し、わずかに高速です。
traverse
あるループが。その実装は、トラバースされるデータ構造に依存します。これは、リスト、木、あるかもしれないMaybe
、Seq
forループや再帰関数のようなものを経由して横断しているの一般的な方法を持っている(uence)、または何か。配列には、forループ、whileループのリスト、再帰的なツリー、またはwhileループとスタックの組み合わせのいずれかが含まれます。しかし、関数型言語では、これらの面倒なループコマンドは必要ありません。ループの内部(関数の形)をデータ構造とより直接的かつ冗長ではなく結合します。
とTraversable
型クラスは、おそらくより多くの独立した汎用性の高い、あなたのアルゴリズムを書くことができます。しかし、私の経験によれば、これTraversable
は通常、アルゴリズムを既存のデータ構造に単に接着するためにのみ使用されます。修飾されたさまざまなデータ型に対して同様の関数を記述する必要がないことも非常に良いことです。