「ほっ」と。キャンペーン

<   2013年 05月 ( 18 )   > この月の画像一覧

All About Monads 読解 8

Monad support in Haskell (続き)

Functions for use with MonadPlus

mzero と mplus を持った (Control.MonadPlus クラスのインスタンス) モナドについては2つのサポート関数が定義されている。一つ目は msum 関数だ。これはリストの sum 関数に似ているが、sum 関数では (+) の累積を計算するのに対し、msum は muplus の累積を計算する。

sum はリストの要素の総和を計算する。

Prelude> import Control.Monad
Prelude Control.Monad> sum [1..10]
55

リストモナドの mzero は [] 、mplus は(++) と同じだから、

Prelude Control.Monad> mzero :: [Int]
[]
Prelude Control.Monad> [1,2] `mplus` [3,4]
[1,2,3,4]

したがって、リストモナドの msum は concat と同じ動作になる。

Prelude Control.Monad> msum [[1,2],[],[3,4],[5]]
[1,2,3,4,5]
Prelude Control.Monad> concat [[1,2],[],[3,4],[5]]
[1,2,3,4,5]

Maybe 型では mzero は Nothing、 mplus は次のように引数に現れた最初の Just だから、

Prelude Control.Monad> Just 1 `mplus` Just 2
Just 1
Prelude Control.Monad> Nothing `mplus` Just 2
Just 2
Prelude Control.Monad> Just 1 `mplus` Nothing
Just 1
Prelude Control.Monad> Nothing `mplus` Nothing
Nothing

したがって、Maybe 型のリストに対する msum の応答はリストの中で初めて出現した Just a の値になる。

Prelude Control.Monad> msum [Nothing, Nothing, Just 1, Just 2]
Just 1

こういう振る舞いをする msum の定義は次のようになっている。

msum :: MonadPlus m => [m a] -> m a
msum xs = foldr mplus mzero xs

msum 関数を使うと再帰関数や fold を使う関数をコンパクトに記述できる。例えば次は、 example9.hs の抜粋だが、

type Variable = String
type Value = String
type EnvironmentStack = [[(Variable,Value)]]

-- lookupVar retrieves a variable's value from the environment stack
-- It uses msum in the Maybe monad to return the first non-Nothing value.
lookupVar :: Variable -> EnvironmentStack -> Maybe Value
lookupVar var stack = msum $ map (lookup var) stack

連想リストのリストを検索するという入れ子の操作を行う lookupVar 関数が、msum を使わない次のプログラムと比べてコンパクトに記述できているのが分かる。

lookupVar :: Variable -> EnvironmentStack -> Maybe Value
lookupVar var [] = Nothing
lookupVar var (e:es) =
  let val = lookup var e
  in maybe (lookupVar var es) Just val

example9.hs はコピペでも動いた。ただし import するライブラリ名を次のように変える必要がある。

import System.Environment
import Control.Monad

実行例はつぎのようになる。

Prelude> :l example9.hs
[1 of 1] Compiling Main ( example9.hs, interpreted )
Ok, modules loaded: Main.
*Main> :main depth [[("name","test"),("depth","2")],[("depth","1")]]
Just "2"

mzero と mpluse のサポート関数の2つ目は guard 関数だ。guard 関数の定義は次のようになる。

guard :: MonadPlus m => Bool -> m ()
guard p = if p then return () else mzero

guard の機能のポイントは述語 p が False の時に実行される mzero だ。 mzero >>= f == mzero なので、述語 p が False のときはそれ以降のブロックの値は全て mzero になってしまい、そのブロックの値が mzero になる。

guard を ghci で実験すると次のようになる。

Prelude> import Control.Monad
Prelude Control.Monad> :set +m
Prelude Control.Monad> let
Prelude Control.Monad| test :: Int -> Maybe Int
Prelude Control.Monad| test x = do guard (x > 5); return x
Prelude Control.Monad|
Prelude Control.Monad> test 6
Just 6
Prelude Control.Monad> test 2
Nothing

example10.hs は guard を Maybe モナドで使った例だ。コピペで動くが、import するモジュール名を次のように変える必要がある。

import System.Environment
import Data.Maybe
import Control.Monad

ghci での実行例を次ぎに示す。

Prelude> :l example10.hs
[1 of 1] Compiling Main ( example10.hs, interpreted )
Ok, modules loaded: Main.
*Main> :main 21
[Rec {name = "Bart", age = 11},Rec {name = "Lisa", age = 8},Rec {name = "Maggie", age = 2}]
*Main> :main 5
[Rec {name = "Maggie", age = 2}]

Summary

Haskell ではモナドに関連する数多くの関数が標準ライブラリに定義されている。MonadPlus クラスなどの標準ライブラリにないモナド関連の関数は、Control.Monad を import すれば使うことができる。その他のライブラリでも、Monad クラスや MonadPlus クラスのインスタンスとしてそれぞれのモジュールで代数的データ型が定義されている。

前へ 目次 次へ
[PR]
by tnomura9 | 2013-05-31 13:00 | Haskell | Comments(0)

All About Monads 読解 7

Monad support in Haskell (続き)

filterM 関数はリストの filter 関数と同じ機能をモナドの中で実行する。次の example5.hs のプログラムで、doesDirectoryExist :: FilePath -> IO Bool 関数は IO Bool を戻値として返すが、filterM はそれを第1引数の述語として取り、第二引数のリストの要素に適用している。example5.hs のプログラムのライブラリ名を調整して実行可能にしたものが次のプログラムだ。

import Control.Monad
import System.Directory
import System.Environment

main :: IO ()
main = do
  names <- getArgs
  dirs <- filterM doesDirectoryExist names
  mapM_ putStrLn dirs

実行例

Prelude> :l example5.hs
[1 of 1] Compiling Main ( example5.hs, interpreted )
Ok, modules loaded: Main.
*Main> :main foo bar Haskell
Haskell

filterM 関数の定義は次のようになる。

filterM :: Monad m => (a -> m Bool) -> [a] -> m [a]
filterM p [] = return []
filterM p (x:xs) = do
  b <- p x
  ys <- filterM p xs
  return (if b then (x:ys) else ys)

zipWithM 関数と zipWithM_ 関数の定義はつぎのようになる。

zipWithM ::(Monad m) => (a -> b -> m c) -> [a] -> [b] -> m [c]
zipWithM f xs ys = sequence (zipWith f xs ys)

zipWithM_ ::(Monad m) => (a -> b -> m c) -> [a] -> [b] -> m ()
zipWithM_ f xs ys = sequence_ (zipWith f xs ys)

zipWithM 関数は、リストの zipWith 関数と同じ機能をモナド内で発揮する。zipWith_ 関数は副作用だけが必要なときに使う。

Conditional monadic computations

モナドの条件分岐の関数は when p s と unless p s だ。when の場合は述語 p が True の時 s が実行され、そうでなければ return () が実行される。 unless は条件分岐について when と反対の動作をする。それぞれの定義は次のようになる。

when :: (Monad m) => Bool -> m () -> m ()
when p s = if p then s else return ()

unless :: (Monad m) => Bool -> m () -> m ()
unless p s = when (not p) s

ap and the lifting functions

Lifting とは普通の関数を、モナド値を引数とする等価な関数に変換することだ。つまり f :: a -> b であれば LiftM f :: m a -> m b となる。次の例のように f x = 2 * x をリフトすると、(liftM f) (Just x) = Just (2 * x) となる。

Prelude> :m Control.Monad
Prelude Control.Monad> let f x = 2 * x
Prelude Control.Monad> (liftM f) (Just 2)
Just 4

liftM は1引数関数をリフトする時の関数で定義は次のようになる。

liftM :: (Monad m) => (a -> b) -> (m a -> m b)
liftM f = \a -> do { a' <- a; return (f a') }

liftM2 は2引数関数をリフトする関数で定義はつぎのようだ。

liftM2 :: (Monad m) => (a -> b -> c) -> (m a -> m b -> m c)
liftM2 f = \a b -> do { a' <- a; b' <- b; return (f a' b') }

Control.Monad には5引数関数までをリフトする関数が準備されている。5引数関数をリフトする関数は liftM5 だ。

liftM 関数を利用すると、普通の関数をモナド内で利用するときの記述が簡潔になる。swapNames という関数を使うと "Smith, John" が "John Smith" に変換される。これをモナドの do ブロック内で使うには普通次のようにする。

getName :: String -> Maybe String
getName name = do
  let db = [("John", "Smith, John"), ("Mike", "Caine, Michael")]
  tempName <- lookup name db
  return (swapNames tempName)

しかし、liftM swapNames で普通の関数を Maybe a 型の引数をとる関数に変換してやると、次のように lookup 関数の結果を直に (liftM swapNames) 関数の引数として与えることができる。

getName :: String -> Maybe String
getName name = do
  let db = [("John", "Smith, John"), ("Mike", "Caine, Michael")]
  liftM swapNames (lookup name db)

リフト関数は高階関数と組み合わせると複雑な処理を非常にコンパクトに書くことができる。次の example7.hs を理解するためにはリストモナドの理解が必要だが、liftM 関数を使わずに書いた場合を想像するとそのコンパクトさが分かる。

-- allCombinations returns a list containing the result of
-- folding the binary operator through all combinations
-- of elements of the given lists
-- For example, allCombinations (+) [[0,1],[1,2,3]] would be
-- [0+1,0+2,0+3,1+1,1+2,1+3], or [1,2,3,2,3,4]
-- and allCombinations (*) [[0,1],[1,2],[3,5]] would be
-- [0*1*3,0*1*5,0*2*3,0*2*5,1*1*3,1*1*5,1*2*3,1*2*5], or [0,0,0,0,3,5,6,10]
allCombinations :: (a -> a -> a) -> [[a]] -> [a]
allCombinations fn [] = []
allCombinations fn (l:ls) = foldl (liftM2 fn) l ls

上のプログラムを理解するのはちょっと大変なので、(liftM2 fn) の振る舞いを見てみる。liftM2 の性質から (liftM2 (+)) :: Num a => [a] -> [a] -> [a] となることが予想できる。そこで、(liftM2 (+)) の動作を実験してみる。

Prelude> :m Control.Monad
Prelude Control.Monad> (liftM2 (+)) [0,1] [2,3]
[2,3,3,4]

これは [ (0+2), (0+3), (1+2), (1+3) ] である。内包的定義の [x+y| x <- [0,1], y <- [2,3]] に相当する。

Prelude Control.Monad> [x+y| x <- [0,1], y <- [2,3]]
[2,3,3,4]

したがって、example7.hs の foldl (liftM2 fn) l ls は l = [0,1], ls = [[2,3,4], [5,6]] の場合は、

foldl (liftM (+)) l ls = ((liftM2 (+)) ((liftM2 (+)) [0,1] [2,3,4]) [5,6]) となるから、結果は次のようになる。

Prelude Control.Monad> foldl (liftM2 (+)) [0,1] [[2,3,4],[5,6]]
[7,8,8,9,9,10,8,9,9,10,10,11]

example7.hs ではつぎのように引数にパターンを使った定義があるが、

allCombinations fn (l:ls) = foldl (liftM2 fn) l ls

よく見ると右項に allCombinations がないのでこれは再帰的定義ではない。したがって上の計算は次のようにしても同じ結果になる。

Prelude Control.Monad> foldl (liftM2 (+)) [0] [[0,1],[2,3,4],[5,6]]
[7,8,8,9,9,10,8,9,9,10,10,11]

関数適用の演算子 ($) は関数適用の結果を次々とつなげていく働きがある。

Prelude> head $ filter even [3,9,7,2,5,4]
2

ap はこの ($) をリフトしたものだ。

ap :: (Monad m) => m (a -> b) -> m a -> m b
ap = liftM2 ($)

liftM2 f x y は return f `ap` x `ap` y と同じだ。

Prelude Control.Monad> liftM2 (+) (Just 1) (Just 2)
Just 3
Prelude Control.Monad> Just (+) `ap` Just 1 `ap` Just 2
Just 3

ap を高階関数に使った効果的な例が、example8.hs だ。コピペで動くが、import するライブラリの名称を次のように変える必要がある。

import System.Environment
import Control.Monad

実行例

*Main> :main 2 "square halve negate incr"
Just (-1)

前へ 目次 次へ
[PR]
by tnomura9 | 2013-05-28 18:21 | Haskell | Comments(0)

Data.Map モジュール

Haskell で連想配列を使いたいときは Data.Map モジュールを使うらしい。Programming Haskell の 3.2 Finite Maps の記事に従って使ってみた。

Prelude> :set +m
Prelude> import Data.Map

まず曜日の省略形と正式名称のデータベース days を fromList 関数を使って作る。

Prelude Data.Map> let
Prelude Data.Map| days = fromList
Prelude Data.Map|  [("Sun","Sunday")
Prelude Data.Map|  ,("Mon","Monday")
Prelude Data.Map|  ,("Tue","Tuesday")
Prelude Data.Map|  ,("Wed","Wednesday")
Prelude Data.Map|  ,("Thu","Thursday")
Prelude Data.Map|  ,("Fri","Friday")
Prelude Data.Map|  ,("Sat","Saturday")]
Prelude Data.Map|

データベース days からキー "Sun" で検索した。

Prelude Data.Map> days ! "Sun"
"Sunday"

Map 型のデータは平衡2分木だが、foList 関数でリストにできる。

Prelude Data.Map> toList days
[("Fri","Friday"),("Mon","Monday"),("Sat","Saturday"),("Sun","Sunday"),("Thu","Thursday"),("Tue","Tuesday"),("Wed","Wednesday")]

keys 関数でキーのリストを取り出せる。

Prelude Data.Map> keys days
["Fri","Mon","Sat","Sun","Thu","Tue","Wed"]

elems 関数で値のリストを取り出せる。

Prelude Data.Map> elems days
["Friday","Monday","Saturday","Sunday","Thursday","Tuesday","Wednesday"]

lookup 関数は Prelude のものと重なるので qualifed で利用する。

Prelude Data.Map> Data.Map.lookup "Tue" days
Just "Tuesday"

空の Map 型データを作るには empty 関数を使う。

Prelude Data.Map> :unset +m
Prelude Data.Map> let fruits = empty

Map 型データに新しいデータを挿入するには insert 関数を使う。破壊的代入はできないので新しいデータを作って違う名前にバインドする。

Prelude Data.Map> let fruits2 = insert "apple" 2 fruits
Prelude Data.Map> fruits2
fromList [("apple",2)]

もう一つデータを挿入する。

Prelude Data.Map> let fruits3 = insert "orange" 5 fruits2
Prelude Data.Map> fruits3
fromList [("apple",2),("orange",5)]

map 関数を使うとリストの場合と同じように Map 型データの値の全てに操作を加える事ができる。map 関数は Prelude のものと名前が衝突するので qualified name で使う。

Prelude Data.Map> Data.Map.map (*2) fruits3
fromList [("apple",4),("orange",10)]

mapKeys 関数は、Map 型データのキーの方に操作を加える事ができる。

Prelude Data.Map> mapKeys (\x -> x ++ "s") fruits3
fromList [("apples",2),("oranges",5)]

delete 関数はキーを指定して要素を削除する。

Prelude Data.Map> let fruits4 = delete "orange" fruits3
Prelude Data.Map> fruits4
fromList [("apple",2)]

Data.Map モジュールにはこの他にも union、fold、filter、findMin、findMax など数多くの関数があり、Map 型の平衡2分木のデータをリストを扱うように手軽に扱える。

Haskell で連想配列を扱いたくなったら、Data.Map モジュールを覗くことになる。こういうお約束の情報が普及したら Haskell の垣根がずいぶんと低くなるのだろう。
[PR]
by tnomura9 | 2013-05-25 18:47 | Haskell | Comments(0)

Killer Lady

【みうめ・217】KiLLER LADY踊ってみた【東・加藤】

【すーざん・にーに】 KiLLER LADY踊ってみた 【sarah・Az.】

【C☆PY】KiLLER LADY【踊ってみた】HD

【ヤッくん】KiLLER LADY踊ってみた【フラムボノン】
[PR]
by tnomura9 | 2013-05-23 23:03 | ボーカロイド | Comments(0)

All About Monads 読解 6

Monad support in Haskell

Haskell の標準装備のモナドの機能は標準の Prelude と Control.Monad モジュールに分けられている。Prelude にはモナド関連のよく使われる関数が備えられており、あまり一般的でない機能は Control.Monad モジュールに記述されている。

In the standard prelude

Haskell 標準の Prelude モジュールには、モナドデータ型を取り扱うために Monad クラスと補助的な関数が記述されている。

The Monad class

以前に見たように Monad 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

        -- Minimal complete definition:
         -- (>>=), return
    m >> k = m >>= \_ -> k
    fail s = error s

Monad クラスの4つの多相関数のうち、最低限実装しなくてはいけないのは (>>=) と return の2つだ。後の2つの (>>) と fail はデフォールトの実装があるので、特に必要がない限り実装を記述する必要はない。

The sequencing functions

sequence 関数はモナドの補助関数の一つだ。それは、モナドの計算(Kleisli 関数やモナド値)のリストを引数にとり、それぞれを実行した結果をリストにして出力する。引数の要素のモナドの計算の一つでも失敗すると fail が実行される。sequence の定義は次のようになる。

sequence :: Monad m => [m a] -> m [a]
sequence = foldr mcons (return [])
            where mcons p q = p >>= \x -> q >>= \y -> return (x:y)

ghci で実行してみた。

Prelude> sequence [putStrLn "hello", putStrLn "world"]
hello
world
[(),()]

sequence_ 関数はモナドの計算の結果のリストを出力しない。これは、モナドの副作用だけが必要なときに便利だ。

sequence_ :: Monad m => [m a] -> m ()
sequence_ = foldr (>>) (return ())

ghci で実行してみた。

Prelude> sequence_ [putStrLn "hello", putStrLn "world"]
hello
world

The mapping functions

mapM 関数は map 関数と同じように第1引数のモナドの計算を第2引数のリストに適用してその結果をリストとして返す。

Prelude> mapM putStrLn ["hello", "dolly"]
hello
dolly
[(),()]

mapM 関数は map 関数と sequence 関数を利用して定義されている。

mapM :: Monad m => (a -> m b) -> [a] -> m [b]
mapM f as = sequence (map f as)

mapM_ 関数は結果のリストを出力しない。

Prelude> mapM_ putStrLn ["that's","great"]
that's
great

mapM_ の定義は次のようになる。

mapM_ :: Monad m => (a -> m b) -> [a] -> m ()
mapM_ f as = sequence_ (map f as)

mapM 関数はモナドの世界での map 関数として使う事ができる。

-- compare the non-monadic and monadic signatures
map :: (a -> b) -> [a] -> [b]
mapM :: Monad m => (a -> m b) -> [a] -> m [b]

The reverse binder function (=<<)

=<< 演算子は >>= と同じだが、情報の流れが逆になる。他のモナドの計算を引数のように使いたいときに利用される。

Prelude> putStrLn =<< getLine
hello
hello

=<< の定義は次のように単純だ。

(=<<) :: Monad m => (a -> m b) -> m a -> m b
f =<< x = x >>= f

In the Monad module

Haskell 98 の標準ライブラリのモナドモジュールは、多くのモナドの拡張機能がエクスポートされている。これらの機能を使うためには、Control.Monad モジュールをインポートするだけでいい。

ghci でテストしてみた。

Prelude> :m Control.Monad
Prelude Control.Monad> :info MonadPlus
class Monad m => MonadPlus m where
mzero :: m a
mplus :: m a -> m a -> m a
-- Defined in `Control.Monad'
instance MonadPlus [] -- Defined in `Control.Monad'
instance MonadPlus Maybe -- Defined in `Control.Monad'
Prelude Control.Monad> [1,2,3] `mplus` [5,6]
[1,2,3,5,6]

The MonadPlus class

Monad モジュールではモナドに mzero 要素と mplus 演算を導入するための MonadPlus モジュールが記述されている。

class Monad m => MonadPlus m where
    mzero :: m a
    mplus :: m a -> m a -> m a

Monadic versions of list functions

リストの関数をモナドで使うために一般化された関数が幾つか定義されている。そのうち、foldM については上述した。

foldM はモナド版の foldl だ。foldM ではリストは左から右へと畳み込まれる。foldM の定義は次のようになる。

foldM :: (Monad m) => (a -> b -> m a) -> a -> [b] -> m a
foldM f a [] = return a
foldM f a (x:xs) = f a x >>= \y -> foldM f y xs

しかし、fold M の定義については do ブロックを使ったほうが分かりやすい。

-- this is not valid Haskell code, it is just for illustration
foldM f a1 [x1,x2,...,xn] = do
    a2 <- f a1 x1
    a3 <- f a2 x2
    ...
    f an xn

リストの右から左への畳込みはリストを reverse してから foldM を適用すればできる。

foldM を利用するとヒツジの血統検索プログラムに強力な検索機能を付け加えることができる。example3.hsをコピーすれば ghci で実行できるが、ライブラリツリーが変更になっているので、ソースの import 部分を次のように変更する必要がある。

import Control.Monad
import System.Environment

ghci で実行すると次のようになる。

Prelude> :l example3.hs
[1 of 1] Compiling Main ( example3.hs, interpreted )
Ok, modules loaded: Main.
*Main> main
Just "Dolly"

example3.hs の traceFamily 関数は、[mother, father, father] のようなモナドのリストから先祖を検索できる。

-- traceFamily is a generic function to find an ancestor
traceFamily :: Sheep -> [ (Sheep -> Maybe Sheep) ] -> Maybe Sheep
traceFamily s l = foldM getParent s l
  where getParent s f = f s

traceFamily とモナドのリストを使って母方の祖父(母の父の父)や、父方の祖母を調べるには次のようにする。

-- we can define complex queries using traceFamily in an easy, clear way
mothersPaternalGrandfather s = traceFamily s [mother, father, father]
paternalGrandmother s = traceFamily s [father, mother]

traceFamily 関数を使うと、系統樹を複雑なパターンで検索できる。実際、"traceFamily s [father, mother]" としたほうが、paternalGrandmother を定義するより分かりやすい。

もっと典型的な foldM の使い方は、do ブロック内で使う使い方に見られる。(example4.hs) 『All About Monads 』の例はライブラリが古いのでそのままでは動かない。簡略化して動くようにしたものを次に示す。ファイルに1行にキーと値をスペース区切りしたものを読み込んで、平衡2分木の Map 型データにするプログラムだ。

addEntryFromFile

-- filename: example4.hs

import qualified Data.Map as Map
import System.IO
import System.Environment
import Control.Monad

type Dict = Map.Map String String
data Entry = Entry {key, value :: String}

addEntry :: Dict -> Entry -> Dict
addEntry d e = Map.insert (key e) (value e) d

readEntry :: String -> Entry
readEntry s = Entry (head e) (last e) where e = words s

addDataFromFile :: Dict -> Handle -> IO Dict
addDataFromFile dict hdl = do
  contents <- hGetContents hdl
  entries <- return (map readEntry (lines contents))
  return (foldl (addEntry) dict entries)

main :: IO ()
main = do
  files <- getArgs
  handles <- mapM (\f -> openFile f ReadMode) files
  dict <- foldM addDataFromFile Map.empty handles
  print (Map.toList dict)

test text の内容

apple red
orange yello
grapes purple

実行結果

Prelude> :l example4.hs
[1 of 1] Compiling Main ( example4.hs, interpreted )
Ok, modules loaded: Main.
*Main> :main test.text
Loading package array-0.4.0.0 ... linking ... done.
Loading package deepseq-1.3.0.0 ... linking ... done.
Loading package containers-0.4.2.1 ... linking ... done.
[("apple","red"),("grapes","purple"),("orange","yello")]

addDataFromFile 関数はファイルのデータを読み取ってデータベースの木構造を作るが、foldM と組み合わせる事によって複数のファイルからのデータを統合できる。

前へ 目次 次へ
[PR]
by tnomura9 | 2013-05-19 17:34 | Haskell | Comments(1)

All About Monads 読解 5

The monad laws

Monad クラスの多相関数 return と >>= は自由に実装できる訳ではなく、モナド則というルールを満たす必要がある。モナド則を実装するかどうかは Haskell コンパイラは関与しないので、自前の実装が必要だ。また、多くのMonad クラスは、基本的なモナド則いがいに、付加的な機能を持っている。

The three fundamental laws

ユーザ定義のデータ型をモナドクラスのインスタンスにして、return と >>= を実装してもそれだけではそのデータ型がモナドになる訳ではない。そのデータ型がモナドになるためには、次の3つのモナド則を return と >>= が満たさなければならない。

1. (return x) >>= f == f x
2. m >>= return == m
3. (m >>= f) >>= g == m >>= (\x -> f x >>= g)

このモナド則は return が >>= 演算子について左単位元である事、同時に右単位元でもあること、>>= 演算子が結合法則を満たしている事を要求している。

どんなタイプコンストラクタでも、return 多相関数と >>= 演算子をもち、それらが、モナド則をみたしていれば、それはモナドだ。

単位元だ、結合法則だといってもそれは圏論の言葉で考えないと意味をなさないし、モナドのプログラムを組むのに圏論の知識が必要という訳でもない。自前のモナドを作るためにはモナド則が必要なのだくらいの理解でも十分プログラムは作る事ができる。

Failure IS an option

Monad クラスの fail と >> という2つの多相関数は、モナドに必須な関数という訳ではなく、付加的なものだ。

デフォールトの fail の実装は次のようになっている。

fail s = error s

特に必要がない限り、この fail の定義をユーザが変更する必要はない。たとえば、Maybe モナドでは fail の定義は次のようになっている。

fail _ = Nothing

ghci で試してみた。

Prelude> fail "dummy" :: Maybe Int
Nothing

ちなみに、IO モナド、リストモナドでは次のようになる。

Prelude> fail "dummy" :: IO Int
*** Exception: user error (dummy)
Prelude> fail "dummy" :: [Int]
[]

fail は数学理論によるモナドには必須の要件ではない。しかし、モナドプログラミングを do 記法を使って記述するときに、ブロック内でパターンマッチの失敗が起きると fail が呼び出される。

Prelude> :set +m
Prelude> let
Prelude| fn :: Int -> Maybe [Int]
Prelude| fn idx = do
Prelude|   let l = [Just [1,2,3], Nothing, Just [], Just [7..20]]
Prelude|   (x:xs) <- l !! idx
Prelude|   return xs
Prelude|
Prelude> fn 0
Just [2,3]
Prelude> fn 1
Nothing
Prelude> fn 2
Nothing
Prelude> fn 3
Just [8,9,10,11,12,13,14,15,16,17,18,19,20]

>> 演算子は、モナドのプログラムをするときに、関数が前段の戻値を必要としないときに使われる。>> は >>= を使って次のように定義される。

(>>) :: m a -> m b -> m b
m >> k = m >>= (\_ -> k)

>> の例を ghci で実行したものを次に示す。

Prelude> do putStr "hello, " >> putStrLn "world"
Prelude|
hello, world

No way out

標準的な Monad クラスに定義されている多相関数では、モナド内の値が取り出せないのに気づいた人もいるだろう。しかし、それは仕様だ。もっとも、モナドの値を取り出そうと思えば、たとえば Maybe モナドの場合は、プログラマは Just x のパターンマッチや、fromJust 関数を使ってモナド値から値を取り出すことができる。

しかし、モナドの値を取り出す必要がなければ、Monad クラスは一方向モナドをつくりだしてくれる。一方向モナドは外部から値を取り入れたり ( return x ) モナドの関数間で値を渡したり ( >>= や >> の bind 演算子) できるが、モナドの外から値を取り出すことはできない。

IO モナドが一方向モナドの馴染み深い例だ。IOモナドの中では、IO a 型の値を戻すような関数しか使うことができないため、プログラマは一旦 IO モナドに入ると、そこから脱出することはできない。Maybe モナドや List モナドの場合はそれほど厳しい規制はなく、値を外に取り出せる。

一方向モナドの最も素晴らしい点は、副作用のある関数を IO モナドの中に閉じ込めて、モナドの外のプログラムから隔離してしまうことができるところだ。

たとえば、IO モナドの readChar :: Char 関数は、標準入力から一文字を読み取って IO Char として返す関数だが、readChar は呼び出されるたびに戻値が異なってしまう。これは、同じ入力に対しては同じ値を出力するという関数の大原則を壊してしまっている。しかし、readChar の戻値は IO タグが付いているので、IO モナド以外の環境では使うことができない。このため、副作用のある関数は IO モナドの中でしか使えないし、モナドの外部からはその値を利用することができない。このように、一方向モナドは副作用のある関数を閉じ込めて隔離してしまうことができるので、モナドの外のプログラムは副作用のことは考慮せずに記述することができるのだ。

また、モナドのプログラミングのもう一つの特徴は、モナドのコンテナの値を関数にできることだ。このばあい、モナドの値が必要になったら、run コマンドの引数としてモナドを与えることで、モナドのコンテナの関数を実行してモナド内の値を取り出すことができる。

Zero and Plus

基本的なモナド3則の他に、付加的な計算規則を持つモナドもある。このようなモナドは mzero という値と mplus という演算子を持つ。これらの値と演算子は次の4つの法則に従わなければならない。

1. mzero >>= f == mzero
2. m >>= (\x -> mzero) == mzero
3. mzero `mplus` m == m
4. m `mplus` mzero == m

この mzero と mplus に関する法則は mzero を 0、mplus を +、>>= を × の数の計算に置き換えると覚えやすい。

この付加的な mzero と mplus は MonadPlus クラスの多相関数だ。Maybe 型は MonadPlus のインスタンスたが、その実装は次のようになっている。

instance MonadPlus Maybe where
    mzero = Nothing
    Nothing `mplus` x = x
    x `mplus` _ = x

ghci で試してみた。

Prelude> :m Control.Monad
Prelude Control.Monad> mzero :: Maybe Int
Nothing
Prelude Control.Monad> Nothing `mplus` Just 1
Just 1
Prelude Control.Monad> Just 1 `mplus` Just 2
Just 1
Prelude Control.Monad> Just 2 `mplus` Nothing
Just 2

Maybe モナドの mzero は Nothing で mplus は引数のうち Nothig でない方の最初の値が返されるのがわかる。

リスト・モナドも MonadPlus のインスタンスだが、mzero は [] で mplus は ++ 演算子だ。

mplus 演算子は別々の計算値を一つのモナド値に統合するのに使われる。たとえば以前の記事で述べた羊の血統を計算するプログラムでは、Maybe モナドを使って羊の両親を検索するプログラムを次のように定義できる。

parent s = (mother s) `mplus` (father s)

この場合は、羊に母親か父親がいる場合はそのうちの一匹が値として返される。

Summary

Monad クラスのインスタンスはモナド則を満たしている必要がある。その規則はモナドの代数学的な規則からきている。モナド則の最初の2つは、return 関数が左単位元と右単位元であることを意味しており、3番目の法則は >>= 演算が結合法則に従っていることを示している。この3原則に従わない実装では、do 記法がきちんと働かない。

return と >>= 関数に加えて、Monad クラスには fail 関数が登録されている。fail クラスはモナドの働きには直接には関与していないが、do 記法の中でエラーが発生するような場合に有用だ。

MonadPlus クラスの関数 mzero と mplus は、モナドに付加的な計算を加えてモナドの計算を拡張する。

前へ 目次 次へ
[PR]
by tnomura9 | 2013-05-17 21:42 | Haskell | Comments(0)

橋下発言について

従軍慰安婦の存在を擁護するような橋下発言のおかげで、日本は韓国に完敗してしまった。これによって、日本人には倫理観がないという印象を世界に発信してしまったのだ。橋下大阪市長には即刻辞任してもらいたい。

倫理観に添わない現実が多々あると言っても、倫理観を捨てされば、社会を形作る枠組みがなくなってしまう。

中国の現在の様々な困難は、中国共産党が倫理観を失ってしまった所にその原因がある。不正や搾取が堂々と行われ、大気は汚染され、毒物や農薬によって食の安全は失われ、国土の大半の水が飲用できず、他の民族を力ずくで侵略し、自国民に堪え難い暴力を振るい、政府による嘘がまかり通っている。中国国民は亡国の危機に気づくべきだ。

しかし、倫理観を失ってしまえば、日本だって遠からず、中国の二の舞になってしまうだろう。

橋下市長、頼むから事の重大さに気がついて早期に辞任する事で日本を救ってくれ。
[PR]
by tnomura9 | 2013-05-17 07:50 | 話のネタ | Comments(0)

モナドとは何か

モナドとは何かというのは知らないでも >>= 演算子が左項のモナド値のコンテナから値を取り出して、右項の Kliesli 射の引数として渡すという仕組みを知っていれば Haskell のモナドは使いこなせる。

しかし、モナドとは一体何なのだろうという疑問が残るのは仕方がない。管理人も分かっているのかと問われると、わかっていない。しかし、いろんな文書を漁って、こんなものではないかと思ったので書いてみる。

まず、圏とは何かだが、恒等射の存在や射の結合法則などの条件をおいておいて、ここでは対象(値の集合)とその対象を関係づける射(関数)とからなるシステムと漠然と捉えることにする。

関手Tとは、このような圏 C, B の対象から対象への関数(対象関数) T(c) と射から射への関数(射関数) T(f) の組みからなっている。

自己関手とは、圏 C から圏 C 自身への関手で、モナドを形作るのはこの自己関手だ。たとえば、Haskell の値 x を x のシングルトンリスト [x] に対応させる関数 fT を考えてみる。x も [x] も Haskell 圏の値だから、この関数は Haskell 圏 C から自分自身への自己関手(対象関数)だ。

Prelude> let fT x = [x]
Prelude> fT 3
[3]

関数 fT の値域 (codomain) は自分自身だから fT x にさらに fT を関数適用させることができる。つまり、fT の合成関数 fT . fT を考える事ができる。

Prelude> (fT . fT) 3
[[3]]

さらに fT . (fT . fT) だって考えられる。

Prelude> (fT . (fT . fT)) 3
[[[3]]]

要するに、モナドとは、このような関手の集合 F = { fT, (fT.fT), (fT. (fT.fT)), ... } に関手から関手への関数μ:T.T -> Tとη:I -> Tを導入してモノイドにしたものだ。このモノイドは圏論の意味でのモノイドで、集合のモノイドに翻訳はできるが、集合のモノイドの概念そのままではないので注意が必要だ。

そこで、次のような圏 C 上の関数を考えてみる。

Prelude> let mu [[x]] = [x]

そうすると、次のように mu (fT . fT) = fT となることが分かる。

Prelude> mu ((fT . fT) 3)
[3]

さらに、次のような関数ηを作る。

Prelude> let eta x = [x]

すると、次のような計算ができる。

Prelude> (mu . (eta . fT)) 3
[3]
Prelude> (mu . (fT . eta)) 3
[3]

これは、μ ηI fT = μ fT ηI = fT というのと同じだ。これは μ という関手を引数にとる2項演算について、ηI が右単位元と左単位元であることを示している。

モナドの μ や η の操作的な意味だが、どうして、これがモノイドなのかは圏論をしっかり勉強しないと分からないのだろう。Haskell のモナドのプログラムとどう関係があるのかさっぱり分からない。

しかし、先人のすばらしい直観に感謝しながら、>>= は左項のコンテナから値を取り出して、右項の Kleisli 射に渡せばいいのだなどという操作的な理解で、do 記法やら >>= を利用してせっせとプログラムを作ればとくに問題なく動いてくれる。

追記

η の定義は、

η x = T x

だし、関手 T の定義は

T x = T x

だ。すると、これは、そういってよければ全く同じようなものだ。そうすると、モナドの自然変換は η 一つだけを要素とし、μを演算とする、単位元だけの自明な群を作る。つまり、

{ η } において、η μ η = η だ。

また、η x = T x で、μ (T ( T x)) = T x だから、ηと μ が反対の作用を持っている事が分かる。また、μ (T x) = x は定義上あり得ないので、μ の演算を使ってもデータは T x から出られない。η を使って x を T x の世界に放り込んだ後は、その後のデータは絶対に T x から出られない。

η x でモナド T の世界に投げ込まれた x は、蟻地獄のように二度と出てこれないというのが、モナドの構造の本質なのではないだろうか。

ここで、Haskell 圏 の値 x と 関数 f があったとしよう。関手 T による x の像は η x で、f の像が f* だとすると f による x の像 f x の関手 T による像は η (f x) = f* η x になる。η を (η μ η) で置き換えるとη (f x) = f* (η μ η) x = (f* η) μ (η x) となるので、関手 T による f x の像は f* と ηの合成関数と x の像 η x を μ 演算子で結合したものになる。(f* η) :: x -> T x だから、これは Kleisli 射だ。また、η (f x) との比較で、(f" η) x = η (f x) であることが分かる。ただし、(f* η) μ (η x) はかたちの上からそう見えるというだけで、理論的にもそうなのかはわからないし、(η μ η) を切り離す事はできない。μ (η x) で x のみを取り出すことはできない。

しかし、モナドの計算や構造を考える上で η μ η = η という関係式が重要なのではないかという気がする。

たとえば、η μ η についてだが、次のような μ' を考えてみると、これもまた η μ' η = η という関係を満たしている。

μ' (T x) = x

(η μ’η) x = η (μ' (η x)) = η (μ'(T x)) = η x = T x

従って

η μ' η = η

が成立する。モナドの場合と違って、μ' も ηも関数なのでその意味は簡単だが、μと同様 f x の関手による像は、x の像 η x と Kleisli 射の演算子 μ による結合になる。しかし、μ' の場合は μ' (T x) = x だから、モナド値から簡単に x を取り出す事ができる。

η x でモナド T の世界に投げ込まれた x が出て来れなくなってしまうのは、T が自己関手であることと、μ (T . T) = T という自然変換の定義の工夫によるものである事がわかる。

モナドを数学的に理解するのは独学では無理なようだ、しかし、>>= 演算子で 右項の M a の a を取り出して、左項の Kleisli 射の引数として与えるということがモナドの本質なら、モナドを使いこなすのに何の難しいこともない。
[PR]
by tnomura9 | 2013-05-14 12:55 | Haskell | Comments(0)

bind 演算子 (>>=) の意味

モナドでは bind 演算子が重要な働きをする。それは2つの Kliesli 射を合成して新しい Kleisli 射を作り出す。モナドのプログラムとは、このような Kleisli 射の組み合わせで新しい Kleisli 射を作り出すということだ。

例えば Maybe モナドの次のようなプログラムを見てみよう。

Prelude> Just 2 >>= \x -> Just (x*2) >>= \x -> Just (x+1)
Just 5

上のプログラムで、\x -> Just (x*2) >>= \x -> Just (x+1) は2つの Kliesli 射(引数が1つで戻値が Maybe a 型の関数) が >>= 演算子で合成され、それに Just 2 で値2が与えられていると考えることができる。

実際、上のプログラムは、次のプログラムと等価だ。

Prelude> Just 2 >>= \x -> Just ((x*2) + 1)
Just 5

このことは、 \x -> Just (x*2) という関数と \x -> Just (x+1) という2つの関数が >>= 演算子で合成されて、\x -> Just ((x*2) + 1) という関数が合成されたことを示している。

しかし、この >>= 演算子による関数の合成には、不可解なところがある。一般に集合 A から集合 B への関数が f :: A -> B で、集合 B から集合 C への関数が g :: B -> C のとき、その合成 g . f は、g . f :: A -> C のように f と g と g . f は定義域と値域がそれに応じて変化するはずだ。

ところが、>>= の場合は、左項の関数も、右項の関数も、合成の結果の関数も次のように同じ a -> Maybe a 型になっている。 >>= による合成を関数の合成 . と比較しても、どうも変な話なのだ。

Prelude> :t \x -> Just (x*2)
\x -> Just (x*2) :: Num a => a -> Maybe a
Prelude> :t \x -> Just (x+1)
\x -> Just (x+1) :: Num a => a -> Maybe a
Prelude> :t \x -> Just (x*2) >>= \x -> Just (x+1)
\x -> Just (x*2) >>= \x -> Just (x+1) :: Num b => b -> Maybe b

これは、実は Kleisli 射 :: a -> Maybe b が一般的な意味での関数ではなく、Haskell 圏の自己関手であるからだ。Kleisli 射は Haskell 圏の自己関手のうちの対象関数だ。そうして、>>= 演算子も単なる関数の合成ではなく、2つの自己関手の垂直合成を行う自然変換なのだ。

どうりで、次のようなモナド則で、1行目が return の >>= に対する左単位元則であり、2行めが return の >>= に対する右単位原則であり、3行目が >>= の結合法則を表していると言われても理解できないはずだ。

return x >>= f == f x
m >>= return == m
(m >>= f) >>= g == m >>= (\x -> f x >>= g)

しかし、幸いな事に圏論と無縁でも、>>= が2つの Kleisli 射 (引数が1つで戻値がモナド値) の関数を合成する演算子で、その合成の仕方は、左項のモナド値のコンテナの値をとりだして、右項の Kliesli 射の引数として与えることで行われるという、甚だ操作的な理解の仕方でも十分モナドを使いこなすことができる。

モナド則にしても >>= と return が上の3つの法則に合致しているのを確かめることができれば、別にことさら触れなくてもいい。また、自前のモナドを実装するときも上に述べたような >>= の動作を真似れば、自然にモナド則も満たされる。

Prelude> :set +m
Prelude> data M a = M a deriving Show
Prelude> instance Monad M where
Prelude|   return x = M x
Prelude|   M x >>= f = f x
Prelude|

Prelude> return 2 >>= \x -> M (x*2)
M 4
Prelude> M 2 >>= return
M 2
Prelude> (M 2 >>= \x -> M (x*2)) >>= \x -> M (x+1)
M 5
Prelude> M 2 >>= (\x -> M (x*2) >>= \x -> M (x+1))
M 5

「単なる自己関手のモノイド対象」など無視しても、モナドのプログラムは自由自在にできるのだ。
[PR]
by tnomura9 | 2013-05-12 11:18 | Haskell | Comments(0)

All About Monads 読解 4

Doing it with class (example2.hs)

Haskell type classes

このセクションはタイプクラスに習熟していないと理解できない。タイプクラスについてはこのブログの『タイプクラスとインスタンス』に簡単に説明している。

ところで、モナドとは何かという答えに対しては、実は Meet the Monads のプログラム例が全てを語っている。

Meet the Monads の羊の血統を調べるプログラムでは、Maybe 型が大活躍していた。

また、Maybe 型を活用するための道具として、Sheep 型のデータを Maybe 型にラッピングするためのデータコンストラクタ Just a があった。

さらに、mother :: Sheep -> Maybe Sheep 関数のように、Sheep を引数とし Maybe Sheep を戻値とする関数(Kleisli 射)があった。

最後に、Maybe Sheep 型のデータからコンテナの Sheep 型のデータを取り出して、mother や father などの Kleisli 射に渡して戻値 Maybe Sheep 型のデータを返す comb :: Sheep -> (Sheep -> Maybe Sheep) -> Maybe Sheep 関数を作成した。

これらの Maybe 型と、Just Sheep と comb 関数(モナドでは >>= 演算子)を組み合わせることで合成 Kleili 射が簡単にできた。

Kleisli 射の合成のメカニズムを分かりやすいようにするため、m をモナドのタイプコンストラクタ、a, b, c, ... を Haskell のデータ型だと表すことにすると、

m a `comb` (a -> m b) `comb` (b -> m c) `comb` (c -> m d) --------> m d

となる。(a -> m b) `comb` (b -> m c) `comb` (c -> m d) が合成関数で a 型のデータを引数とし、m d 型の値を返すのでこれも Kleisli 射になる。

注:

m a >>= (a -> m b) で、m a は値で Kleilsi 射ではないが、次のようにラムダ抽象を使うと、Kleisli 射として表現できる。

Prelude> (\x -> return x >>= \x -> putStrLn x) "hello"
hello

このような a -> m b 型の関数の連結がモナドの機能の全てだと言い切って良い。これには、何ら特殊なライブラリは関係してこない。タイプコンストラクタ m も データコンストラクタ m a も comb 関数も普通に定義できる。別に Monad クラスのインスタンスでなくてもかまわないのだ。

しかし、このような Kleisli 射(関数)の連鎖を利用できる型は多い、Maybe 型、Either 型、[] (リスト) 型、State 型、Reader 型、Writer 型、そして IO 型だ。これらの型は一様にデータコンストラクタ return、>>= 演算子を用いて、Kleisli 射(関数)型の関数のひと続きの合成を利用するし、同じ定型的な手続をとる。そうであれば、この手続きを共用するようにすれば、似たようなプログラムの共通のインターフェースを作ることができる。

そうであれば、どうすればいいのだろうか。答えはデータコンストラクタの働きをする return と関数合成の演算子 >>= を Monad タイプクラスの多相関数にして、Maybe や IO などのタイプコンストラクタを Monad タイプのインスタンスにすればいいのだ。

Monad タイプクラスの意味とは return と >>= を多相関数にするというだけのことでしかない。モナドの神秘性をおおいに減じてしまうが、そうなのだ。

The Monad class

モナドクラスは return と >>= を多相関数にするだけの機能を果たすだけだから、つぎのように return と >>= の型を宣言しているだけだ。

class Monad m where
    (>>=) :: m a -> (a -> m b) -> m b
    return :: a -> m a

別に、この多相関数を使わなくてもいいのだが、自分のモナドを作るときに、それを Monad クラスのインスタンスにして return 関数と >>= 演算子を使うことにより、共通のインターフェースを利用できることになる。また、プログラムの読み手も扱い慣れたモナドの多相関数が使われることにより、プログラムの解読が容易になる。

Example continued

羊の血統探索のプログラムでは、モナドのコンストラクタに直接的な Just a を用い、>>= 演算子の働きをする comb 関数を自前で定義したが、Maybe 型は既に Monad クラスのインスタンスであり、return と >>= の多相関数は次のようにライブラリで定義されている。

instance Monad Maybe where
    Nothing >>= f = Nothing
    (Just x) >>= f = f x
    return = Just

これを見ると >>= の定義は羊の血統探索プログラムの comb 関数と全く同じだ。そこで、このプログラムは comb を >>= に置換することができる。

次のプログラムは、羊の血統探索プログラムをモナドの記法で置き換えたものだ。

-- filename: example1b.hs

data Sheep = Sheep {name::String, mother::Maybe Sheep, father::Maybe Sheep}

instance Show Sheep where
show s = show (name s)

maternalGrandfather :: Sheep -> Maybe Sheep
maternalGrandfather s = return s >>= mother >>= father

fathersMaternalGrandmother :: Sheep -> Maybe Sheep
fathersMaternalGrandmother s = return s >>= father >>= mother >>= mother

mothersPaternalGrandfather :: Sheep -> Maybe Sheep
mothersPaternalGrandfather s = return s >>= mother >>= father >>= father

adam = Sheep "Adam" Nothing Nothing
eve = Sheep "Eve" Nothing Nothing
uranus = Sheep "Uranus" Nothing Nothing
gaea = Sheep "Gaea" Nothing Nothing
kronos = Sheep "Kronos" (Just gaea) (Just uranus)
holly = Sheep "Holly" (Just eve) (Just adam)
roger = Sheep "Roger" (Just eve) (Just kronos)
molly = Sheep "Molly" (Just holly) (Just roger)
dolly = Sheep "Dolly" (Just molly) Nothing

このプログラムも次のように実行できる。

*Main> maternalGrandfather dolly
Just "Roger"

さて、自分のデータ型を Monad のインスタンスにするためには、実は retrun と >>= 演算子の実装をするだけでいい。それができれば、自動的に do 記法を使えるようになる。次の例が do 記法を使って上のプログラムを記述したものだ。do 記法は >>= 演算子で Kleisli 射(関数)を連結していくプログラムを、手続き言語風に記述するためのシンタックスシュガーだ。

-- filename: example2.hs

data Sheep = Sheep {name::String, mother::Maybe Sheep, father::Maybe Sheep}

instance Show Sheep where
  show s = show (name s)

maternalGrandfather :: Sheep -> Maybe Sheep
maternalGrandfather s = do
  m <- mother s
  gf <- father m
  return gf

fathersMaternalGrandmother :: Sheep -> Maybe Sheep
fathersMaternalGrandmother s = do
  f <- father s
  gm <- mother f
  ggm <- mother gm
  return ggm

mothersPaternalGrandfather :: Sheep -> Maybe Sheep
mothersPaternalGrandfather s = do
  m <- mother s
  gf <- father m
  ggf <- father gf
  return ggf

adam = Sheep "Adam" Nothing Nothing
eve = Sheep "Eve" Nothing Nothing
uranus = Sheep "Uranus" Nothing Nothing
gaea = Sheep "Gaea" Nothing Nothing
kronos = Sheep "Kronos" (Just gaea) (Just uranus)
holly = Sheep "Holly" (Just eve) (Just adam)
roger = Sheep "Roger" (Just eve) (Just kronos)
molly = Sheep "Molly" (Just holly) (Just roger)
dolly = Sheep "Dolly" (Just molly) Nothing

このように、モナドというのは Kleisli 射(関数)を >>= 演算子で連結していくための仕組みだという意味しかないという事を理解すれば、モナドの神秘性に惑わされることなく、自分のプログラムに積極的に使っていくことができるだろう。

自前のモナドを作るのは簡単だ、自前のデータ型を Monad クラスのインスタンスにして、return と >>= というたった2つの関数を実装するだけだから。たったそれだけの操作で、自前のデータ型が立派なモナドに変身する。

たとえば、次のような簡単なデータ型 M a も立派なモナドにする事ができる。

Prelude> data M a = M a deriving Show

Prelude> let double x = M (2 * x)
Prelude> let addOne x = M (x + 1)

Prelude> :set +m
Prelude> instance Monad M where
Prelude|   return = M
Prelude|   M x >>= f = f x
Prelude|

Prelude> return 2 >>= double >>= addOne
M 5

Maybe モナドやリストモナド、Stateモナド、IO モナドの多彩な振る舞いに目を奪われて、必要以上にモナドを難しく考えてはいないだろうか。しかし、モナドとは上に述べたような単純な構造(フレームワーク)を提供してるだけなのだ。標準モナドなどの多彩な振る舞いは、それらの代数的データ型の定義の巧妙さに負っているだけで、モナドの本質とは関係ないのだ。

前へ 目次 次へ
[PR]
by tnomura9 | 2013-05-08 21:48 | Haskell | Comments(0)