SE情報技術研究会’s blog

http://se-info-tech.connpass.com

2016-01-04 『ふつうのHaskellプログラミング』 もくもく読書会(第11章) の振り返り

ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門

ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門

  • 作者: 青木峰郎,山下伸夫
  • 出版社/メーカー: SBクリエイティブ
  • 発売日: 2014/10/05
  • メディア: オンデマンド (ペーパーバック)
  • この商品を含むブログを見る

第11章 モナド

モナドとは何か

  • Monadクラスのインスタンスになっているものとして、IO、Maybe、リストがある
  • モナドには演算をつなぐという目的がある

Monadクラス

class Monad where
  (>>=) :: ma -> (a -> m b) -> m b
  return :: a -> m a
  • (>>=)はバインドと読む
  • Monad(>>=)returnを実装しているだけでなくモナド則を満たしていなければならない

モナド

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

Maybeモナド

data Maybe a = Nothing | Just a deriving (Eq, Ord)
  • 値は「Nothing」か「Just x」のどちらか
  • 対象の値がある場合、「Just x」
  • 対象の値がない場合、「Nothing」

alist = association list = 2要素タプルのリスト

Maybeモナドの目的

  • Maybe を返す関数を連続して使わなければならないときに便利
  • 関数を連続して適用してるうちに結果が途中でNothingになると、それ以降の関数で適用してもNothingとして返ってくる

Maybeモナドの実例

本のサンプルでは以下の処理をしている

  1. lookup関数を使ってconfigから"database"を検索し結果を取得
  2. 1の結果からさらにlookup関数を使って"encoding"を検索
case (lookup "database" config) of
  Just entries -> lookup "encoding" entries
  Nothing -> Nothing

これを(>>=)で下記に書き換えれる

lookup "database" config >>= lookup "encoding"
メモ

GHCiで複数行を書く場合は:{:}で囲む。

例:

> :{
> case (lookup "database" config) of
>   Just entries -> lookup "encoding" entries
>   Nothing -> Nothing
> :}

Maybeモナドの仕組み

Mayboeモナドの(>>=)の実装

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

lookupの例の場合、fが「lookup "encoding"」になる

returnクラスメソッドの使い道

returnの定義

> :t return
return :: Monad m => a -> m a

Mayboeモナドのreturnの実装

instance Monad Maybe where
  return x = Just x
  …略
  • 値aを渡したら「Just a」を返す
  • (>>=)で連結している関数の中で結果にモナドを返さないものがある場合に使える

リストモナド

リストモナドの目的

  • リストモナドの目的は適用するたびに値の数が増えたり減ったりする関数を連結すること

リストモナドの実例

本のサンプルではファイル名のパターンを展開する。以下の処理をする

  1. []を使って例えば「img[01].png」とあれば「img0.png」、「img1.png」ができる
  2. {}を使って例えば「img.{png,jpg}」とあれば「img.png」、「img.jpg」ができる

上記の関数として次のものがある(実装は第13章)

  • 1の実装はexpandCharClassでされている
  • 2の実装はexpandAltWordsでされている

Maybeモナドの仕組み

リストモナドの実装

instance Monad [] where
  xs >>= f = conactMap f xs
  return x = [x]

IOモナド

IOモナドの目的

  • IOモナド = アクション
  • 遅延評価が基本のHaskellでアクションが評価される順序を確実に指定できるようにする
  • 参照透過性を保ったまま言語に副作用を持ち込む

IOモナドの概念

  • IOモナドは入出力を実行していない世界
  • IOモナドが評価されると次の2つが生成される
  • 入出力結果
  • 入出力を実行した後の世界

本の説明だとgetContentsアクションの結果「cs」をputStr csで出力する例で説明している

main = do cs <- getContents
          putStr cs

もしくは

main = getContents >>= putStr
  • 入力値「cs」を得るためにgetContentsが先に評価されることを指定している
  • getContentsが実行される前の世界とputStrが実行された後の世界は別の値とHaskellでは考える
    → そのためHaskellでは副作用が発生していないと考える

つまりgetContentの場合だと

  • 入出力結果 = 読み込んだ文字列
  • 入出力を実行した後の世界 = 現状のまま

となり、putStrの場合だと

  • 入出力結果 = なし
  • 入出力を実行した後の世界 = 文字列の表示

ということか?
※ 入出力結果と言うより関数の処理の結果としての値と言うことかと思われる

> :t getContents
getContents :: IO String
> :t putStr
putStr :: String -> IO ()

IOモナドと(>>)

下記のように書き換えれる

do putStrLn "Hello, world"
   putStrLn "Hello, again!!!"

putStrLn "Hello, World" >>= (\x -> putStrLn "Hello, again!!!")

putStrLn "Hello, World" >> putStrLn "Hello, again!!!"

入出力以外でのIOモナド

  • IOモナドは入出力以外でも使われることがある
  • 副作用が必要なものに使える
  • 例としてランダム値や現在時刻やOSとのやりとりなどにIOモナドが使われる

    モナドの構文

    do式

  • (>>=)を使った式は全てdo式に書き換えれる

let節

do式内でlet節を使う場合に注意が必要

do let n = 2 * 16
   in print n
  • インデントをletのlより右にすればOK
do let n = 2 * 16
    in print n
  • インデントをletのlに合わす場合は「in」を除くとOK
do let n = 2 * 16
   print n

do式とif

本ではインデントをifの「i」に合わすとコンパイルエラーとあるがGHCiで試したら問題なかった。 kome

> :}
| do if True
|    then putStrLn "True"
|    else putStrLn "False"
| :}

ファイルにして読み込んでも問題なし

main = do if True
          then putStrLn "True"
          else putStrLn "False"

練習問題

答えの内容で問題なし