Haskell   モナド

ホーム   目次


この章では、モナドの説明をしています。

参照透過性と副作用

Haskellは純粋関数型言語です。純粋関数型言語は、同じ値の引数に関数を適用した場合は、必ず同じ値が返されることが保証されています。この、関数の戻り値が引数の値によってのみ決まること「参照透過性」と言います。

一方、キーボードから入力を受け取ったり、ディスプレイに何か表示するというような、コンピューターの状態を変更することを「副作用」と呼びます。

しかし、参照透過性のプログラミング言語は、副作用には対応しません。副作用に対応するということは参照透過性でなくなることだからです。これでは、Hakellはキーボードから入力したり、ディスプレイに文字列を表示することさえできないことになります。

モナド

上記の問題を解決すために、Haskellはモナドという型クラスを採用しました。

型クラス Monad の IO インスタンスは、値を返す機能と、副作用を処理するという機能を、 一つの戻り値の中に包み込みました。

型クラス Monad の Maybe インスタンスは、値がないという要素と値という要素を、 一つの戻り値に包み込みました。

IO モナド

IO モナドは、入出力という副作用を処理するモナドです。

まず、ディスプレイに文字列を表示する、putStr、putStrLn、print の型を調べてみます。

Prelude> :t putStr
putStr :: String -> IO ()
Prelude> :t putStrLn
putStrLn :: String -> IO ()
Prelude> :t print
print :: Show a => a -> IO ()

3つの関数とも、IO () 型を返しています。これは戻り値が IO モナド型で、その値が、() であることを表しています。() は、ユニットと呼ばれる値で、実際には値がないことを表しています。

これらの関数は、実際の値は持っていなく、ディスプレイに文字列を表示するという、副作用だけを行います。

なお、IO モナドのメソッド(関数)のことを「IOアクション」もしくは単に「アクション」と呼びます。

次に、キーボードから文字列を読み込む、getLine アクションの型を調べてみます。

Prelude> :t getLine
getLine :: IO String

戻りの型は、IO 型ですが、今度は String という値も入っています。この String 型の値は、戻り値から取り出してから使用することになります。

直列化

一つの識別子 (関数や変数) に複数のアクションを束縛する場合は、それらのアクションがどのような順序で実行されるかを決めて、一つのアクションとしてまとめなければなりません。このことを「直列化」と言います。

直列化には「bind」と呼ばれる方法と「do」と呼ばれる方法があります。

bind

bind とは、>>= という演算子のことです。戻り値を使わない場合は、>> を使います。


a =
    putStr "enter your name: " >>
    getLine >>= \name -> putStrLn $ "Hello " ++ name ++ "!"
    
Main> a
enter your name: app
Hello app!

上記の例のように bind はつないでいくことができます。

do

do は、bind の糖衣構文 (シンタックスシュガー) です。「糖衣構文」とは、複雑な記述を簡単な記述に置き換える方法です。内部的には複雑な記述が行わています。

a = do
    putStr "enter your name: "
    name <- getLine
    putStrLn $ "Hello " ++ name ++ "!"
    

実行結果は先ほどと同じです。

<-

do 記法の中では、<- を使って、 IO モナドの戻り値から値を取り出して識別子に束縛できます。具体的に言うと、 getLine :: IO String
String だけを取り出して識別子に束縛します。


a = do
    putStr "enter your name: "
    let temp = getLine
    name <- temp
    putStrLn $ "Hello " ++ name ++ "!"
    

let

do 記法の中では、in を省略した let を使うことによって 識別子に普通の式や値を束縛できます。アクションも束縛できますが、束縛されるのは、式であって、アクションから値を取り出すわけではありません。この例の場合では、temp に束縛されるのは、getLine というアクションです。アクションから値を取り出すには、 識別子に対して、あらためて <- を使う必要があります。

Maybe モナド

Maybe は、「何かの値のあるデータかもしれない (maybe) し、何も値のないデータ かもしれない (maybe) 」というデータです。具体的には、何も値がない状態を表す Nothing という要素と、何かの値がある状態を表す Just a という要素を持つリストだと言われています。

return

return」を使うことによって、モナド値を作ることがきます。

Prelude> let a = return Nothing
Prelude> :t a
a :: Monad m => m (Maybe a)
Prelude> a
Nothing

Maybe 型の値を持つ識別子が作られ、その値は、Nothing (値がないという意味) であることがわかります。

Prelude> let b = return (Just 1)
Prelude> :t b
b :: (Monad m, Num a) => m (Maybe a)
Prelude> b
Just 1

Maybe 型の値を持つ識別子が作られ、その値は、Just 1 であることがわかります。

Prelude> let c = return 1
Prelude> :t c
c :: (Monad m, Num a) => m a
Prelude> c
1

モナドと数値を合わせも持つ型を持つ識別子が作られ、その値は、1であることがわかります。

Just

値の入ったMaybeモナドは Just キーワードでも作れます。


main = do
    let a = return 1 :: Maybe Int
        b = Just 1
    print (a, b)
    
(Just 1,Just 1)

Maybe モナドから値を取り出す

case of を使って値を取り出す


justOrNothing n =
    if n >= 0
        then (Just n)
        else Nothing
        
a = do
    putStr "enter number: "
    val <- getLine    
    let n = justOrNothing $ read val
    print n
    case n of
        Nothing -> return ()
        Just n  -> print n
    
*Main> a
enter number: 50
Just 50
50
*Main> a
enter number: (-50)
Nothing
*Main>

パターンマッチを使って値を取り出す


justOrNothing n =
    if n >= 0
        then (Just n)
        else Nothing
        
a = do
    putStr "enter number: "
    val <- getLine    
    let n = justOrNothing $ read val
    print n
    match n
    where
        match Nothing  = return ()
        match (Just n) = print  n
    
*Main> a
enter number: 50
Just 50
50
*Main> a
enter number: (-50)
Nothing
*Main>


11803 visits
Posted: Jan. 23, 2019
Update: Jan. 23, 2019

ホーム   目次   ページトップ