11
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

filterMの定義を覗いたら疲れた

Posted at

filterM を使って、べき集合を表すことができる。

*Main Control.Monad> filterM (\x -> [True, False]) [1..3]
[[1,2,3],[1,2],[1,3],[1],[2,3],[2],[3],[]]

filterM は、述語にモナド値を返す関数を取ることができるので、この場合は非決定性計算 の文脈を付けられることになる。つまり、[1..3] のそれぞれの要素に対して、対象とするパターンと対象外とするパターンを試し、答えになり得るリストを全て出力することができる。これは、べき集合(与えられた集合から、その部分集合の全体として新たに作り出される集合)である。

と、日本語で書いてみると確かにそうなりそうだなぁと思えるのだが、定義を見てみたら頭がこんがらがってしまったので整理することにした。
実際のところ、非決定性計算 + 畳み込み関数 という、内部の操作が膨れ上がりそうな定義を見て胸がウッとなったのが、今回の動機である。

filterM の定義を覗く

filterM          :: (Monad m) => (a -> m Bool) -> [a] -> m [a]
filterM p        = foldr go (return [])
  where
    go x r = do
      flg <- p x
      ys <- r
      return (if flg then x:ys else ys)

とりあえず、 filterM 関数の実体は foldr 関数であり、その引数に go というモナド値を返す関数を取っていることがわかる。畳み込み操作にこのような関数を使うとどうなるのか。これは、文脈を維持して値を畳み込んでいくということなので、今回はリストのリストがどんどん出来上がっていくことになる。

go 関数を >>= で表す

定義に沿って、内容を確かめるために、上記の go 関数を書き換える。局所関数なのでこのコードは正しくないが、雰囲気としてはこのようになる。

go x r =    p x >>= (\flg -> 
            r   >>= (\ys -> 
            return (if flg then x:ys else ys)))

リストモナドの定義は次のようになっている。

instance Monad []  where
    xs >>= f             = [y | x <- xs, y <- f x]

つまり、リストモナドの場合、 >>= は左辺のリストの各要素に右辺の関数を適用した結果(この結果はリストになる)を連結している。

この操作を go 関数に当てはめてみると、x を引数に取って作られた p x ( Bool のリスト )の各要素を r ( リストのリスト )に置き換えて、この時Bool のリストの要素が True なら対応するリストのリストの要素の先頭に x をくっつけるがFalse なら何もしない、ということになる。なるほどわかりにくい。

図解してみる

ここで、 filterM (\x -> [True, False]) [1..3]foldr で表してみると、

filterM (\x -> [True, False]) [1..3] 
1 =>  foldr go (return []) [1..3]
2 =>  go 1 (go 2 (go 3 (return [])))
3 =>  go 1 (go 2 ([ y | x <- [True, False], y <- (\z -> return []) x ] ])
4 =>  go 1 (go 2 ( [[], []]))
5 =>  go 1 (go 2 ( [[3], []]))
6 =>  go 1 [ y | x <- [True, False], y <- (\z -> return [[3], []]) ]
7 =>  go 1 [ [3], [], [3], []] ]
8 =>  go 1 [ [2,3], [2], [3], [] ]
9 =>  [ y | x <- [True, False], y <- (\z -> return [ [2,3], [2], [3], [] ])]
10 =>  [[2,3], [2], [3], [], [2,3], [2], [3], []]
11 =>  [[1,2,3], [1,2], [1,3], [1], [2,3], [2], [3], []]

上の5行目から8行目までを図解してみると、少し理解が進みそう。

go.png

go 関数の第一引数がBool のリストになり、TrueFalse のそれぞれがアキュムレータで置き換わるために、再帰されるたびに全体の要素数が2倍になっているということがわかる。

終わりに

一旦は何をやっているのか腑に落ちたものの、また忘れそうな気がする。このページは私自身何度も見ることになりそう。

11
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?