HOME | 7. 自前の data と class | Haskell のお勉強 | 9. 探索 | download | 書き込む |
Monad は実はそれほど難しい概念ではありません。 "Haskell は Monad を使って参照透明性をおかすことなく IO を実現している。" といううたい文句や、"Monad を理解するのは難しいかもしれない" などという脅し を気にしないで、Haskell 98 にある定義を見れば分かりやすいと思います。
上級 Haskeller は Monad を駆使して難しいことをやりますが、 それは Monad が難しいのではなく、彼らのやっていることが難しいだけです。 つまり、Monad を使うと難しいことが出来るが、Monad そのものが難しいわけではない ということです。
それでは、class Monad の定義を見てみましょう。次のようになっています。
[code 1]
-- in predefined types and class
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
fail :: String -> m a
m >> k = m >>= \_ -> k
fail s = error s
[code 1] から class Monad には 4 つの総称関数
(>>=), (>>), return, fail が
あることが分かります。また、
"class Monad m where" とか "m a" だとか "m b" といった記述が
あるので、
これらの総称関数は Maybe, List, IO などの何か複合的 data 型に関するものだと
いうこと、さらに、raturn とか fail という単語がみえるので、失敗するかもしれない
計算に関わっているということが分かります。さらに、
(>>=) :: m a -> (a -> m b) -> m bから、(>>=) は引数として、
そして、(>>) は、
m >> k = m >>= \_ -> kから、m の値を捨てて、k を呼び出す関数であることが分かります。 (Lisp の progn に似ています。)
以上のことから、Monad というのは失敗するかもしれない計算をつなぎ合わせる 総称関数群であることが分かります。
[code 2]
-- in standard-prelude
instance Monad Maybe where
(Just x) >>= k = k x
Nothing >>= k = Nothing
return = Just
fail s = Nothing
つまり、以下のようになります。
[example 1]
01: EasyMonad> s2i (0,"12345") 02: Just (1,"2345") 03: EasyMonad> s2i (1, "2345") 04: Just (12,"345")[code 3]
01: -- easy_monad.hs 02: -- a small script for haskell8.html 03: 04: module EasyMonad where 05: 06: import Char 07: 08: --- Let's convert a String to Int if the String is [0-9]+ 09: -- s2i is a (reading one character) function 10: s2i :: (Int, String) -> Maybe (Int, String) 11: s2i (i, "") = Just (i, "") 12: s2i (i, c:cs) | isDigit c = Just (i*10 + ord c - ord '0', cs) 13: | otherwise = Nothing 14: 15: -- same as s2i, written in Monadic term 16: s2i' :: (Int, String) -> Maybe (Int, String) 17: s2i' (i, "") = return (i, "") 18: s2i' (i, c:cs) | isDigit c = return (i*10 + ord c - ord '0', cs) 19: | otherwise = fail "The char is not Digit" 20: 21: -- whole function to convert string to Maybe Int 22: str2int :: String -> Maybe Int 23: str2int "" = Nothing 24: str2int str = iter (0, str) 25: where iter (i, "") = Just i 26: iter (i, cs) = let p = s2i (i, cs) 27: in if p == Nothing 28: then Nothing 29: else p >>= iterこのコードは付録の easy_monad.hs に入っているので、それを hugs などの対話的な処理系で load し、[example 2] のようにして遊んでみてください。 文字列が読み込まれていく様子と(>>=) の使い方が分かると思います。
[example 2]
EasyMonad> s2i (0, "123") -- read one Just (1,"23") EasyMonad> s2i (0, "123") >>= s2i -- read two Just (12,"3") EasyMonad> s2i (0, "123") >>= s2i >>= s2i -- read three Just (123,"") EasyMonad> s2i (0, "12a") >>= s2i >>= s2i -- I cannot convert "12a" into an Int Nothing EasyMonad> s2i (0, "abc") >>= s2i >>= s2i -- I cannot convert "abc" into an Int, either Nothings2i' (code 1, 16--19 行目) は s2i を Monad 用語で書き換えたものです。全く同様に動作します。
str2int (code 1, 22--29 行目)は s2i を用いて String を Maybe Int に変換する 関数です。
EasyMonad> str2int "12345" Just 12345 EasyMonad> str2int "12+345" Nothingこの例から Monad 自体はそんなに難しくないことがお解かりいただけたと思います。
c1 >>= c2 >>= c3
⇔ (c1 >>= c2) >>= c3
⇔ c1 >>= (c2 >>= c3) (注 1)
注 1: 上のは概念的に書いたもの。Haskell のコードとして書くと
c1 >>= (\x -> c2 x >>= c3) 。
自前の Monad を作るときは Monad 則を満たすようにして下さい。
[Monad 則]
return a >>= k = k a m >>= return = m m >>= (\x -> k x >>= h) = (m >>= k) >>= h試しに Maybe の Monad が Monad 則を満たしていることを確認してみましょう。
-- Law 1 return x >>= k ⇒ Just x >>= k ⇒ k x -- Law 2 Just x >>= return ⇒ return x ⇒ Just x -- Law 3 Just y >>= (\x -> k x >>= h) ⇒ (\x -> k x >>= h) y ⇒ k y >>= h ⇒ (Just y >>= k) >>= h
IO の結果を関数に渡すため IO の Monad が定義されています。 '...' の部分は実装に依存します。
instance Monad IO where (>>=) = ... return = ... fail s = ioError (userError s)
翻訳前 | 翻訳後 |
---|---|
do{foo} | foo |
do{foo; bazs} | foo >> do{bazs} |
do{let hoge; bazs} | let hoge in do{bazs} |
do{ p <- foo; bazs} | let ok p = do{bazs} ; ok _ = fail "..." in foo >>= ok |
4番目の翻訳規則は fail を考慮しているため一見すると分かりにくくなっていますが、
基本的には以下の式と同等です。
[翻訳規則 4']
do{ p <- foo; bazs} ⇒ foo >>= (\ p -> do{bazs})
以下に通常の記法と do 記法を用いたエコープログラムを示します。
01: -- simple echo 02: -- do notation 03: my_echo :: IO() 04: my_echo = do putStrLn "Enter something." 05: str <- getLine 06: putStrLn $ "You have entered: " ++ str 07: 08: -- conventional notation 09: my_echo' :: IO() 10: my_echo' = putStrLn "Enter something." >> 11: getLine >>= (\str -> putStrLn $ "You have entered: " ++ str)
instance Monad [] where m >>= k = concat (map k m) return x = [x] fail s = [](>>=) が Maybe 型とだいぶ異なっています。 List における (>>=) は、リストの要素全てに k を作用させ、 それを concat でつなぎ合わせるということです。 また、return はリストを作ること、 fail は空リストです。
以下にリストの要素のうち奇数を選んでそれを c 倍する関数を
01: --- Monad at List 02: --- if odd then (*c) else fail 03: --- list notation 04: mul_odd1 :: Int -> [Int] -> [Int] 05: mul_odd1 c xs = xs >>= (\x -> if odd x 06: then [x*c] 07: else []) 08: 09: --- monadic notation 10: mul_odd2 :: Int -> [Int] -> [Int] 11: mul_odd2 c xs = xs >>= (\x -> if odd x 12: then return (x*c) 13: else fail "I like odd.") 14: 15: --- internal capsule 16: mul_odd3 :: Int -> [Int] -> [Int] 17: mul_odd3 c xs = [x*c | x <- xs, odd x]
EasyMonad> mul_odd2 10 [1,2,3,4,5] [10,30,50]
[定義]
sequence :: Monad m => [m a] -> m [a]
sequence = foldr mcons (return [])
where mcons p q = p >>= \x -> q >>= \y -> return (x:y)
sequence_ :: Monad m => [m a] -> m ()
sequence_ = foldr (>>) (return ())
-- The xxxM functions take list arguments, but lift the function or
-- list element to a monad type
mapM :: Monad m => (a -> m b) -> [a] -> m [b]
mapM f as = sequence (map f as)
mapM_ :: Monad m => (a -> m b) -> [a] -> m ()
mapM_ f as = sequence_ (map f as)
[例]
foo :: [Int] ->IO() foo = mapM_ print {- foo [1,2,3] ⇒ sequence_ $ map print [1,2,3] ⇒ sequence_ [(print 1), (print 2), (print 3)] ⇒ print 1 >> print 2 >> print 3 -} baz :: [Double] -> Maybe [Double] baz xs = mapM bar xs where bar x = if x > 0 then return (sqrt x) else fail "I like positive." {- baz [1,4,9] ⇒ sequence [Just 1.0, Just 2.0, Just 3.0] ⇒ Just [1.0, 2.0, 3.0] baz [1,-4,9] ⇒ sequence [Just 1.0, Nothing, Just 3.0] ⇒ Nothing -}
HOME | 7. 自前の data と class | Haskell のお勉強 | 9. 探索 | download | 書き込む |