モナドの例について読むときはいつでも、彼らは常に IO をケーススタディとして提示します。
誰かが提示できるリスト操作を行うモナドの例はありますか? これはやり過ぎかもしれませんが、モナドが通常のリスト操作手法よりも優れているかどうかに興味があります。
Haskell のリスト モナドの大きな秘密は、リスト内包表記が do ブロックの構文糖衣であることです。リスト内包表記を書くときはいつでも、代わりにリストモナドインスタンスを使用する do ブロックを使ってそれを書くことができました。
2 つのリストを取り、それらのデカルト積 (つまり、最初のリストと2 番目のリスト(x,y)
のすべての組み合わせのリスト) を返したいとします。x
y
リスト内包表記でそれを行うことができます:
ghci> [(x,y) | x <- [1,2], y <- [3,4]] -- [(1,3),(1,4),(2,3),(2,4)]
リスト内包表記は、この do ブロックのシンタックス シュガーです。
zs = do x <- [1,2]
y <- [3,4]
return (x,y)
これは、構文糖衣です
zs = [1,2] >>= \x -> [3,4] >>= \y -> return (x,y)
ただし、この例はモナドの威力を実際に示しているわけではありません。なぜなら、リストに Monad インスタンスがあるという事実に依存しなくても、簡単に書くことができるからです。たとえば、Applicative インスタンスのみを使用する場合:
ghci> import Control.Applicative
ghci> (,) <$> [1,2] <*> [3,4] -- [(1,3),(1,4),(2,3),(2,4)]
ここで、正の整数のリストのすべての要素を取得し、それをそれ自体と同じ回数複製するとします (f [1,2,3] = [1,2,2,3,3,3]
たとえば)。なぜそれをしたいのか誰にもわかりませんが、それは簡単です:
ghci> let f xs = [ y | x <- xs, y <- replicate x x ]
ghci> f [1,2,3] -- [1,2,2,3,3,3]
これは単なる構文糖衣です。
f xs = do x <- xs
y <- replicate x x
return y
これは、構文糖衣です
f xs = xs >>= \x -> replicate x x >>= \y -> return y
今回は、applicative インスタンスだけを使用してそれを記述することはできません。主な違いは、最初のバインドから出力を取得し ( x
)、残りのブロックをそれに依存させたことです ( y <- replicate x x
)。
list モナドを使用して「単純な」リストユーティリティ関数を巧みに作成する典型的な例は次のとおりです。
import Control.Monad (filterM)
-- | Compute all subsets of the elements of a list.
powerSet :: [a] -> [[a]]
powerSet = filterM (\x -> [True, False])
の型はfilterM
ですMonad m => (a -> m Bool) -> [a] -> m [a]
。リスト モナドのコンテキストでは、これは非決定論的リスト フィルターです。つまり、代替回答のリストを返す非決定論的述語を取るフィルター操作です。の結果は、代替可能な結果のリストです。filterM
または、より簡単な言葉でfilterM (\x -> [True, False])
言えば、リストの各要素について、それを保持し、破棄したいということです。 filterM
各リスト要素に対してこれを行うことの可能なすべての組み合わせを見つけ出します。
以下は、与えられた整数の約数のペアを見つける非常にばかげた方法です:
divisors:: Int -> [(Int,Int)]
divisors n = do
x <- [1 .. n]
y <- [1 .. n]
if x*y == n then return (x, y) else []
このフラグメントの意味はかなり単純明快です。明らかに、これはこの特定の問題を解決する愚かな方法です。(この場合、はるかに効率的な方法が考えられます。) しかし、この検索が非常に複雑な、より複雑な問題を想像してみてください。この種の検索イディオムは非常に便利です。
別の例では、素因数分解から数値のすべての約数を作成しています (順不同ではありますが)。
divs n = map product
. mapM (\(p,n)-> map (p^) [0..n])
. primeFactorization $ n
-- mapM f = sequence . map f
-- primeFactorization 12 ==> [(2,2),(3,1)] -- 12 == 2^2 * 3^1
関数f
inmapM f == sequence . map f
は、入力リストの各エントリに対して、因数の累乗のリストを生成します。次にsequence
、リストから一度に 1 つの番号を選択して、すべてのパスを形成します。次に、すべての可能な組み合わせのこのリストが供給されmap product
、除数が計算されます。
12 -- primeFactorization:
[(2,2),(3,1)] -- map (\(p,n)-> ...):
[ [1,2,4], [1,3] ] -- sequence:
[[1,1],[1,3],[2,1],[2,3],[4,1],[4,3]] -- map product:
[1,3,2,6,4,12] -- the divisors of 12
Luis Casillas の回答で与えられた優れた説明は、ここにも当てはまります。
リスト モナドのコンテキストで
mapM :: (Monad m) => (a -> m b) -> [a] -> m [b]
は、非決定論的リスト マップ:非決定論的関数 (代替結果のリストを生成するようなもの) をリストにマップし、すべての代替可能な結果リストを作成するマッピング操作です。
リスト操作の良い例は、jQueryのように HTML/XML ドキュメントをクエリすることです。jQuery はモナドです。例を見てみましょう:
$(doc).find('> body')
.find('> *')
.is('table')
.is('.bar')
.find('> caption')
.text()
これにより、CSS クラスを持つすべての最上位テーブルのキャプションが得られますbar
。これは jQuery の通常とは異なる使用方法ですが (通常は単にbody > table.bar > caption
.
xml-conduit
同様にそれを行います:
child cursor >>= element "body" >>= child
>>= element "table" >=> attributeIs "class" "bar"
>>= child >>= element "caption"
>>= descendants >>= content
リストモナドが行うことは、ステップを結合することです。モナド自体は HTML/XML について何も知りません。
HXT
あなたはほとんど同じ方法でそれを行います。新しいHXT
バージョンでは、モナドの代わりにアローと呼ばれるものを使用していますが、基本的な原則は同じです。