誰かがHaskellのトラバース機能を説明できますか?


98

私はtraverseから機能を理解しようとして失敗していますData.Traversable。その意味が分からない。私は命令的なバックグラウンドを持っているので、誰かが命令的なループに関してそれを私に説明してもらえますか?疑似コードは大歓迎です。ありがとう。


1
イテレーターパターンの本質」という記事は、トラバースの概念を段階的に構築するために役立つ場合があります。ただし、いくつかの高度な概念が存在します
ジャッキー

回答:


120

traversefmapはと同じですが、データ構造を再構築しているときにエフェクトを実行することもできます。

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ため、別のメソッドとして存在します。ApplicativeMonad

不純な言語でこれを実装fmapする場合、traverse副作用を防ぐ方法がないため、と同じになります。データ構造を再帰的にトラバースする必要があるため、ループとして実装することはできません。これがJavaScriptでそれを行う方法の小さな例です:

Node.prototype.traverse = function (f) {
  return new Node(this.l.traverse(f), f(this.k), this.r.traverse(f));
}

このように実装すると、言語が許可する効果に制限されます。非決定論(アプリケーションモデルのリストインスタンス)が必要で、言語に組み込まれていない場合は、運が悪いです。


11
「効果」という用語はどういう意味ですか?
missingfaktor、2011

23
@missingfaktor:Functorパラメトリックではないパーツの構造情報を意味します。状態の値Stateの故障、MaybeおよびEither、内の要素の数[]、及び内もちろん任意の外部の副作用IO。私はそれを一般的な用語としては気にしません(Monoid"空"と "追加"を使用する関数のように、概念はこの用語が最初に提案するよりも一般的です)。これはかなり一般的で、目的を十分に果たします。
CAマッキャン2011

@CA McCann:わかりました。回答ありがとうございます!
missingfaktor

1
「私はあなたがこれをすることになっていないと確信しています[...]。」間違いなく-の効果をap以前の結果に依存させるのと同じくらい厄介です。私はそれに応じてその発言を言い換えました。
2016年

2
「適用はモナドとほとんど同じですが、効果は以前の結果に依存することができません。」...この行でたくさんのものがクリックしてくれました、ありがとう!
もう一度、

57

traverse内部に物事をオンTraversableTraversable「内側」のもののApplicative、与えられた関数になり、そのApplicative事のsのうち。

Maybeas 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]]

1
最終結果は私にこれを思い出させます。
hugomg 2011

3
@missingno:ええ、彼らは見逃しましたfac n = length $ traverse rep [1..n]
Landei

1
実際には、「List-encoding-programmer」の下にあります(ただし、リスト内包表記を使用しています)。そのウェブサイトは包括的です :)
hugomg '18

1
@missingno:うーん、まったく同じではありません...どちらもリストモナドのデカルト積の動作に依存していますが、サイトは一度に2つしか使用しないため、liftA2 (,)を使用したより一般的な形式よりも似ていtraverseます。
CAマッキャン2011

41

次のように定義できるので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他のタイプのコンテナで使用したり、他のアプリケーションを使用したりすると、さらに興味深いものになります。


では、トラバースは単にmapMのより一般的な形式ですか?実際、sequenceA . fmapリストはと同等でsequence . mapはありませんか?
Raskell 2017

「シーケンス副作用」とはどういう意味ですか?あなたの答えの「副作用」とは何ですか-副作用はモナドでのみ可能であると思いました。よろしく。
Marek

1
@Marek「副作用はモナドでのみ可能だと思ったばかりです」-接続はそれよりもはるかに緩いです:(1)IO タイプは副作用を表すために使用できます。(2)IOたまたまモナドになっているので、とても便利です。モナドは本質的に副作用にリンクされていません。また、通常の意味での「副作用」よりも広い「効果」の意味があることにも注意してください-純粋な計算を含むもの。この最後の点については、「効果的な」とは正確にはどういう意味かを参照してください。
duplode

(ちなみに、@ hammar、上のコメントで概説した理由により、この回答では「副作用」を「効果」に自由に変更しました。)
duplode

17

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_これらの関数のバージョンがあり、どちらも関数からの戻り値を無視し、わずかに高速です。



7

traverse あるループが。その実装は、トラバースされるデータ構造に依存します。これは、リスト、木、あるかもしれないMaybeSeqforループや再帰関数のようなものを経由して横断しているの一般的な方法を持っている(uence)、または何か。配列には、forループ、whileループのリスト、再帰的なツリー、またはwhileループとスタックの組み合わせのいずれかが含まれます。しかし、関数型言語では、これらの面倒なループコマンドは必要ありません。ループの内部(関数の形)をデータ構造とより直接的かつ冗長ではなく結合します。

Traversable型クラスは、おそらくより多くの独立した汎用性の高い、あなたのアルゴリズムを書くことができます。しかし、私の経験によれば、これTraversableは通常、アルゴリズムを既存のデータ構造に単に接着するためにのみ使用されます。修飾されたさまざまなデータ型に対して同様の関数を記述する必要がないことも非常に良いことです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.